girder-oauth 5.0.20.dev2__tar.gz → 5.0.20.dev6__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {girder_oauth-5.0.20.dev2 → girder_oauth-5.0.20.dev6}/PKG-INFO +1 -1
- {girder_oauth-5.0.20.dev2 → girder_oauth-5.0.20.dev6}/girder_oauth/providers/cilogon.py +12 -11
- {girder_oauth-5.0.20.dev2 → girder_oauth-5.0.20.dev6}/girder_oauth/providers/microsoft.py +2 -1
- {girder_oauth-5.0.20.dev2 → girder_oauth-5.0.20.dev6}/girder_oauth.egg-info/PKG-INFO +1 -1
- girder_oauth-5.0.20.dev6/girder_oauth.egg-info/scm_version.json +8 -0
- {girder_oauth-5.0.20.dev2 → girder_oauth-5.0.20.dev6}/plugin_tests/oauth_test.py +251 -16
- girder_oauth-5.0.20.dev2/girder_oauth.egg-info/scm_version.json +0 -8
- {girder_oauth-5.0.20.dev2 → girder_oauth-5.0.20.dev6}/MANIFEST.in +0 -0
- {girder_oauth-5.0.20.dev2 → girder_oauth-5.0.20.dev6}/girder_oauth/__init__.py +0 -0
- {girder_oauth-5.0.20.dev2 → girder_oauth-5.0.20.dev6}/girder_oauth/providers/__init__.py +0 -0
- {girder_oauth-5.0.20.dev2 → girder_oauth-5.0.20.dev6}/girder_oauth/providers/base.py +0 -0
- {girder_oauth-5.0.20.dev2 → girder_oauth-5.0.20.dev6}/girder_oauth/providers/bitbucket.py +0 -0
- {girder_oauth-5.0.20.dev2 → girder_oauth-5.0.20.dev6}/girder_oauth/providers/box.py +0 -0
- {girder_oauth-5.0.20.dev2 → girder_oauth-5.0.20.dev6}/girder_oauth/providers/github.py +0 -0
- {girder_oauth-5.0.20.dev2 → girder_oauth-5.0.20.dev6}/girder_oauth/providers/globus.py +0 -0
- {girder_oauth-5.0.20.dev2 → girder_oauth-5.0.20.dev6}/girder_oauth/providers/google.py +0 -0
- {girder_oauth-5.0.20.dev2 → girder_oauth-5.0.20.dev6}/girder_oauth/providers/linkedin.py +0 -0
- {girder_oauth-5.0.20.dev2 → girder_oauth-5.0.20.dev6}/girder_oauth/rest.py +0 -0
- {girder_oauth-5.0.20.dev2 → girder_oauth-5.0.20.dev6}/girder_oauth/settings.py +0 -0
- {girder_oauth-5.0.20.dev2 → girder_oauth-5.0.20.dev6}/girder_oauth/web_client/dist/girder-plugin-oauth.umd.cjs +0 -0
- {girder_oauth-5.0.20.dev2 → girder_oauth-5.0.20.dev6}/girder_oauth/web_client/dist/style.css +0 -0
- {girder_oauth-5.0.20.dev2 → girder_oauth-5.0.20.dev6}/girder_oauth.egg-info/SOURCES.txt +0 -0
- {girder_oauth-5.0.20.dev2 → girder_oauth-5.0.20.dev6}/girder_oauth.egg-info/dependency_links.txt +0 -0
- {girder_oauth-5.0.20.dev2 → girder_oauth-5.0.20.dev6}/girder_oauth.egg-info/entry_points.txt +0 -0
- {girder_oauth-5.0.20.dev2 → girder_oauth-5.0.20.dev6}/girder_oauth.egg-info/not-zip-safe +0 -0
- {girder_oauth-5.0.20.dev2 → girder_oauth-5.0.20.dev6}/girder_oauth.egg-info/requires.txt +0 -0
- {girder_oauth-5.0.20.dev2 → girder_oauth-5.0.20.dev6}/girder_oauth.egg-info/scm_file_list.json +0 -0
- {girder_oauth-5.0.20.dev2 → girder_oauth-5.0.20.dev6}/girder_oauth.egg-info/top_level.txt +0 -0
- {girder_oauth-5.0.20.dev2 → girder_oauth-5.0.20.dev6}/plugin.cmake +0 -0
- {girder_oauth-5.0.20.dev2 → girder_oauth-5.0.20.dev6}/plugin_tests/__init__.py +0 -0
- {girder_oauth-5.0.20.dev2 → girder_oauth-5.0.20.dev6}/pyproject.toml +0 -0
- {girder_oauth-5.0.20.dev2 → girder_oauth-5.0.20.dev6}/setup.cfg +0 -0
- {girder_oauth-5.0.20.dev2 → girder_oauth-5.0.20.dev6}/setup.py +0 -0
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import urllib.parse
|
|
1
2
|
import warnings
|
|
2
3
|
|
|
3
4
|
import requests
|
|
@@ -29,15 +30,14 @@ class CILogon(ProviderBase):
|
|
|
29
30
|
|
|
30
31
|
redirectUri = '/'.join((getApiUrl(), 'oauth', 'cilogon', 'callback'))
|
|
31
32
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
return url
|
|
33
|
+
query = urllib.parse.urlencode({
|
|
34
|
+
'client_id': clientId,
|
|
35
|
+
'response_type': 'code',
|
|
36
|
+
'scope': ' '.join(cls._AUTH_SCOPES),
|
|
37
|
+
'redirect_uri': redirectUri,
|
|
38
|
+
'state': state,
|
|
39
|
+
})
|
|
40
|
+
return f'{cls._AUTHORITY}/authorize?{query}'
|
|
41
41
|
|
|
42
42
|
def getToken(self, code):
|
|
43
43
|
clientId = self.getClientIdSetting()
|
|
@@ -60,8 +60,9 @@ class CILogon(ProviderBase):
|
|
|
60
60
|
response = requests.post(token_url, data=data)
|
|
61
61
|
|
|
62
62
|
if response.status_code != 200:
|
|
63
|
-
raise
|
|
64
|
-
|
|
63
|
+
raise RestException('Error acquiring token: %s' %
|
|
64
|
+
response.json().get('error_description', 'Unknown error'),
|
|
65
|
+
code=502)
|
|
65
66
|
|
|
66
67
|
return response.json()
|
|
67
68
|
|
|
@@ -60,7 +60,8 @@ class Microsoft(ProviderBase):
|
|
|
60
60
|
)
|
|
61
61
|
if 'access_token' not in result:
|
|
62
62
|
strings = result['error'], result['error_description']
|
|
63
|
-
raise
|
|
63
|
+
raise RestException(
|
|
64
|
+
'Error "%s" acquiring access token for client: %s' % strings, code=502)
|
|
64
65
|
return result
|
|
65
66
|
|
|
66
67
|
def getUser(self, token):
|
|
@@ -1311,9 +1311,165 @@ class OauthTest(base.TestCase):
|
|
|
1311
1311
|
):
|
|
1312
1312
|
self._testOauth(providerInfo)
|
|
1313
1313
|
|
|
1314
|
-
def
|
|
1315
|
-
|
|
1316
|
-
|
|
1314
|
+
def testCilogonOauth(self): # noqa
|
|
1315
|
+
providerInfo = {
|
|
1316
|
+
'id': 'cilogon',
|
|
1317
|
+
'name': 'CILogon',
|
|
1318
|
+
'client_id': {
|
|
1319
|
+
'key': PluginSettings.CILOGON_CLIENT_ID,
|
|
1320
|
+
'value': 'cilogon_test_client_id'
|
|
1321
|
+
},
|
|
1322
|
+
'client_secret': {
|
|
1323
|
+
'key': PluginSettings.CILOGON_CLIENT_SECRET,
|
|
1324
|
+
'value': 'cilogon_test_client_secret'
|
|
1325
|
+
},
|
|
1326
|
+
'allowed_callback_re':
|
|
1327
|
+
r'^http://127\.0\.0\.1(?::\d+)?/api/v1/oauth/cilogon/callback$',
|
|
1328
|
+
'url_re': r'^https://cilogon\.org/authorize',
|
|
1329
|
+
'accounts': {
|
|
1330
|
+
'existing': {
|
|
1331
|
+
'auth_code': 'cilogon_existing_auth_code',
|
|
1332
|
+
'access_token': 'cilogon_existing_test_token',
|
|
1333
|
+
'user': {
|
|
1334
|
+
'login': self.adminUser['login'],
|
|
1335
|
+
'email': self.adminUser['email'],
|
|
1336
|
+
'firstName': self.adminUser['firstName'],
|
|
1337
|
+
'lastName': self.adminUser['lastName'],
|
|
1338
|
+
'oauth': {
|
|
1339
|
+
'provider': 'cilogon',
|
|
1340
|
+
'id': '2399'
|
|
1341
|
+
}
|
|
1342
|
+
}
|
|
1343
|
+
},
|
|
1344
|
+
'new': {
|
|
1345
|
+
'auth_code': 'cilogon_new_auth_code',
|
|
1346
|
+
'access_token': 'cilogon_new_test_token',
|
|
1347
|
+
'user': {
|
|
1348
|
+
# this login is not provided by CILogon, but will be
|
|
1349
|
+
# created internally by _deriveLogin
|
|
1350
|
+
'login': 'apollo',
|
|
1351
|
+
'email': 'apollo@creed.chi.il.us',
|
|
1352
|
+
'firstName': 'Apollo',
|
|
1353
|
+
'lastName': 'Creed',
|
|
1354
|
+
'oauth': {
|
|
1355
|
+
'provider': 'cilogon',
|
|
1356
|
+
'id': 'the1best'
|
|
1357
|
+
}
|
|
1358
|
+
}
|
|
1359
|
+
}
|
|
1360
|
+
}
|
|
1361
|
+
}
|
|
1362
|
+
|
|
1363
|
+
@httmock.urlmatch(scheme='https', netloc='^cilogon.org$',
|
|
1364
|
+
path='^/authorize$', method='GET')
|
|
1365
|
+
def mockCilogonRedirect(url, request):
|
|
1366
|
+
redirectUri = None
|
|
1367
|
+
try:
|
|
1368
|
+
params = urllib.parse.parse_qs(url.query)
|
|
1369
|
+
# Check redirect_uri first, so other errors can still redirect
|
|
1370
|
+
redirectUri = params['redirect_uri'][0]
|
|
1371
|
+
self.assertEqual(params['client_id'], [providerInfo['client_id']['value']])
|
|
1372
|
+
except (KeyError, AssertionError) as e:
|
|
1373
|
+
return {
|
|
1374
|
+
'status_code': 404,
|
|
1375
|
+
'content': json.dumps({
|
|
1376
|
+
'error': repr(e)
|
|
1377
|
+
})
|
|
1378
|
+
}
|
|
1379
|
+
try:
|
|
1380
|
+
self.assertRegex(redirectUri, providerInfo['allowed_callback_re'])
|
|
1381
|
+
self.assertEqual(params['response_type'], ['code'])
|
|
1382
|
+
self.assertEqual(params['scope'], ['openid email profile'])
|
|
1383
|
+
state = params['state'][0]
|
|
1384
|
+
# Nothing to test for state, since provider doesn't care
|
|
1385
|
+
except (KeyError, AssertionError) as e:
|
|
1386
|
+
returnQuery = urllib.parse.urlencode({
|
|
1387
|
+
'error': repr(e),
|
|
1388
|
+
})
|
|
1389
|
+
else:
|
|
1390
|
+
returnQuery = urllib.parse.urlencode({
|
|
1391
|
+
'state': state,
|
|
1392
|
+
'code': providerInfo['accounts'][self.accountType]['auth_code']
|
|
1393
|
+
})
|
|
1394
|
+
return {
|
|
1395
|
+
'status_code': 302,
|
|
1396
|
+
'headers': {
|
|
1397
|
+
'Location': '%s?%s' % (redirectUri, returnQuery)
|
|
1398
|
+
}
|
|
1399
|
+
}
|
|
1400
|
+
|
|
1401
|
+
@httmock.urlmatch(scheme='https', netloc='^cilogon.org$',
|
|
1402
|
+
path='^/oauth2/token$', method='POST')
|
|
1403
|
+
def mockCilogonToken(url, request):
|
|
1404
|
+
try:
|
|
1405
|
+
params = urllib.parse.parse_qs(request.body)
|
|
1406
|
+
self.assertEqual(params['grant_type'], ['authorization_code'])
|
|
1407
|
+
self.assertEqual(params['client_id'], [providerInfo['client_id']['value']])
|
|
1408
|
+
for account in providerInfo['accounts'].values():
|
|
1409
|
+
if account['auth_code'] == params['code'][0]:
|
|
1410
|
+
break
|
|
1411
|
+
else:
|
|
1412
|
+
self.fail()
|
|
1413
|
+
self.assertEqual(params['client_secret'], [providerInfo['client_secret']['value']])
|
|
1414
|
+
self.assertRegex(
|
|
1415
|
+
params['redirect_uri'][0], providerInfo['allowed_callback_re'])
|
|
1416
|
+
except (KeyError, AssertionError) as e:
|
|
1417
|
+
return {
|
|
1418
|
+
'status_code': 400,
|
|
1419
|
+
'headers': {
|
|
1420
|
+
'Content-Type': 'application/json'
|
|
1421
|
+
},
|
|
1422
|
+
'content': json.dumps({
|
|
1423
|
+
'error': repr(e),
|
|
1424
|
+
'error_description': repr(e)
|
|
1425
|
+
})
|
|
1426
|
+
}
|
|
1427
|
+
return {
|
|
1428
|
+
'status_code': 200,
|
|
1429
|
+
'headers': {
|
|
1430
|
+
'Content-Type': 'application/json'
|
|
1431
|
+
},
|
|
1432
|
+
'content': json.dumps({
|
|
1433
|
+
'token_type': 'Bearer',
|
|
1434
|
+
'access_token': account['access_token'],
|
|
1435
|
+
'expires_in': 3600,
|
|
1436
|
+
'id_token': 'cilogon_id_token'
|
|
1437
|
+
})
|
|
1438
|
+
}
|
|
1439
|
+
|
|
1440
|
+
@httmock.urlmatch(scheme='https', netloc='^cilogon.org$',
|
|
1441
|
+
path='^/oauth2/userinfo$', method='GET')
|
|
1442
|
+
def mockCilogonApiUser(url, request):
|
|
1443
|
+
try:
|
|
1444
|
+
for account in providerInfo['accounts'].values():
|
|
1445
|
+
if 'Bearer %s' % account['access_token'] == request.headers['Authorization']:
|
|
1446
|
+
break
|
|
1447
|
+
else:
|
|
1448
|
+
self.fail()
|
|
1449
|
+
except AssertionError as e:
|
|
1450
|
+
return {
|
|
1451
|
+
'status_code': 401,
|
|
1452
|
+
'content': json.dumps({
|
|
1453
|
+
'message': repr(e)
|
|
1454
|
+
})
|
|
1455
|
+
}
|
|
1456
|
+
return json.dumps({
|
|
1457
|
+
'sub': account['user']['oauth']['id'],
|
|
1458
|
+
'email': account['user']['email'],
|
|
1459
|
+
'given_name': account['user']['firstName'],
|
|
1460
|
+
'family_name': account['user']['lastName']
|
|
1461
|
+
})
|
|
1462
|
+
|
|
1463
|
+
with httmock.HTTMock(
|
|
1464
|
+
mockCilogonRedirect,
|
|
1465
|
+
mockCilogonToken,
|
|
1466
|
+
mockCilogonApiUser,
|
|
1467
|
+
# Must keep 'mockOtherRequest' last
|
|
1468
|
+
self.mockOtherRequest
|
|
1469
|
+
):
|
|
1470
|
+
self._testOauth(providerInfo)
|
|
1471
|
+
|
|
1472
|
+
def testMicrosoftOauth(self): # noqa
|
|
1317
1473
|
providerInfo = {
|
|
1318
1474
|
'id': 'microsoft',
|
|
1319
1475
|
'name': 'Microsoft',
|
|
@@ -1348,11 +1504,10 @@ class OauthTest(base.TestCase):
|
|
|
1348
1504
|
'auth_code': 'microsoft_new_auth_code',
|
|
1349
1505
|
'access_token': 'microsoft_new_test_token',
|
|
1350
1506
|
'user': {
|
|
1351
|
-
# login
|
|
1352
|
-
#
|
|
1353
|
-
# or underscores
|
|
1507
|
+
# this login is not provided by Microsoft, but will be
|
|
1508
|
+
# derived from the email address by _deriveLogin
|
|
1354
1509
|
'login': 'drago',
|
|
1355
|
-
'email': '
|
|
1510
|
+
'email': 'drago@labs.ussr.gov',
|
|
1356
1511
|
'firstName': 'Ivan',
|
|
1357
1512
|
'lastName': 'Drago',
|
|
1358
1513
|
'oauth': {
|
|
@@ -1364,14 +1519,27 @@ class OauthTest(base.TestCase):
|
|
|
1364
1519
|
}
|
|
1365
1520
|
}
|
|
1366
1521
|
|
|
1522
|
+
@httmock.urlmatch(scheme='https', netloc='^login.microsoftonline.com$',
|
|
1523
|
+
path=r'^/organizations/v2\.0/\.well-known/openid-configuration$',
|
|
1524
|
+
method='GET')
|
|
1525
|
+
def mockMicrosoftDiscovery(url, request):
|
|
1526
|
+
return json.dumps({
|
|
1527
|
+
'authorization_endpoint':
|
|
1528
|
+
'https://login.microsoftonline.com/organizations/oauth2/v2.0/authorize',
|
|
1529
|
+
'token_endpoint':
|
|
1530
|
+
'https://login.microsoftonline.com/organizations/oauth2/v2.0/token',
|
|
1531
|
+
'issuer': 'https://login.microsoftonline.com/organizations/v2.0',
|
|
1532
|
+
'tenant_region_scope': 'EU',
|
|
1533
|
+
'cloud_instance_name': 'microsoftonline.com',
|
|
1534
|
+
'cloud_graph_host_name': 'graph.windows.net',
|
|
1535
|
+
'msgraph_host': 'graph.microsoft.com',
|
|
1536
|
+
})
|
|
1537
|
+
|
|
1367
1538
|
@httmock.urlmatch(scheme='https', netloc='^login.microsoftonline.com$',
|
|
1368
1539
|
path='^/organizations/oauth2/v2.0/authorize$', method='GET')
|
|
1369
1540
|
def mockMicrosoftRedirect(url, request):
|
|
1370
|
-
redirectUri = None
|
|
1371
1541
|
try:
|
|
1372
1542
|
params = urllib.parse.parse_qs(url.query)
|
|
1373
|
-
# Check redirect_uri first, so other errors can still redirect
|
|
1374
|
-
redirectUri = params['redirect_uri'][0]
|
|
1375
1543
|
self.assertEqual(params['client_id'], [providerInfo['client_id']['value']])
|
|
1376
1544
|
except (KeyError, AssertionError) as e:
|
|
1377
1545
|
return {
|
|
@@ -1381,10 +1549,13 @@ class OauthTest(base.TestCase):
|
|
|
1381
1549
|
})
|
|
1382
1550
|
}
|
|
1383
1551
|
try:
|
|
1384
|
-
self.assertRegex(redirectUri, providerInfo['allowed_callback_re'])
|
|
1385
1552
|
state = params['state'][0]
|
|
1386
1553
|
# Nothing to test for state, since provider doesn't care
|
|
1387
|
-
self.assertEqual(params['scope'], ['
|
|
1554
|
+
self.assertEqual(params['scope'], ['User.Read offline_access openid profile'])
|
|
1555
|
+
self.assertEqual(params['response_type'], ['code'])
|
|
1556
|
+
# msal's get_authorization_request_url is called without a
|
|
1557
|
+
# redirect_uri, so none should be present here
|
|
1558
|
+
self.assertNotIn('redirect_uri', params)
|
|
1388
1559
|
except (KeyError, AssertionError) as e:
|
|
1389
1560
|
returnQuery = urllib.parse.urlencode({
|
|
1390
1561
|
'error': repr(e),
|
|
@@ -1398,15 +1569,79 @@ class OauthTest(base.TestCase):
|
|
|
1398
1569
|
return {
|
|
1399
1570
|
'status_code': 302,
|
|
1400
1571
|
'headers': {
|
|
1401
|
-
'Location': '
|
|
1572
|
+
'Location': 'http://127.0.0.1:30000/api/v1/oauth/microsoft/callback?%s'
|
|
1573
|
+
% returnQuery
|
|
1574
|
+
}
|
|
1575
|
+
}
|
|
1576
|
+
|
|
1577
|
+
@httmock.urlmatch(scheme='https', netloc='^login.microsoftonline.com$',
|
|
1578
|
+
path='^/organizations/oauth2/v2.0/token$', method='POST')
|
|
1579
|
+
def mockMicrosoftToken(url, request):
|
|
1580
|
+
try:
|
|
1581
|
+
params = urllib.parse.parse_qs(request.body)
|
|
1582
|
+
self.assertEqual(params['grant_type'], ['authorization_code'])
|
|
1583
|
+
self.assertEqual(params['client_id'], [providerInfo['client_id']['value']])
|
|
1584
|
+
for account in providerInfo['accounts'].values():
|
|
1585
|
+
if account['auth_code'] == params['code'][0]:
|
|
1586
|
+
break
|
|
1587
|
+
else:
|
|
1588
|
+
self.fail()
|
|
1589
|
+
self.assertEqual(params['client_secret'], [providerInfo['client_secret']['value']])
|
|
1590
|
+
except (KeyError, AssertionError) as e:
|
|
1591
|
+
return {
|
|
1592
|
+
'status_code': 400,
|
|
1593
|
+
'headers': {
|
|
1594
|
+
'Content-Type': 'application/json'
|
|
1595
|
+
},
|
|
1596
|
+
'content': json.dumps({
|
|
1597
|
+
'error': repr(e),
|
|
1598
|
+
'error_description': repr(e)
|
|
1599
|
+
})
|
|
1402
1600
|
}
|
|
1601
|
+
return {
|
|
1602
|
+
'status_code': 200,
|
|
1603
|
+
'headers': {
|
|
1604
|
+
'Content-Type': 'application/json'
|
|
1605
|
+
},
|
|
1606
|
+
'content': json.dumps({
|
|
1607
|
+
'token_type': 'Bearer',
|
|
1608
|
+
'access_token': account['access_token'],
|
|
1609
|
+
'expires_in': 3600
|
|
1610
|
+
})
|
|
1403
1611
|
}
|
|
1404
1612
|
|
|
1613
|
+
@httmock.urlmatch(scheme='https', netloc='^graph.microsoft.com$',
|
|
1614
|
+
path='^/v1.0/me$', method='GET')
|
|
1615
|
+
def mockMicrosoftApiUser(url, request):
|
|
1616
|
+
try:
|
|
1617
|
+
for account in providerInfo['accounts'].values():
|
|
1618
|
+
if 'Bearer %s' % account['access_token'] == request.headers['Authorization']:
|
|
1619
|
+
break
|
|
1620
|
+
else:
|
|
1621
|
+
self.fail()
|
|
1622
|
+
except AssertionError as e:
|
|
1623
|
+
return {
|
|
1624
|
+
'status_code': 401,
|
|
1625
|
+
'content': json.dumps({
|
|
1626
|
+
'message': repr(e)
|
|
1627
|
+
})
|
|
1628
|
+
}
|
|
1629
|
+
return json.dumps({
|
|
1630
|
+
'id': account['user']['oauth']['id'],
|
|
1631
|
+
'mail': account['user']['email'],
|
|
1632
|
+
'givenName': account['user']['firstName'],
|
|
1633
|
+
'surname': account['user']['lastName']
|
|
1634
|
+
})
|
|
1635
|
+
|
|
1636
|
+
# The mocked authority URLs above use the 'organizations' tenant,
|
|
1637
|
+
# whereas the default tenant is 'common'
|
|
1638
|
+
Setting().set(PluginSettings.MICROSOFT_TENANT_ID, 'organizations')
|
|
1639
|
+
|
|
1405
1640
|
with httmock.HTTMock(
|
|
1641
|
+
mockMicrosoftDiscovery,
|
|
1406
1642
|
mockMicrosoftRedirect,
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
# mockMicrosoftApiEmail,
|
|
1643
|
+
mockMicrosoftToken,
|
|
1644
|
+
mockMicrosoftApiUser,
|
|
1410
1645
|
# Must keep 'mockOtherRequest' last
|
|
1411
1646
|
self.mockOtherRequest,
|
|
1412
1647
|
):
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{girder_oauth-5.0.20.dev2 → girder_oauth-5.0.20.dev6}/girder_oauth/web_client/dist/style.css
RENAMED
|
File without changes
|
|
File without changes
|
{girder_oauth-5.0.20.dev2 → girder_oauth-5.0.20.dev6}/girder_oauth.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{girder_oauth-5.0.20.dev2 → girder_oauth-5.0.20.dev6}/girder_oauth.egg-info/entry_points.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{girder_oauth-5.0.20.dev2 → girder_oauth-5.0.20.dev6}/girder_oauth.egg-info/scm_file_list.json
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|