From 3626fa4b98418123077040bd2ccd0671f877b96d Mon Sep 17 00:00:00 2001 From: Jens Langhammer Date: Tue, 12 Mar 2019 17:18:08 +0100 Subject: [PATCH] add sentry client --- .../allauth}/.gitlab-ci.yml | 0 .../allauth}/allauth_passbook/__init__.py | 0 .../allauth}/allauth_passbook/provider.py | 0 .../allauth}/allauth_passbook/urls.py | 1 + .../allauth}/allauth_passbook/views.py | 2 +- .../allauth}/requirements.txt | 0 {allauth => client-packages/allauth}/setup.py | 0 .../sentry-auth-passbook/.gitignore | 5 + .../sentry-auth-passbook/.travis.yml | 32 +++ client-packages/sentry-auth-passbook/LICENSE | 201 ++++++++++++++++++ .../sentry-auth-passbook/MANIFEST.in | 3 + client-packages/sentry-auth-passbook/Makefile | 26 +++ .../sentry-auth-passbook/README.rst | 55 +++++ .../sentry-auth-passbook/conftest.py | 14 ++ .../sentry_auth_passbook/__init__.py | 7 + .../sentry_auth_passbook/client.py | 45 ++++ .../sentry_auth_passbook/constants.py | 14 ++ .../sentry_auth_passbook/provider.py | 62 ++++++ .../sentry_auth_passbook/views.py | 75 +++++++ .../sentry-auth-passbook/setup.cfg | 12 ++ client-packages/sentry-auth-passbook/setup.py | 45 ++++ .../sentry-auth-passbook/tests/test_plugin.py | 0 .../tests/test_provider.py | 6 + .../sentry-auth-passbook/tests/test_views.py | 17 ++ requirements-dev.txt | 2 +- 25 files changed, 622 insertions(+), 2 deletions(-) rename {allauth => client-packages/allauth}/.gitlab-ci.yml (100%) rename {allauth => client-packages/allauth}/allauth_passbook/__init__.py (100%) rename {allauth => client-packages/allauth}/allauth_passbook/provider.py (100%) rename {allauth => client-packages/allauth}/allauth_passbook/urls.py (99%) rename {allauth => client-packages/allauth}/allauth_passbook/views.py (100%) rename {allauth => client-packages/allauth}/requirements.txt (100%) rename {allauth => client-packages/allauth}/setup.py (100%) create mode 100644 client-packages/sentry-auth-passbook/.gitignore create mode 100644 client-packages/sentry-auth-passbook/.travis.yml create mode 100644 client-packages/sentry-auth-passbook/LICENSE create mode 100644 client-packages/sentry-auth-passbook/MANIFEST.in create mode 100644 client-packages/sentry-auth-passbook/Makefile create mode 100644 client-packages/sentry-auth-passbook/README.rst create mode 100644 client-packages/sentry-auth-passbook/conftest.py create mode 100644 client-packages/sentry-auth-passbook/sentry_auth_passbook/__init__.py create mode 100644 client-packages/sentry-auth-passbook/sentry_auth_passbook/client.py create mode 100644 client-packages/sentry-auth-passbook/sentry_auth_passbook/constants.py create mode 100644 client-packages/sentry-auth-passbook/sentry_auth_passbook/provider.py create mode 100644 client-packages/sentry-auth-passbook/sentry_auth_passbook/views.py create mode 100644 client-packages/sentry-auth-passbook/setup.cfg create mode 100644 client-packages/sentry-auth-passbook/setup.py create mode 100644 client-packages/sentry-auth-passbook/tests/test_plugin.py create mode 100644 client-packages/sentry-auth-passbook/tests/test_provider.py create mode 100644 client-packages/sentry-auth-passbook/tests/test_views.py diff --git a/allauth/.gitlab-ci.yml b/client-packages/allauth/.gitlab-ci.yml similarity index 100% rename from allauth/.gitlab-ci.yml rename to client-packages/allauth/.gitlab-ci.yml diff --git a/allauth/allauth_passbook/__init__.py b/client-packages/allauth/allauth_passbook/__init__.py similarity index 100% rename from allauth/allauth_passbook/__init__.py rename to client-packages/allauth/allauth_passbook/__init__.py diff --git a/allauth/allauth_passbook/provider.py b/client-packages/allauth/allauth_passbook/provider.py similarity index 100% rename from allauth/allauth_passbook/provider.py rename to client-packages/allauth/allauth_passbook/provider.py diff --git a/allauth/allauth_passbook/urls.py b/client-packages/allauth/allauth_passbook/urls.py similarity index 99% rename from allauth/allauth_passbook/urls.py rename to client-packages/allauth/allauth_passbook/urls.py index 0a5bcc1d4..29e09509b 100644 --- a/allauth/allauth_passbook/urls.py +++ b/client-packages/allauth/allauth_passbook/urls.py @@ -1,5 +1,6 @@ """passbook provider""" from allauth.socialaccount.providers.oauth2.urls import default_urlpatterns + from allauth_passbook.provider import PassbookProvider urlpatterns = default_urlpatterns(PassbookProvider) diff --git a/allauth/allauth_passbook/views.py b/client-packages/allauth/allauth_passbook/views.py similarity index 100% rename from allauth/allauth_passbook/views.py rename to client-packages/allauth/allauth_passbook/views.py index 6c939d552..c79208fe2 100644 --- a/allauth/allauth_passbook/views.py +++ b/client-packages/allauth/allauth_passbook/views.py @@ -1,10 +1,10 @@ """passbook adapter""" import requests - from allauth.socialaccount import app_settings from allauth.socialaccount.providers.oauth2.views import (OAuth2Adapter, OAuth2CallbackView, OAuth2LoginView) + from allauth_passbook.provider import PassbookProvider diff --git a/allauth/requirements.txt b/client-packages/allauth/requirements.txt similarity index 100% rename from allauth/requirements.txt rename to client-packages/allauth/requirements.txt diff --git a/allauth/setup.py b/client-packages/allauth/setup.py similarity index 100% rename from allauth/setup.py rename to client-packages/allauth/setup.py diff --git a/client-packages/sentry-auth-passbook/.gitignore b/client-packages/sentry-auth-passbook/.gitignore new file mode 100644 index 000000000..40985afd7 --- /dev/null +++ b/client-packages/sentry-auth-passbook/.gitignore @@ -0,0 +1,5 @@ +*.pyc +*.egg-info/ +*.eggs +/dist +/build diff --git a/client-packages/sentry-auth-passbook/.travis.yml b/client-packages/sentry-auth-passbook/.travis.yml new file mode 100644 index 000000000..a19bbdb85 --- /dev/null +++ b/client-packages/sentry-auth-passbook/.travis.yml @@ -0,0 +1,32 @@ +sudo: false +language: python +services: + - memcached + - postgresql + - redis-server +python: + - '2.7' +cache: + directories: + - node_modules + - "$HOME/.cache/pip" +deploy: + provider: pypi + user: getsentry + password: + secure: kVmxKHkBWRLYyZme05p+WZSJmb8GjHV9uyuaSCVMRlqWCW+GXRB7P1xXR2jb9URTlNdcs56Ab/UrwzCbMFGC8LmwCeFVgIR/ltytVZG2FgXZPWaeA4dH25qK2oGWgzJ/xeiMpmuJqN9hRl25MX6jG7FZKvrrOkG7+8tpPd1yO+uYWZQbnebZMjcPBqEpn7CC0hR39GSoyVAbydpMe5hwENGQM26CepcicdrelfawItoUrXrkJzBHkIQQTO/xRSbCtRJOtzI5lwtv3GP0hcbOy5tI5dhG/93pLwZRc5+dZaCaP7oaVeOcBjN0zfINRQobt8d6h2Qgvd/YyFkGi0/xKn1zMmKIVLOG6VsYwEAUq8wNOsP4A/jdm4Y0J/1oEZStCkpaGpx85TYi4kq1hWQdyqaVJSPhh4Tk4roIaS2zOYQl+nIpbHqmJ4FJrg1il+TCdjBXobATQ1mKRBUrjD+RDzH/r4ogbd8+UwvvvevpqS2K+/wgT6UD0MzDInv9S29CUQvuFhPoqyJb5XRddHMRE9EEK/2Z8tFN91sDATnqfXHgwnvu00q/nKP5JnijBPzGmx7ydgUViIukklDrlPvo9BbRJz0Vr2vbAvMTrLMLCXqi5CwTm+v+iaOf/YaCziaG2vx0eVASYjpOLCedSgRZBubPM8z4E/HMXhChN7sVDWk= + on: + tags: true + distributions: sdist bdist_wheel +env: + global: + - PIP_DOWNLOAD_CACHE=".pip_download_cache" +before_install: + - pip install codecov +install: + - make develop +script: + - PYFLAKES_NODOCTEST=1 flake8 + - coverage run --source=. -m py.test tests +after_success: + - codecov diff --git a/client-packages/sentry-auth-passbook/LICENSE b/client-packages/sentry-auth-passbook/LICENSE new file mode 100644 index 000000000..e53ef543f --- /dev/null +++ b/client-packages/sentry-auth-passbook/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + +Copyright 2016 Functional Software, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/client-packages/sentry-auth-passbook/MANIFEST.in b/client-packages/sentry-auth-passbook/MANIFEST.in new file mode 100644 index 000000000..83db68ec8 --- /dev/null +++ b/client-packages/sentry-auth-passbook/MANIFEST.in @@ -0,0 +1,3 @@ +include setup.py package.json webpack.config.js README.rst MANIFEST.in LICENSE AUTHORS +recursive-include sentry_auth_supervisr/templates * +global-exclude *~ diff --git a/client-packages/sentry-auth-passbook/Makefile b/client-packages/sentry-auth-passbook/Makefile new file mode 100644 index 000000000..bf3a1729f --- /dev/null +++ b/client-packages/sentry-auth-passbook/Makefile @@ -0,0 +1,26 @@ +.PHONY: clean develop install-tests lint publish test + +develop: + pip install "pip>=7" + pip install -e . + make install-tests + +install-tests: + pip install .[tests] + +lint: + @echo "--> Linting python" + flake8 + @echo "" + +test: + @echo "--> Running Python tests" + py.test tests || exit 1 + @echo "" + +publish: + python setup.py sdist bdist_wheel upload + +clean: + rm -rf *.egg-info src/*.egg-info + rm -rf dist build diff --git a/client-packages/sentry-auth-passbook/README.rst b/client-packages/sentry-auth-passbook/README.rst new file mode 100644 index 000000000..64d8f7475 --- /dev/null +++ b/client-packages/sentry-auth-passbook/README.rst @@ -0,0 +1,55 @@ +GitHub Auth for Sentry +====================== + +An SSO provider for Sentry which enables GitHub organization-restricted authentication. + +Install +------- + +:: + + $ pip install https://github.com/getsentry/sentry-auth-github/archive/master.zip + +Setup +----- + +Create a new application under your organization in GitHub. Enter the **Authorization +callback URL** as the prefix to your Sentry installation: + +:: + + https://example.sentry.com + + +Once done, grab your API keys and drop them in your ``sentry.conf.py``: + +.. code-block:: python + + GITHUB_APP_ID = "" + + GITHUB_API_SECRET = "" + + +Verified email addresses can optionally be required: + +.. code-block:: python + + GITHUB_REQUIRE_VERIFIED_EMAIL = True + + +Optionally you may also specify the domain (for GHE users): + +.. code-block:: python + + GITHUB_BASE_DOMAIN = "git.example.com" + + GITHUB_API_DOMAIN = "api.git.example.com" + + +If Subdomain isolation is disabled in GHE: + +.. code-block:: python + + GITHUB_BASE_DOMAIN = "git.example.com" + + GITHUB_API_DOMAIN = "git.example.com/api/v3" diff --git a/client-packages/sentry-auth-passbook/conftest.py b/client-packages/sentry-auth-passbook/conftest.py new file mode 100644 index 000000000..0018dfefc --- /dev/null +++ b/client-packages/sentry-auth-passbook/conftest.py @@ -0,0 +1,14 @@ +from __future__ import absolute_import + +# Run tests against sqlite for simplicity +import os +import os.path +import sys + +sys.path.insert(0, os.path.join(os.path.dirname(__file__))) + +os.environ.setdefault('DB', 'sqlite') + +pytest_plugins = [ + 'sentry.utils.pytest' +] diff --git a/client-packages/sentry-auth-passbook/sentry_auth_passbook/__init__.py b/client-packages/sentry-auth-passbook/sentry_auth_passbook/__init__.py new file mode 100644 index 000000000..5a02d365d --- /dev/null +++ b/client-packages/sentry-auth-passbook/sentry_auth_passbook/__init__.py @@ -0,0 +1,7 @@ +from __future__ import absolute_import + +from sentry.auth import register + +from .provider import PassbookOAuth2Provider + +register('passbook', PassbookOAuth2Provider) diff --git a/client-packages/sentry-auth-passbook/sentry_auth_passbook/client.py b/client-packages/sentry-auth-passbook/sentry_auth_passbook/client.py new file mode 100644 index 000000000..069b87ed6 --- /dev/null +++ b/client-packages/sentry-auth-passbook/sentry_auth_passbook/client.py @@ -0,0 +1,45 @@ +from __future__ import absolute_import, print_function + +from requests.exceptions import RequestException + +from sentry import http +from sentry.utils import json + +from .constants import BASE_DOMAIN + + +class SupervisrApiError(Exception): + def __init__(self, message='', status=0): + super(SupervisrApiError, self).__init__(message) + self.status = status + + +class SupervisrClient(object): + def __init__(self, client_id, client_secret): + self.client_id = client_id + self.client_secret = client_secret + self.http = http.build_session() + + def _request(self, path, access_token): + params = { + 'client_id': self.client_id, + 'client_secret': self.client_secret, + } + + headers = { + 'Authorization': 'Bearer {0}'.format(access_token), + } + + try: + req = self.http.get('https://{0}/{1}'.format(BASE_DOMAIN, path.lstrip('/')), + params=params, + headers=headers, + ) + except RequestException as e: + raise SupervisrApiError(unicode(e), status=getattr(e, 'status_code', 0)) + if req.status_code < 200 or req.status_code >= 300: + raise SupervisrApiError(req.content, status=req.status_code) + return json.loads(req.content) + + def get_user(self, access_token): + return self._request('/api/core/v1/accounts/me/?format=openid', access_token) diff --git a/client-packages/sentry-auth-passbook/sentry_auth_passbook/constants.py b/client-packages/sentry-auth-passbook/sentry_auth_passbook/constants.py new file mode 100644 index 000000000..14e43270c --- /dev/null +++ b/client-packages/sentry-auth-passbook/sentry_auth_passbook/constants.py @@ -0,0 +1,14 @@ +from __future__ import absolute_import, print_function + +from django.conf import settings + +CLIENT_ID = getattr(settings, 'PASSBOOK_APP_ID', None) + +CLIENT_SECRET = getattr(settings, 'PASSBOOK_API_SECRET', None) + +SCOPE = 'openid:userinfo' + +BASE_DOMAIN = getattr(settings, 'PASSBOOK_BASE_DOMAIN', 'id.beryju.org') + +ACCESS_TOKEN_URL = 'https://{0}/application/oauth/token/'.format(BASE_DOMAIN) +AUTHORIZE_URL = 'https://{0}/application/oauth/authorize/'.format(BASE_DOMAIN) diff --git a/client-packages/sentry-auth-passbook/sentry_auth_passbook/provider.py b/client-packages/sentry-auth-passbook/sentry_auth_passbook/provider.py new file mode 100644 index 000000000..f6eaf6fb7 --- /dev/null +++ b/client-packages/sentry-auth-passbook/sentry_auth_passbook/provider.py @@ -0,0 +1,62 @@ +from __future__ import absolute_import, print_function + +from sentry.auth.exceptions import IdentityNotValid +from sentry.auth.providers.oauth2 import (OAuth2Callback, OAuth2Login, + OAuth2Provider) + +from .client import PassbookApiError, PassbookClient +from .constants import (ACCESS_TOKEN_URL, AUTHORIZE_URL, CLIENT_ID, + CLIENT_SECRET, SCOPE) +from .views import FetchUser, PassbookConfigureView + + +class PassbookOAuth2Provider(OAuth2Provider): + access_token_url = ACCESS_TOKEN_URL + authorize_url = AUTHORIZE_URL + name = 'Passbook' + client_id = CLIENT_ID + client_secret = CLIENT_SECRET + + def __init__(self, **config): + super(PassbookOAuth2Provider, self).__init__(**config) + + def get_configure_view(self): + return PassbookConfigureView.as_view() + + def get_auth_pipeline(self): + return [ + OAuth2Login( + authorize_url=self.authorize_url, + client_id=self.client_id, + scope=SCOPE, + ), + OAuth2Callback( + access_token_url=self.access_token_url, + client_id=self.client_id, + client_secret=self.client_secret, + ), + FetchUser( + client_id=self.client_id, + client_secret=self.client_secret, + ), + ] + + def get_refresh_token_url(self): + return ACCESS_TOKEN_URL + + def build_identity(self, state): + data = state['data'] + user_data = state['user'] + return { + 'id': user_data['email'], + 'email': user_data['email'], + 'name': user_data['name'], + 'data': self.get_oauth_data(data), + } + + def build_config(self, state): + return {} + + def refresh_identity(self, auth_identity): + client = PassbookClient(self.client_id, self.client_secret) + access_token = auth_identity.data['access_token'] diff --git a/client-packages/sentry-auth-passbook/sentry_auth_passbook/views.py b/client-packages/sentry-auth-passbook/sentry_auth_passbook/views.py new file mode 100644 index 000000000..fca5e7eff --- /dev/null +++ b/client-packages/sentry-auth-passbook/sentry_auth_passbook/views.py @@ -0,0 +1,75 @@ +from __future__ import absolute_import, print_function + +from django import forms + +from sentry.auth.view import AuthView, ConfigureView +from sentry.models import AuthIdentity + +from .client import PassbookClient + + +def _get_name_from_email(email): + """ + Given an email return a capitalized name. Ex. john.smith@example.com would return John Smith. + """ + name = email.rsplit('@', 1)[0] + name = ' '.join([n_part.capitalize() for n_part in name.split('.')]) + return name + + +class FetchUser(AuthView): + def __init__(self, client_id, client_secret, *args, **kwargs): + self.client = PassbookClient(client_id, client_secret) + super(FetchUser, self).__init__(*args, **kwargs) + + def handle(self, request, helper): + access_token = helper.fetch_state('data')['access_token'] + + user = self.client.get_user(access_token) + + # A user hasn't set their name in their Passbook profile so it isn't + # populated in the response + if not user.get('name'): + user['name'] = _get_name_from_email(user['email']) + + helper.bind_state('user', user) + + return helper.next_step() + + +class ConfirmEmailForm(forms.Form): + email = forms.EmailField(label='Email') + + +class ConfirmEmail(AuthView): + def handle(self, request, helper): + user = helper.fetch_state('user') + + # TODO(dcramer): this isnt ideal, but our current flow doesnt really + # support this behavior; + try: + auth_identity = AuthIdentity.objects.select_related('user').get( + auth_provider=helper.auth_provider, + ident=user['id'], + ) + except AuthIdentity.DoesNotExist: + pass + else: + user['email'] = auth_identity.user.email + + if user.get('email'): + return helper.next_step() + + form = ConfirmEmailForm(request.POST or None) + if form.is_valid(): + user['email'] = form.cleaned_data['email'] + helper.bind_state('user', user) + return helper.next_step() + + return self.respond('sentry_auth_passbook/enter-email.html', { + 'form': form, + }) + +class PassbookConfigureView(ConfigureView): + def dispatch(self, request, organization, auth_provider): + return self.render('sentry_auth_passbook/configure.html') diff --git a/client-packages/sentry-auth-passbook/setup.cfg b/client-packages/sentry-auth-passbook/setup.cfg new file mode 100644 index 000000000..1ac2247e0 --- /dev/null +++ b/client-packages/sentry-auth-passbook/setup.cfg @@ -0,0 +1,12 @@ +[wheel] +universal = 1 + +[pytest] +python_files = test*.py +addopts = --tb=native -p no:doctest +norecursedirs = bin dist docs htmlcov script hooks node_modules .* {args} + +[flake8] +ignore = F999,E501,E128,E124,E402,W503,E731,C901 +max-line-length = 100 +exclude = .tox,.git,*/migrations/*,node_modules/*,docs/* diff --git a/client-packages/sentry-auth-passbook/setup.py b/client-packages/sentry-auth-passbook/setup.py new file mode 100644 index 000000000..1fd481de8 --- /dev/null +++ b/client-packages/sentry-auth-passbook/setup.py @@ -0,0 +1,45 @@ +#!/usr/bin/env python +""" +sentry-auth-passbook +================== + +:copyright: (c) 2016 Functional Software, Inc +""" +from setuptools import find_packages, setup + +install_requires = [ + 'sentry>=7.0.0', +] + +tests_require = [ + 'mock', + 'flake8>=2.0,<2.1', +] + +setup( + name='sentry-auth-passbook', + version='1.0.0', + author='BeryJu.org', + author_email='support@beryju.org', + url='https://passbook.beryju.org', + description='passbook authentication provider for Sentry', + long_description=__doc__, + license='MIT', + packages=find_packages(exclude=['tests']), + zip_safe=False, + install_requires=install_requires, + tests_require=tests_require, + extras_require={'tests': tests_require}, + include_package_data=True, + entry_points={ + 'sentry.apps': [ + 'auth_passbook = sentry_auth_passbook', + ], + }, + classifiers=[ + 'Intended Audience :: Developers', + 'Intended Audience :: System Administrators', + 'Operating System :: OS Independent', + 'Topic :: Software Development' + ], +) diff --git a/client-packages/sentry-auth-passbook/tests/test_plugin.py b/client-packages/sentry-auth-passbook/tests/test_plugin.py new file mode 100644 index 000000000..e69de29bb diff --git a/client-packages/sentry-auth-passbook/tests/test_provider.py b/client-packages/sentry-auth-passbook/tests/test_provider.py new file mode 100644 index 000000000..a2368450e --- /dev/null +++ b/client-packages/sentry-auth-passbook/tests/test_provider.py @@ -0,0 +1,6 @@ +from sentry.testutils import TestCase + + +class GitHubOAuth2ProviderTest(TestCase): + def test_simple(self): + pass diff --git a/client-packages/sentry-auth-passbook/tests/test_views.py b/client-packages/sentry-auth-passbook/tests/test_views.py new file mode 100644 index 000000000..e837f2d5e --- /dev/null +++ b/client-packages/sentry-auth-passbook/tests/test_views.py @@ -0,0 +1,17 @@ +from __future__ import absolute_import, print_function + +import pytest +from sentry_auth_sentry.views import _get_name_from_email + +expected_data = [ + ('john.smith@example.com', 'John Smith'), + ('john@example.com', 'John'), + ('XYZ-234=3523@example.com', 'Xyz-234=3523'), + ('XYZ.1111@example.com', 'Xyz 1111'), + ('JOHN@example.com', 'John'), +] + + +@pytest.mark.parametrize("email,expected_name", expected_data) +def test_get_name_from_email(email, expected_name): + assert _get_name_from_email(email) == expected_name diff --git a/requirements-dev.txt b/requirements-dev.txt index cfb9c1604..9153c1bf5 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,5 +1,5 @@ -r requirements.txt --r allauth/requirements.txt +-r client-packages/allauth/requirements.txt coverage isort astroid==2.0.4