Browse Source

Merge pull request #210 from CatalystCode/enhancement/social-admin-info

Enhancement/Read admin info from third-party auth
pull/232/head
Hiroki Nakayama 5 years ago
committed by GitHub
parent
commit
bfd4822d63
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 450 additions and 1 deletions
  1. 26
      app/app/settings.py
  2. 70
      app/server/social_auth.py
  3. 52
      app/server/tests/cassettes/TestAzureADTenantSocialAuth.test_fetch_permissions_is_admin.yaml
  4. 52
      app/server/tests/cassettes/TestAzureADTenantSocialAuth.test_fetch_permissions_not_admin.yaml
  5. 76
      app/server/tests/cassettes/TestGithubSocialAuth.test_fetch_permissions_is_admin.yaml
  6. 77
      app/server/tests/cassettes/TestGithubSocialAuth.test_fetch_permissions_not_admin.yaml
  7. 95
      app/server/tests/test_social_auth.py
  8. 3
      requirements.txt

26
app/app/settings.py

@ -22,7 +22,7 @@ from furl import furl
BASE_DIR = path.dirname(path.dirname(path.abspath(__file__)))
env = Env()
env.read_env(BASE_DIR, recurse=False)
env.read_env(path.join(BASE_DIR, '.env'), recurse=False)
# Quick-start development settings - unsuitable for production
@ -131,10 +131,34 @@ AUTHENTICATION_BACKENDS = [
SOCIAL_AUTH_GITHUB_KEY = env('OAUTH_GITHUB_KEY', None)
SOCIAL_AUTH_GITHUB_SECRET = env('OAUTH_GITHUB_SECRET', None)
GITHUB_ADMIN_ORG_NAME = env('GITHUB_ADMIN_ORG_NAME', None)
GITHUB_ADMIN_TEAM_NAME = env('GITHUB_ADMIN_TEAM_NAME', None)
if GITHUB_ADMIN_ORG_NAME and GITHUB_ADMIN_TEAM_NAME:
SOCIAL_AUTH_GITHUB_SCOPE = ['read:org']
SOCIAL_AUTH_AZUREAD_TENANT_OAUTH2_KEY = env('OAUTH_AAD_KEY', None)
SOCIAL_AUTH_AZUREAD_TENANT_OAUTH2_SECRET = env('OAUTH_AAD_SECRET', None)
SOCIAL_AUTH_AZUREAD_TENANT_OAUTH2_TENANT_ID = env('OAUTH_AAD_TENANT', None)
AZUREAD_ADMIN_GROUP_ID = env('AZUREAD_ADMIN_GROUP_ID', None)
if AZUREAD_ADMIN_GROUP_ID:
SOCIAL_AUTH_AZUREAD_TENANT_OAUTH2_RESOURCE = 'https://graph.microsoft.com/'
SOCIAL_AUTH_AZUREAD_TENANT_OAUTH2_SCOPE = ['Directory.Read.All']
SOCIAL_AUTH_PIPELINE = [
'social_core.pipeline.social_auth.social_details',
'social_core.pipeline.social_auth.social_uid',
'social_core.pipeline.social_auth.auth_allowed',
'social_core.pipeline.social_auth.social_user',
'social_core.pipeline.user.get_username',
'social_core.pipeline.user.create_user',
'social_core.pipeline.social_auth.associate_user',
'social_core.pipeline.social_auth.load_extra_data',
'social_core.pipeline.user.user_details',
'server.social_auth.fetch_github_permissions',
'server.social_auth.fetch_azuread_permissions',
]
# Database
# https://docs.djangoproject.com/en/2.0/ref/settings/#databases

70
app/server/social_auth.py

@ -0,0 +1,70 @@
import requests
from django.conf import settings
from social_core.backends.azuread_tenant import AzureADTenantOAuth2
from social_core.backends.github import GithubOAuth2
# noinspection PyUnusedLocal
def fetch_github_permissions(strategy, details, user=None, is_new=False, *args, **kwargs):
org_name = getattr(settings, 'GITHUB_ADMIN_ORG_NAME', '')
team_name = getattr(settings, 'GITHUB_ADMIN_TEAM_NAME', '')
if not user or not isinstance(kwargs['backend'], GithubOAuth2) or not org_name or not team_name:
return
response = requests.post(
url='https://api.github.com/graphql',
headers={
'Authorization': 'Bearer {}'.format(kwargs['response']['access_token']),
},
json={
'query': '''
query($userName: String!, $orgName: String!, $teamName: String!) {
organization(login: $orgName) {
teams(query: $teamName, userLogins: [$userName], first: 1) {
nodes {
name
}
}
}
}
''',
'variables': {
'userName': details['username'],
'orgName': org_name,
'teamName': team_name,
}
}
)
response.raise_for_status()
response = response.json()
is_superuser = {'name': team_name} in response['data']['organization']['teams']['nodes']
if user.is_superuser != is_superuser:
user.is_superuser = is_superuser
user.save()
# noinspection PyUnusedLocal
def fetch_azuread_permissions(strategy, details, user=None, is_new=False, *args, **kwargs):
group_id = getattr(settings, 'AZUREAD_ADMIN_GROUP_ID', '')
if not user or not isinstance(kwargs['backend'], AzureADTenantOAuth2) or not group_id:
return
response = requests.post(
url='https://graph.microsoft.com/v1.0/me/checkMemberGroups',
headers={
'Authorization': 'Bearer {}'.format(kwargs['response']['access_token']),
},
json={
'groupIds': [group_id]
}
)
response.raise_for_status()
response = response.json()
is_superuser = group_id in response['value']
if user.is_superuser != is_superuser:
user.is_superuser = is_superuser
user.save()

52
app/server/tests/cassettes/TestAzureADTenantSocialAuth.test_fetch_permissions_is_admin.yaml

@ -0,0 +1,52 @@
interactions:
- request:
body: '{"groupIds": ["dddddddd-dddd-dddd-dddd-dddddddddddd"]}'
headers:
Accept:
- '*/*'
Accept-Encoding:
- gzip, deflate
Connection:
- keep-alive
Content-Length:
- '54'
Content-Type:
- application/json
User-Agent:
- python-requests/2.21.0
method: POST
uri: https://graph.microsoft.com/v1.0/me/checkMemberGroups
response:
body:
string: '{"@odata.context":"https://graph.microsoft.com/v1.0/$metadata#Collection(Edm.String)","value":["dddddddd-dddd-dddd-dddd-dddddddddddd"]}'
headers:
Cache-Control:
- private
Content-Type:
- application/json;odata.metadata=minimal;odata.streaming=true;IEEE754Compatible=false;charset=utf-8
Date:
- Mon, 20 May 2019 19:34:27 GMT
Duration:
- '89.2611'
Location:
- https://graph.microsoft.com
OData-Version:
- '4.0'
Strict-Transport-Security:
- max-age=31536000
Transfer-Encoding:
- chunked
Vary:
- Accept-Encoding
client-request-id:
- 78c55087-ba07-4e80-8498-8b23b1901356
content-length:
- '135'
request-id:
- 78c55087-ba07-4e80-8498-8b23b1901356
x-ms-ags-diagnostic:
- '{"ServerInfo":{"DataCenter":"East US","Slice":"SliceC","Ring":"2","ScaleUnit":"000","RoleInstance":"AGSFE_IN_51","ADSiteName":"EUS"}}'
status:
code: 200
message: OK
version: 1

52
app/server/tests/cassettes/TestAzureADTenantSocialAuth.test_fetch_permissions_not_admin.yaml

@ -0,0 +1,52 @@
interactions:
- request:
body: '{"groupIds": ["eeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee"]}'
headers:
Accept:
- '*/*'
Accept-Encoding:
- gzip, deflate
Connection:
- keep-alive
Content-Length:
- '54'
Content-Type:
- application/json
User-Agent:
- python-requests/2.21.0
method: POST
uri: https://graph.microsoft.com/v1.0/me/checkMemberGroups
response:
body:
string: '{"@odata.context":"https://graph.microsoft.com/v1.0/$metadata#Collection(Edm.String)","value":[]}'
headers:
Cache-Control:
- private
Content-Type:
- application/json;odata.metadata=minimal;odata.streaming=true;IEEE754Compatible=false;charset=utf-8
Date:
- Mon, 20 May 2019 19:34:52 GMT
Duration:
- '84.3986'
Location:
- https://graph.microsoft.com
OData-Version:
- '4.0'
Strict-Transport-Security:
- max-age=31536000
Transfer-Encoding:
- chunked
Vary:
- Accept-Encoding
client-request-id:
- 69bf4728-ae4e-47ef-afd0-7d2c31129b83
content-length:
- '97'
request-id:
- 69bf4728-ae4e-47ef-afd0-7d2c31129b83
x-ms-ags-diagnostic:
- '{"ServerInfo":{"DataCenter":"East US","Slice":"SliceC","Ring":"2","ScaleUnit":"000","RoleInstance":"AGSFE_IN_5","ADSiteName":"EUS"}}'
status:
code: 200
message: OK
version: 1

76
app/server/tests/cassettes/TestGithubSocialAuth.test_fetch_permissions_is_admin.yaml

@ -0,0 +1,76 @@
interactions:
- request:
body: '{"query": "\n query($userName: String!, $orgName: String!,
$teamName: String!) {\n organization(login: $orgName) {\n teams(query:
$teamName, userLogins: [$userName], first: 1) {\n nodes
{\n name\n }\n }\n }\n }\n ",
"variables": {"userName": "c-w", "orgName": "CatalystCode", "teamName": "doccano-dev"}}'
headers:
Accept:
- '*/*'
Accept-Encoding:
- gzip, deflate
Connection:
- keep-alive
Content-Length:
- '513'
Content-Type:
- application/json
User-Agent:
- python-requests/2.21.0
method: POST
uri: https://api.github.com/graphql
response:
body:
string: '{"data":{"organization":{"teams":{"nodes":[{"name":"doccano-dev"}]}}}}'
headers:
Access-Control-Allow-Origin:
- '*'
Access-Control-Expose-Headers:
- ETag, Link, Location, 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-Media-Type
Cache-Control:
- no-cache
Content-Security-Policy:
- default-src 'none'
Content-Type:
- application/json; charset=utf-8
Date:
- Mon, 20 May 2019 17:38:20 GMT
Referrer-Policy:
- origin-when-cross-origin, strict-origin-when-cross-origin
Server:
- GitHub.com
Status:
- 200 OK
Strict-Transport-Security:
- max-age=31536000; includeSubdomains; preload
Transfer-Encoding:
- chunked
X-Accepted-OAuth-Scopes:
- repo
X-Content-Type-Options:
- nosniff
X-Frame-Options:
- deny
X-GitHub-Media-Type:
- github.v4; format=json
X-GitHub-Request-Id:
- E979:03BD:225D930:49FB694:5CE2E60C
X-OAuth-Scopes:
- read:org
X-RateLimit-Limit:
- '5000'
X-RateLimit-Remaining:
- '4955'
X-RateLimit-Reset:
- '1558377500'
X-XSS-Protection:
- 1; mode=block
content-length:
- '70'
status:
code: 200
message: OK
version: 1

77
app/server/tests/cassettes/TestGithubSocialAuth.test_fetch_permissions_not_admin.yaml

@ -0,0 +1,77 @@
interactions:
- request:
body: '{"query": "\n query($userName: String!, $orgName: String!,
$teamName: String!) {\n organization(login: $orgName) {\n teams(query:
$teamName, userLogins: [$userName], first: 1) {\n nodes
{\n name\n }\n }\n }\n }\n ",
"variables": {"userName": "hirosan", "orgName": "CatalystCode", "teamName":
"doccano-dev"}}'
headers:
Accept:
- '*/*'
Accept-Encoding:
- gzip, deflate
Connection:
- keep-alive
Content-Length:
- '517'
Content-Type:
- application/json
User-Agent:
- python-requests/2.21.0
method: POST
uri: https://api.github.com/graphql
response:
body:
string: '{"data":{"organization":{"teams":{"nodes":[]}}}}'
headers:
Access-Control-Allow-Origin:
- '*'
Access-Control-Expose-Headers:
- ETag, Link, Location, 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-Media-Type
Cache-Control:
- no-cache
Content-Security-Policy:
- default-src 'none'
Content-Type:
- application/json; charset=utf-8
Date:
- Mon, 20 May 2019 17:38:20 GMT
Referrer-Policy:
- origin-when-cross-origin, strict-origin-when-cross-origin
Server:
- GitHub.com
Status:
- 200 OK
Strict-Transport-Security:
- max-age=31536000; includeSubdomains; preload
Transfer-Encoding:
- chunked
X-Accepted-OAuth-Scopes:
- repo
X-Content-Type-Options:
- nosniff
X-Frame-Options:
- deny
X-GitHub-Media-Type:
- github.v4; format=json
X-GitHub-Request-Id:
- E97B:0EFE:220AB47:4963FE2:5CE2E60C
X-OAuth-Scopes:
- read:org
X-RateLimit-Limit:
- '5000'
X-RateLimit-Remaining:
- '4954'
X-RateLimit-Reset:
- '1558377500'
X-XSS-Protection:
- 1; mode=block
content-length:
- '48'
status:
code: 200
message: OK
version: 1

95
app/server/tests/test_social_auth.py

@ -0,0 +1,95 @@
from django.contrib.auth import get_user_model
from django.test import TestCase, override_settings
from social_core.backends.azuread_tenant import AzureADTenantOAuth2
from social_core.backends.github import GithubOAuth2
from vcr_unittest import VCRMixin
from .. import social_auth
User = get_user_model()
class VCRTestCase(VCRMixin, TestCase):
@property
def access_token(self):
raise NotImplementedError()
def _get_vcr(self, **kwargs):
kwargs['decode_compressed_response'] = True
kwargs['record_mode'] = 'none' if self.access_token == 'censored' else 'all'
return super()._get_vcr(**kwargs)
def _get_vcr_kwargs(self, **kwargs):
kwargs['filter_headers'] = ['Authorization']
return super()._get_vcr_kwargs(**kwargs)
@override_settings(GITHUB_ADMIN_ORG_NAME='CatalystCode')
@override_settings(GITHUB_ADMIN_TEAM_NAME='doccano-dev')
class TestGithubSocialAuth(VCRTestCase):
strategy = None
backend = GithubOAuth2(strategy=strategy)
access_token = 'censored'
def test_fetch_permissions_is_admin(self):
user = User()
social_auth.fetch_github_permissions(
strategy=self.strategy,
details={'username': 'c-w'},
user=user,
backend=self.backend,
response={'access_token': self.access_token},
)
self.assertTrue(user.is_superuser)
def test_fetch_permissions_not_admin(self):
user = User()
social_auth.fetch_github_permissions(
strategy=self.strategy,
details={'username': 'hirosan'},
user=user,
backend=self.backend,
response={'access_token': self.access_token},
)
self.assertFalse(user.is_superuser)
@override_settings(SOCIAL_AUTH_AZUREAD_TENANT_OAUTH2_KEY='aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa')
@override_settings(SOCIAL_AUTH_AZUREAD_TENANT_OAUTH2_SECRET='bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb=')
@override_settings(SOCIAL_AUTH_AZUREAD_TENANT_OAUTH2_TENANT='cccccccc-cccc-cccc-cccc-cccccccccccc')
class TestAzureADTenantSocialAuth(VCRTestCase):
strategy = None
backend = AzureADTenantOAuth2(strategy=strategy)
access_token = 'censored'
@override_settings(AZUREAD_ADMIN_GROUP_ID='dddddddd-dddd-dddd-dddd-dddddddddddd')
def test_fetch_permissions_is_admin(self):
user = User()
social_auth.fetch_azuread_permissions(
strategy=self.strategy,
details={},
user=user,
backend=self.backend,
response={'access_token': self.access_token},
)
self.assertTrue(user.is_superuser)
@override_settings(AZUREAD_ADMIN_GROUP_ID='eeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee')
def test_fetch_permissions_not_admin(self):
user = User()
social_auth.fetch_azuread_permissions(
strategy=self.strategy,
details={},
user=user,
backend=self.backend,
response={'access_token': self.access_token},
)
self.assertFalse(user.is_superuser)

3
requirements.txt

@ -22,6 +22,7 @@ model-mommy==1.6.0
psycopg2-binary==2.7.7
python-dateutil==2.7.3
pytz==2018.4
requests==2.21.0
six==1.11.0
seqeval==0.0.6
social-auth-app-django==3.1.0
@ -29,4 +30,6 @@ social-auth-core[azuread]==3.0.0
text-unidecode==1.2
tornado==5.0.2
unittest-xml-reporting==2.5.1
vcrpy==2.0.1
vcrpy-unittest==0.1.7
whitenoise[brotli]==4.1.2
Loading…
Cancel
Save