From fa68d034e56616221f7a2bc8d59a9e72ac7c9bdb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Barrag=C3=A1n=20Merino?= Date: Wed, 11 Jun 2014 14:18:08 +0200 Subject: [PATCH] Create a connectors module and make a github one --- taiga/base/connectors/__init__.py | 0 taiga/base/connectors/exceptions.py | 27 +++++ taiga/base/connectors/github.py | 160 ++++++++++++++++++++++++++++ 3 files changed, 187 insertions(+) create mode 100644 taiga/base/connectors/__init__.py create mode 100644 taiga/base/connectors/exceptions.py create mode 100644 taiga/base/connectors/github.py diff --git a/taiga/base/connectors/__init__.py b/taiga/base/connectors/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/taiga/base/connectors/exceptions.py b/taiga/base/connectors/exceptions.py new file mode 100644 index 00000000..b7647e67 --- /dev/null +++ b/taiga/base/connectors/exceptions.py @@ -0,0 +1,27 @@ +# Copyright (C) 2014 Andrey Antukh +# Copyright (C) 2014 Jesús Espino +# Copyright (C) 2014 David Barragán +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +from taiga.base.exceptions import BaseException + +from django.utils.translation import ugettext_lazy as _ + +class ConnectorBaseException(BaseException): + status_code = 400 + default_detail = _("Connection error.") + + +class GitHubApiError(ConnectorBaseException): + pass diff --git a/taiga/base/connectors/github.py b/taiga/base/connectors/github.py new file mode 100644 index 00000000..73705dbf --- /dev/null +++ b/taiga/base/connectors/github.py @@ -0,0 +1,160 @@ +# Copyright (C) 2014 Andrey Antukh +# Copyright (C) 2014 Jesús Espino +# Copyright (C) 2014 David Barragán +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . + +import requests +import json + +from collections import namedtuple +from urllib.parse import urljoin + +from django.conf import settings + +from . import exceptions as exc + + +###################################################### +## Data +###################################################### + +CLIENT_ID = getattr(settings, "GITHUB_API_CLIENT_ID", None) +CLIENT_SECRET = getattr(settings, "GITHUB_API_CLIENT_SECRET", None) + +URL = getattr(settings, "GITHUB_URL", "https://github.com/") +API_URL = getattr(settings, "GITHUB_API_URL", "https://api.github.com/") +API_RESOURCES_URLS = { + "login": { + "authorize": "login/oauth/authorize", + "access-token": "login/oauth/access_token" + }, + "user": { + "profile": "user", + "emails": "user/emails" + } +} + + +HEADERS = {"Accept": "application/json",} + +AuthInfo = namedtuple("AuthInfo", ["access_token"]) +User = namedtuple("User", ["id", "username", "full_name", "bio"]) +Email = namedtuple("Email", ["email", "is_primary"]) + + +###################################################### +## utils +###################################################### + +def _build_url(*args, **kwargs) -> str: + """ + Return a valid url. + """ + resource_url = API_RESOURCES_URLS + for key in args: + resource_url = resource_url[key] + + if kwargs: + resource_url = resource_url.format(**kwargs) + + return urljoin(API_URL, resource_url) + + +def _get(url:str, headers:dict) -> dict: + """ + Make a GET call. + """ + response = requests.get(url, headers=headers) + + data = response.json() + if response.status_code != 200: + raise exc.GitHubApiErrorApiError({"status_code": response.status_code, + "error": data.get("error", "")}) + return data + + +def _post(url:str, params:dict, headers:dict) -> dict: + """ + Make a POST call. + """ + response = requests.post(url,params=params, headers=headers) + + data = response.json() + if response.status_code != 200 or "error" in data: + raise exc.GitHubApiErrorApiError({"status_code": response.status_code, + "error": data.get("error", "")}) + return data + + +###################################################### +## Simple calls +###################################################### + +def login(access_code:str, client_id:str=CLIENT_ID, client_secret:str=CLIENT_SECRET, + headers:dict=HEADERS): + """ + Get access_token fron an user authorized code, the client id and the client secret key. + (See https://developer.github.com/v3/oauth/#web-application-flow). + """ + url = urljoin(URL, "login/oauth/access_token") + params={"code": access_code, + "client_id": client_id, + "client_secret": client_secret, + "scope": "user:emails"} + data = _post(url, params=params, headers=headers) + return AuthInfo(access_token=data.get("access_token", None)) + + +def get_user_profile(headers:dict=HEADERS): + """ + Get authenticated user info. + (See https://developer.github.com/v3/users/#get-the-authenticated-user). + """ + url = _build_url("user", "profile") + data = _get(url, headers=headers) + return User(id=data.get("id", None), + username=data.get("login", None), + full_name=(data.get("name", None) or ""), + bio=(data.get("bio", None) or "")) + + +def get_user_emails(headers:dict=HEADERS) -> list: + """ + Get a list with all emails of the authenticated user. + (See https://developer.github.com/v3/users/emails/#list-email-addresses-for-a-user). + """ + url = _build_url("user", "emails") + data = _get(url, headers=headers) + return [Email(email=e.get("email", None), is_primary=e.get("primary", None)) + for e in data] + + +###################################################### +## Convined calls +###################################################### + +def me(access_code:str) -> tuple: + """ + Connect to a github account and get all personal info (profile and the primary email). + """ + auth_info = login(access_code) + + headers = HEADERS.copy() + headers["Authorization"] = "token {}".format(auth_info.access_token) + + user = get_user_profile(headers=headers) + emails = get_user_emails(headers=headers) + + primary_email = next(filter(lambda x: x.is_primary, emails)) + return primary_email.email, user