Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add API for token management #2

Merged
merged 1 commit into from
Dec 24, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions docs/examples.rst
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,29 @@ Below is an example demonstrating how to update a user (requires admin privilege
"username_to_update",
update)


Example 3: Creating a token
---------------------------

Below is an example illustrating how to create a token for you application::

import gogs_client
from getpass import getpass
from platform import node

api = GogsApi("https://try.gogs.io/")

try: token_str = open("tokenfile.txt","r").read()
except OSError: token_str = None
if token_str:
token = gogs_client.Token(token_str)
else:
username = input("username> ")
password = getpass("password> ")
login = gogs_client.UsernamePassword(username, password)
token = api.ensure_token(login, "my cool app on "+node(), username)
open("tokenfile.txt", "w".write(token.token))

username = api.authenticated_user(token)
print("User {} authenticated by token {}".format(username, token_str))

19 changes: 18 additions & 1 deletion gogs_client/auth.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"""
Various classes for Gogs authentication
"""
from gogs_client.entities import json_get


class Authentication(object):
Expand All @@ -22,11 +23,27 @@ class Token(Authentication):
"""
An immutable representation of a Gogs authentication token
"""
def __init__(self, token):
def __init__(self, token, name=None):
"""
:param str token: contents of Gogs authentication token
"""
self._token = token
self._name = name

@staticmethod
def from_json(parsed_json):
name = json_get(parsed_json, "name")
sha1 = json_get(parsed_json, "sha1")
return Token(sha1, name)

@property
def name(self):
"""
The name of the token

:rtype: str
"""
return self._name

@property
def token(self):
Expand Down
63 changes: 63 additions & 0 deletions gogs_client/interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from gogs_client._implementation.http_utils import RelativeHttpRequestor, append_url
from gogs_client.entities import GogsUser, GogsRepo
from gogs_client.auth import Token


class GogsApi(object):
Expand Down Expand Up @@ -43,6 +44,68 @@ def authenticated_user(self, auth):
response = self._get("/user", auth=auth)
return GogsUser.from_json(self._check_ok(response).json())

def get_tokens(self, auth, username=None):
"""
Returns tokens defined for specified user.
If no user specified uses user authenticated by the given authentication.
Right now, authentication must be UsernamePassword (not Token).
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why must auth be UsernamePassword? self.authenticated_user(..) and self._get(..) should be able to handle any authentication type.

Copy link
Contributor Author

@pyhedgehog pyhedgehog Dec 22, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, 👍


:param auth.Authentication auth: authentication for user to retrieve
:param str username: username of owner of tokens

:return: list of token representation
:rtype: List[Token]
:raises NetworkFailure: if there is an error communicating with the server
:raises ApiFailure: if the request cannot be serviced
"""
if username is None:
username = self.authenticated_user(auth).username
response = self._get("/users/{u}/tokens".format(u=username), auth=auth)
return [Token.from_json(o) for o in self._check_ok(response).json()]

def create_token(self, auth, name, username=None):
"""
Creates new token with specified name for specified user.
If no user specified uses user authenticated by the given authentication.
Right now, authentication must be UsernamePassword (not Token).

:param auth.Authentication auth: authentication for user to retrieve
:param str name: name of new token
:param str username: username of owner of new token

:return: new token representation
:rtype: Token
:raises NetworkFailure: if there is an error communicating with the server
:raises ApiFailure: if the request cannot be serviced
"""
if username is None:
username = self.authenticated_user(auth).username
data = {"name": name}
response = self._post("/users/{u}/tokens".format(u=username), auth=auth, data=data)
return Token.from_json(self._check_ok(response).json())

def ensure_token(self, auth, name, username=None):
"""
Creates new token if token with specified name for specified user does not exists.
If no user specified uses user authenticated by the given authentication.
Right now, authentication must be UsernamePassword (not Token).
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto, why?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Authentication must be UsernamePassword is server requirement.


:param auth.Authentication auth: authentication for user to retrieve
:param str name: name of new token
:param str username: username of owner of new token

:return: token representation
:rtype: Token
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As things currently stand, shouldn't this be TokenInfo? For instance, this method could return self.create_token(..), which is of type TokenInfo.

I still would suggest scraping the TokenInfo class altogether (see other comment), but want to make sure I'm not missing something.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As far as I understand your auth.Token should not be bound to json parsing.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see what's wrong with giving auth.Token a from_json(..) method.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

:raises NetworkFailure: if there is an error communicating with the server
:raises ApiFailure: if the request cannot be serviced
"""
if username is None:
username = self.authenticated_user(auth).username
tokens = [token for token in self.get_tokens(auth, username) if token.name == name]
if tokens:
return tokens[0]
return self.create_token(auth, name, username)

def create_repo(self, auth, name, description=None, private=False, auto_init=False,
gitignore_templates=None, license_template=None, readme_template=None):
"""
Expand Down
35 changes: 34 additions & 1 deletion tests/interface_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,15 @@ def setUp(self):
"email": "[email protected]",
"avatar_url": "/avatars/1"
}"""
self.token = gogs_client.Token("mytoken")
self.token_json_str = """{
"name": "new token",
"sha1": "mytoken"
}"""
self.username_password = gogs_client.UsernamePassword(
"auth_username", "password")
self.expected_repo = gogs_client.GogsRepo.from_json(json.loads(self.repo_json_str))
self.expected_user = gogs_client.GogsUser.from_json(json.loads(self.user_json_str))
self.token = gogs_client.Token.from_json(json.loads(self.token_json_str))

@responses.activate
def test_create_repo1(self):
Expand Down Expand Up @@ -238,6 +242,31 @@ def test_authenticated_user(self):
user = self.client.authenticated_user(self.token)
self.assert_users_equals(user, self.expected_user)

@responses.activate
def test_ensure_token(self):
uri = self.path("/users/{}/tokens".format(self.username_password.username))
responses.add(responses.GET, uri, body="[]", status=200)
responses.add(responses.POST, uri, body=self.token_json_str, status=200)
responses.add(responses.GET, uri, body="["+self.token_json_str+"]", status=200)
token = self.client.ensure_token(self.username_password, self.token.name, self.username_password.username)
self.assert_tokens_equals(token, self.token)
token = self.client.ensure_token(self.username_password, self.token.name, self.username_password.username)
self.assert_tokens_equals(token, self.token)

@responses.activate
def test_ensure_auth_token(self):
uri = self.path("/user")
responses.add(responses.GET, uri, body=self.user_json_str, status=200)
uri = self.path("/users/{}/tokens".format(self.expected_user.username))
responses.add(responses.GET, uri, body="[]", status=200)
responses.add(responses.POST, uri, body=self.token_json_str, status=200)
tokens = self.client.get_tokens(self.username_password)
self.assertEqual(tokens, [])
tokeninfo = self.client.create_token(self.username_password, self.token.name)
self.assert_tokens_equals(tokeninfo, self.token)
token = self.client.ensure_token(self.username_password, self.token.name)
self.assert_tokens_equals(token, self.token)

# helper methods

@staticmethod
Expand Down Expand Up @@ -277,6 +306,10 @@ def assert_users_equals(self, user, expected):
self.assertEqual(user.email, expected.email)
self.assertEqual(user.avatar_url, expected.avatar_url)

def assert_tokens_equals(self, token, expected):
self.assertEqual(token.name, expected.name)
self.assertEqual(token.token, expected.token)


if __name__ == "__main__":
unittest.main()