8000 Add support for GPG keys · jacquerie/github3.py@9f99587 · GitHub
[go: up one dir, main page]

Skip to content

Commit 9f99587

Browse files
committed
Add support for GPG keys
Implement the CRUD API for users' GPG keys described in https://developer.github.com/v3/users/gpg_keys/. Closes sigmavirus24#704
1 parent b8e7aa8 commit 9f99587

File tree

12 files changed

+395
-8
lines changed

12 files changed

+395
-8
lines changed

src/github3/github.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -335,6 +335,24 @@ def create_gist(self, description, files, public=True):
335335
json = self._json(self._post(url, data=new_gist), 201)
336336
return self._instance_or_null(gists.Gist, json)
337337

338+
@requires_auth< 8000 /span>
339+
def create_gpg_key(self, armored_public_key):
340+
"""Create a new GPG key.
341+
342+
.. versionadded:: 1.2.0
343+
344+
:param str armored_public_key:
345+
(required), your GPG key, generated in ASCII-armored format
346+
:returns:
347+
the created GPG key if successful, otherwise ``None``
348+
:rtype:
349+
:class:`~github3.users.GPGKey`
350+
"""
351+
url = self._build_url('user', 'gpg_keys')
352+
data = {'armored_public_key': armored_public_key}
353+
json = self._json(self._post(url, data=data), 201)
354+
return self._instance_or_null(users.GPGKey, json)
355+
338356
@requires_auth
339357
def create_issue(self, owner, repository, title, body=None, assignee=None,
340358
milestone=None, labels=[], assignees=None):
@@ -740,6 +758,39 @@ def gitignore_templates(self):
740758
url = self._build_url('gitignore', 'templates')
741759
return self._json(self._get(url), 200) or []
742760

761+
@requires_auth
762+
def gpg_key(self, id_num):
763+
"""Retrieve the GPG key of the authenticated user specified by id_num.
764+
765+
.. versionadded:: 1.2.0
766+
767+
:returns:
768+
the GPG key specified by id_num
769+
:rtype:
770+
:class:`~github3.users.GPGKey`
771+
"""
772+
url = self._build_url('user', 'gpg_keys', id_num)
773+
json = self._json(self._get(url), 200)
774+
return self._instance_or_null(users.GPGKey, json)
775+
776+
@requires_auth
777+
def gpg_keys(self, number=-1, etag=None):
778+
"""Iterate over the GPG keys of the authenticated user.
779+
780+
.. versionadded:: 1.2.0
781+
782+
:param int number: (optional), number of GPG keys to return. Default:
783+
-1 returns all available GPG keys
784+
:param str etag: (optional), ETag from a previous request to the same
785+
endpoint
786+
:returns:
787+
generator of the GPG keys belonging to the authenticated user
788+
:rtype:
789+
:class:`~github3.users.GPGKey`
790+
"""
791+
url = self._build_url('user', 'gpg_keys')
792+
return self._iter(int(number), url, users.GPGKey, etag=etag)
793+
743794
@requires_auth
744795
def is_following(self, username):
745796
"""Check if the authenticated user is following login.

src/github3/users.py

Lines changed: 146 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,100 @@
1212
from .events import Event
1313

1414

15+
class GPGKey(models.GitHubCore):
16+
"""The object representing a user's GPG key.
17+
18+
.. versionadded:: 1.2.0
19+
20+
Please see GitHub's `GPG Key Documentation` for more information.
21+
22+
.. _GPG Key Documentation:
23+
https://developer.github.com/v3/users/gpg_keys/
24+
25+
.. attribute:: can_certify
26+
27+
TODO
28+
29+
.. attribute:: can_encrypt_comms
30+
31+
TODO
32+
33+
.. attribute:: can_encrypt_storage
34+
35+
TODO
36+
37+
.. attribute:: can_sign
38+
39+
TODO
40+
41+
.. attribute:: created_at
42+
43+
A :class:`~datetime.datetime` representing the date and time when
44+
this GPG key was created.
45+
46+
.. attribute:: emails
47+
48+
TODO.
49+
50+
.. attribute:: expires_at
51+
52+
A :class:`~datetime.datetime` representing the date and time when
53+
this GPG key will expire.
54+
55+
.. attribute:: id
56+
57+
The unique identifier of this GPG key.
58+
59+
.. attribute:: key_id
60+
61+
TODO
62+
63+
.. attribute:: primary_key_id
64+
65+
TODO
66+
67+
.. attribute:: public_key
68+
69+
TODO
70+
71+
.. attribute:: subkeys
72+
73+
TODO
74+
"""
75+
76+
def _update_attributes(self, key):
77+
self.can_certify = key['can_certify']
78+
self.can_encrypt_comms = key['can_encrypt_comms']
79+
self.can_encrypt_storage = key['can_encrypt_storage']
80+
self.can_sign = key['can_sign']
81+
self.created_at = self._strptime(key['created_at'])
82+
self.emails = [ShortEmail(email, self) for email in key['emails']]
83+
self.expires_at = self._strptime(key['expires_at'])
84+
self.id = key['id']
85+
self.key_id = key['key_id']
86+
self.primary_key_id = key['primary_key_id']
87+
self.public_key = key['public_key']
88+
self.subkeys = [GPGKey(subkey, self) for subkey in key['subkeys']]
89+
90+
def _repr(self):
91+
return '<GPG Key [{0}]>'.format(self.key_id)
92+
93+
def __str__(self):
94+
return self.key_id
95+
96+
@requires_auth
97+
def delete(self):
98+
"""Delete this GPG key.
99+
100+
:returns:
101+
True if successful, False otherwise
102+
:rtype:
103+
bool
104+
"""
105+
url = self._build_url('user', 'gpg_keys', self.id)
106+
return self._boolean(self._delete(url), 204, 404)
107+
108+
15109
class Key(models.GitHubCore):
16110
"""The object representing a user's SSH key.
17111
@@ -121,15 +215,26 @@ def is_free(self):
121215
return self.name == 'free' # (No coverage)
122216

123217

124-
class Email(models.GitHubCore):
125-
"""The object used to represent an AuthenticatedUser's email.
218+
class _Email(models.GitHubCore):
219+
"""Base email object."""
126220

127-
Please see GitHub's `Emails documentation`_ for more information.
221+
class_name = '_Email'
128222

129-
.. _Emails documentation:
130-
https://developer.github.com/v3/users/emails/
223+
def _update_attributes(self, email):
224+
self.email = email['email']
225+
self.verified = email['verified']
226+
227+
def _repr(self):
228+
return '<{0} [{1}]>'.format(self.class_name, self.email)
229+
230+
def __str__(self):
231+
return self.email
232+
233+
234+
class ShortEmail(_Email):
235+
"""The object used to represent an email attached to a GPG key.
131236
132-
The attributes represented on this object include:
237+
This object has the following attributes:
133238
134239
.. attribute:: email
135240
@@ -139,16 +244,35 @@ class Email(models.GitHubCore):
139244
140245
A boolean value representing whether the address has been verified or
141246
not
247+
"""
248+
249+
class_name = 'ShortEmail'
250+
251+
252+
class Email(_Email):
253+
"""The object used to represent an AuthenticatedUser's email.
254+
255+
Please see GitHub's `Emails documentation`_ for more information.
256+
257+
.. _Emails documentation:
258+
https://developer.github.com/v3/users/emails/
259+
260+
This object has all of the attribute of :class:`ShortEmail` as well as
261+
the following attributes:
142262
143263
.. attribute:: primary
144264
145265
A boolean value representing whether the address is the primary
146266
address for the user or not
267+
268+
.. attribute:: visibility
269+
270+
A string value representing whether an authenticated user can view the
271+
email address. Use ``public`` to allow it, ``private`` to disallow it.
147272
"""
148273

149274
def _update_attributes(self, email):
150-
self.email = email['email']
151-
self.verified = email['verified']
275+
super(Email, self)._update_attributes(email)
152276
self.primary = email['primary']
153277
self.visibility = email['visibility']
154278

@@ -276,6 +400,20 @@ def following(self, number=-1, etag=None):
276400
url = self._build_url('following', base_url=self._api)
277401
return self._iter(int(number), url, ShortUser, etag=etag)
278402

403+
def gpg_keys(self, number=-1, etag=None):
404+
"""Iterate over the GPG keys of this user.
405+
406+
.. versionadded:: 1.2.0
407+
408+
:param int number: (optional), number of GPG keys to return. Default:
409+
-1 returns all available GPG keys
410+
:param str etag: (optional), ETag from a previous request to the same
411+
endpoint
412+
:returns: generator of :class:`GPGKey <GPGKey>`\ s
413+
"""
414+
url = self._build_url('gpg_keys', base_url=self._api)
415+
return self._iter(int(number), url, GPGKey, etag=etag)
416+
279417
def keys(self, number=-1, etag=None):
280418
r"""Iterate over the public keys of this user.
281419

tests/cassettes/GPGKey_delete.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"http_interactions": [{"request": {"body": {"string": "", "encoding": "utf-8"}, "headers": {"Accept-Encoding": ["gzip, deflate"], "Accept": ["application/vnd.github.v3.full+json"], "User-Agent": ["github3.py/1.1.0"], "Accept-Charset": ["utf-8"], "Connection": ["keep-alive"], "Content-Type": ["application/json"], "Authorization": ["token <AUTH_TOKEN>"]}, "method": "GET", "uri": "https://api.github.com/user/gpg_keys/407238"}, "response": {"body": {"string": "", "base64_string": "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", "encoding": "utf-8"}, "headers": {"X-XSS-Protection": ["1; mode=block"], "Content-Security-Policy": ["default-src 'none'"], "Access-Control-Expose-Headers": ["ETag, Link, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval"], "Transfer-Encoding": ["chunked"], "Access-Control-Allow-Origin": ["*"], "X-Frame-Options": ["deny"], "Status": ["200 OK"], "X-GitHub-Request-Id": ["D3E4:516C:9D3C2B:167E04C:5B68B87E"], "ETag": ["W/\"237167c8cd2a7c37786fabdf4c456cc5\""], "Date": ["Mon, 06 Aug 2018 21:07:11 GMT"], "X-RateLimit-Remaining": ["4999"], "Strict-Transport-Security": ["max-age=31536000; includeSubdomains; preload"], "Server": ["GitHub.com"], "X-OAuth-Scopes": ["admin:gpg_key"], "X-GitHub-Media-Type": ["github.v3; param=full; format=json"], "X-Content-Type-Options": ["nosniff"], "Content-Encoding": ["gzip"], "X-Runtime-rack": ["0.049307"], "Vary": ["Accept, Authorization, Cookie, X-GitHub-OTP"], "X-RateLimit-Limit": ["5000"], "Cache-Control": ["private, max-age=60, s-maxage=60"], "Referrer-Policy": ["origin-when-cross-origin, strict-origin-when-cross-origin"], "Content-Type": ["application/json; charset=utf-8"], "X-Accepted-OAuth-Scopes": ["admin:gpg_key, read:gpg_key, write:gpg_key"], "X-RateLimit-Reset": ["1533593231"]}, "status": {"message": "OK", "code": 200}, "url": "https://api.github.com/user/gpg_keys/407238"}, "recorded_at": "2018-08-06T21:07:11"}, {"request": {"body": {"string": "", "encoding": "utf-8"}, "headers": {"Content-Length": ["0"] 3CAD , "Accept-Encoding": ["gzip, deflate"], "Accept": ["application/vnd.github.v3.full+json"], "User-Agent": ["github3.py/1.1.0"], "Accept-Charset": ["utf-8"], "Connection": ["keep-alive"], "Content-Type": ["application/json"], "Authorization": ["token <AUTH_TOKEN>"]}, "method": "DELETE", "uri": "https://api.github.com/user/gpg_keys/407238"}, "response": {"body": {"string": "", "encoding": null}, "headers": {"Status": ["204 No Content"], "X-RateLimit-Remaining": ["4998"], "X-GitHub-Media-Type": ["github.v3; param=full; format=json"], "X-Runtime-rack": ["0.046726"], "X-Content-Type-Options": ["nosniff"], "Access-Control-Allow-Origin": ["*"], "Access-Control-Expose-Headers": ["ETag, Link, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval"], "X-GitHub-Request-Id": ["D3E4:516C:9D3C37:167E056:5B68B87F"], "Strict-Transport-Security": ["max-age=31536000; includeSubdomains; preload"], "X-XSS-Protection": ["1; mode=block"], "Server": ["GitHub.com"], "Content-Security-Policy": ["default-src 'none'"], "X-RateLimit-Limit": ["5000"], "Date": ["Mon, 06 Aug 2018 21:07:11 GMT"], "X-OAuth-Scopes": ["admin:gpg_key"], "Referrer-Policy": ["origin-when-cross-origin, strict-origin-when-cross-origin"], "Content-Type": ["application/octet-stream"], "X-Accepted-OAuth-Scopes": ["admin:gpg_key"], "X-Frame-Options": ["deny"], "X-RateLimit-Reset": ["1533593231"]}, "status": {"message": "No Content", "code": 204}, "url": "https://api.github.com/user/gpg_keys/407238"}, "recorded_at": "2018-08-06T21:07:11"}], "recorded_with": "betamax/0.8.1"}

0 commit comments

Comments
 (0)
0