def new_authorized_apps_collection(db): """Move the authorized_apps information from the users collection to an authorized_apps collection. """ app_cache = {} auth = Authorizator(db) scopes = ['read-passwords', 'write-passwords', 'read-userinfo'] for user in db.users.find(): authorized_apps = user['authorized_apps'] # create an authorized_apps document for every authorized app for app_id in authorized_apps: if app_id not in app_cache: app_cache[app_id] = db.applications.find_one({'_id': app_id}) app = app_cache[app_id] credentials = { 'client_id': app['client_id'], 'user': user, 'redirect_uri': app['callback_url'], 'response_type': 'code', } auth.store_user_authorization(scopes, credentials) safe_print('Storing authorized app "%s" for user %s' % ( app['client_id'], get_user_display_name(user), )) # remove the authorized_apps attribute from all users db.users.update({}, {'$unset': {'authorized_apps': ''}}, multi=True)
def revoke_application(request): assert_authenticated_user_is_registered(request) try: app_id = bson.ObjectId(request.matchdict['app']) except bson.errors.InvalidId: return HTTPBadRequest(body='Invalid application id') app = request.db.applications.find_one(app_id) if app is None: return HTTPNotFound() authorizator = Authorizator(request.db, app) if not authorizator.is_app_authorized(request.user): return HTTPUnauthorized() if 'submit' in request.POST: authorizator.remove_user_authorization(request.user) request.session.flash( _('The access to application ${app} has been revoked', mapping={'app': app['name']}), 'success', ) return HTTPFound( location=request.route_path('oauth2_authorized_applications')) return {'app': app}
def test_revoke_application(self): # this view required authentication res = self.testapp.get('/oauth2/applications/xxx/revoke') self.assertEqual(res.status, '200 OK') res.mustcontain('Log in') # Log in user_id = self.db.users.insert({ 'twitter_id': 'twitter1', 'screen_name': 'John Doe', 'first_name': 'John', 'last_name': 'Doe', 'email': '*****@*****.**', }) self.testapp.get('/__login/' + str(user_id)) res = self.testapp.get('/oauth2/applications/xxx/revoke', status=400) self.assertEqual(res.status, '400 Bad Request') res.mustcontain('Invalid application id') res = self.testapp.get( '/oauth2/applications/000000000000000000000000/revoke', status=404) self.assertEqual(res.status, '404 Not Found') # create a valid app app_id = self.db.applications.insert({ 'owner': bson.ObjectId(), 'name': 'Test Application', 'main_url': 'http://example.com', 'callback_url': 'http://example.com/callback', 'client_id': '123456', 'client_secret': 'secret', }) authorizator = Authorizator(self.db) credentials = { 'client_id': '123456', 'user': {'_id': user_id}, 'redirect_uri': 'http://example.com/callback', 'response_type': 'code', } authorizator.store_user_authorization(['read-passwords'], credentials) res = self.testapp.get('/oauth2/applications/%s/revoke' % str(app_id)) self.assertEqual(res.status, '200 OK') res.mustcontain('Revoke authorization to application <span>Test Application</span>') res = self.testapp.post('/oauth2/applications/%s/revoke' % str(app_id), { 'submit': 'Yes, I am sure', }) self.assertEqual(res.status, '302 Found') self.assertEqual(res.location, 'http://localhost/oauth2/authorized-applications') self.assertFalse(authorizator.is_app_authorized(['read-passwords'], credentials))
def test_some_users(self): authorizator = Authorizator(self.db) app1_id = self.db.applications.insert({ 'client_id': 'app1', 'callback_url': 'https://example.com/callback/1', }) app2_id = self.db.applications.insert({ 'client_id': 'app2', 'callback_url': 'https://example.com/callback/2', }) u1_id = self.db.users.insert({ 'first_name': 'John', 'last_name': 'Doe', 'email': '*****@*****.**', 'authorized_apps': [app1_id, app2_id], }) auths = authorizator.get_user_authorizations({'_id': u1_id}) self.assertEqual(auths.count(), 0) u2_id = self.db.users.insert({ 'first_name': 'John2', 'last_name': 'Doe2', 'email': '*****@*****.**', 'send_passwords_periodically': False, 'authorized_apps': [app1_id], }) auths = authorizator.get_user_authorizations({'_id': u2_id}) self.assertEqual(auths.count(), 0) sys.argv = ['notused', self.conf_file_path, 'new_authorized_apps_collection'] sys.stdout = StringIO() result = migrate() self.assertEqual(result, None) stdout = sys.stdout.getvalue() stdout = sys.stdout.getvalue() expected_output = """Storing authorized app "app1" for user John Doe <*****@*****.**> Storing authorized app "app2" for user John Doe <*****@*****.**> Storing authorized app "app1" for user John2 Doe2 <*****@*****.**> """ self.assertEqual(stdout, expected_output) user1 = self.db.users.find_one({'_id': u1_id}) self.assertFalse('authorized_apps' in user1) auths = authorizator.get_user_authorizations({'_id': u1_id}) self.assertEqual(auths.count(), 2) user2 = self.db.users.find_one({'_id': u2_id}) self.assertFalse('authorized_apps' in user2) auths = authorizator.get_user_authorizations({'_id': u2_id}) self.assertEqual(auths.count(), 1)
def test_already_authorized_app(self): user_id = self._login() self._create_client() authorizator = Authorizator(self.db) auths = authorizator.get_user_authorizations({'_id': user_id}) self.assertEqual(auths.count(), 0) # do an initial authorization res = self.testapp.get('/oauth2/endpoints/authorization', { 'response_type': 'code', 'client_id': '123456', 'redirect_uri': 'https://example.com/callback', }) self.assertEqual(res.status, '200 OK') res = self.testapp.post('/oauth2/endpoints/authorization', { 'submit': 'Authorize', 'response_type': 'code', 'client_id': '123456', 'redirect_uri': 'https://example.com/callback', 'scope': 'read-passwords', }) self.assertEqual(res.status, '302 Found') auths = authorizator.get_user_authorizations({'_id': user_id}) self.assertEqual(auths.count(), 1) # Now do a second authorization res = self.testapp.get('/oauth2/endpoints/authorization', { 'response_type': 'code', 'client_id': '123456', 'redirect_uri': 'https://example.com/callback', }) self.assertEqual(res.status, '302 Found') auths = authorizator.get_user_authorizations({'_id': user_id}) self.assertEqual(auths.count(), 1) grants = self.db.authorization_codes.find({ 'client_id': '123456', 'user': user_id, }) # There are two grants now self.assertEqual(grants.count(), 2) code = grants[1]['code'] location = 'https://example.com/callback?code=%s' % code self.assertEqual(res.location, location)
def test_non_authorized_app_yet(self): user_id = self._login() self._create_client() authorizator = Authorizator(self.db) auths = authorizator.get_user_authorizations({'_id': user_id}) self.assertEqual(auths.count(), 0) res = self.testapp.get('/oauth2/endpoints/authorization', { 'response_type': 'code', 'client_id': '123456', 'redirect_uri': 'https://example.com/callback', }) self.assertEqual(res.status, '200 OK') res.mustcontain('Authorize Application') res.mustcontain('Permissions:') res.mustcontain('Access your passwords') res.mustcontain('Allow access') res.mustcontain('No, thanks') res.mustcontain('You can revoke this authorization in the future.') res = self.testapp.post('/oauth2/endpoints/authorization', { 'submit': 'Authorize', 'response_type': 'code', 'client_id': '123456', 'redirect_uri': 'https://example.com/callback', 'scope': 'read-passwords', }) self.assertEqual(res.status, '302 Found') # Check that the app is authorized now auths = authorizator.get_user_authorizations({'_id': user_id}) self.assertEqual(auths.count(), 1) auth = auths[0] self.assertEqual(auth['redirect_uri'], 'https://example.com/callback') self.assertEqual(auth['response_type'], 'code') self.assertEqual(auth['client_id'], '123456') self.assertEqual(auth['scope'], 'read-passwords') self.assertEqual(auth['user'], user_id) # Check the right redirect url grant = self.db.authorization_codes.find_one({ 'client_id': '123456', 'user': user_id, }) self.assertNotEqual(grant, None) code = grant['code'] location = 'https://example.com/callback?code=%s' % code self.assertEqual(res.location, location)
def merge_users(db, user1, user2): # move all passwords of user2 to user1 db.passwords.update({'owner': user2['_id']}, { '$set': { 'owner': user1['_id'], }, }, multi=True) # move authorized_apps from user2 to user1 authorizator = Authorizator(db) for auth in authorizator.get_user_authorizations(user2): credentials = { 'client_id': auth['client_id'], 'user': user1, 'redirect_uri': auth['redirect_uri'], 'response_type': auth['response_type'], } scopes = auth['scope'].split(' ') authorizator.store_user_authorization(scopes, credentials) authorizator.remove_all_user_authorizations(user2) updates = {} # copy the providers for provider in get_available_providers(): key = provider + '_id' if key in user2 and key not in user1: sets = updates.setdefault('$set', {}) sets[key] = user2[key] db.users.update({'_id': user1['_id']}, updates) # remove user2 db.users.remove(user2['_id'])
def test_authorizator(self): app = {"_id": "app1"} authorizator = Authorizator(self.db, app) self.assertTrue(isinstance(authorizator.auth_codes, AuthorizationCodes)) self.assertTrue(isinstance(authorizator.access_codes, AccessCodes)) user = {"name": "John Doe", "authorized_apps": []} self.db.users.insert(user, safe=True) self.assertFalse(authorizator.is_app_authorized(user)) authorizator.store_user_authorization(user) user = self.db.users.find_one({"name": "John Doe"}) self.assertTrue(authorizator.is_app_authorized(user)) self.assertEqual(user["authorized_apps"], ["app1"]) authorizator.remove_user_authorization(user) user = self.db.users.find_one({"name": "John Doe"}) self.assertFalse(authorizator.is_app_authorized(user)) self.assertFalse("app1" in user["authorized_apps"])
def test_authorizator(self): app = {'_id': 'app1'} authorizator = Authorizator(self.db, app) self.assertTrue(isinstance(authorizator.auth_codes, AuthorizationCodes)) self.assertTrue(isinstance(authorizator.access_codes, AccessCodes)) user = {'name': 'John Doe', 'authorized_apps': []} self.db.users.insert(user, safe=True) self.assertFalse(authorizator.is_app_authorized(user)) authorizator.store_user_authorization(user) user = self.db.users.find_one({'name': 'John Doe'}) self.assertTrue(authorizator.is_app_authorized(user)) self.assertEqual(user['authorized_apps'], ['app1']) authorizator.remove_user_authorization(user) user = self.db.users.find_one({'name': 'John Doe'}) self.assertFalse(authorizator.is_app_authorized(user)) self.assertFalse('app1' in user['authorized_apps'])
def setUp(self): super(AuthorizatorTests, self).setUp() self.authorizator = Authorizator(self.db)
class AuthorizatorTests(testing.TestCase): def setUp(self): super(AuthorizatorTests, self).setUp() self.authorizator = Authorizator(self.db) def test_is_app_authorized_no_authorized_apps(self): self.assertFalse(self.authorizator.is_app_authorized([ 'scope1', 'scope2', ], { 'client_id': 1, 'user': {'_id': 1}, 'redirect_uri': 'http://example.com/callback', 'response_type': 'code', })) def test_is_app_authorized_different_client_id(self): self.db.authorized_apps.insert({ 'client_id': 1, 'user': 1, 'redirect_uri': 'http://example.com/callback', 'response_type': 'code', 'scope': 'scope1 scope2', }) self.assertFalse(self.authorizator.is_app_authorized([ 'scope1', 'scope2', ], { 'client_id': 2, 'user': {'_id': 1}, 'redirect_uri': 'http://example.com/callback', 'response_type': 'code', })) def test_is_app_authorized_different_user(self): self.db.authorized_apps.insert({ 'client_id': 1, 'user': 1, 'redirect_uri': 'http://example.com/callback', 'response_type': 'code', 'scope': 'scope1 scope2', }) self.assertFalse(self.authorizator.is_app_authorized([ 'scope1', 'scope2', ], { 'client_id': 1, 'user': {'_id': 2}, 'redirect_uri': 'http://example.com/callback', 'response_type': 'code', })) def test_is_app_authorized_different_redirect_uri(self): self.db.authorized_apps.insert({ 'client_id': 1, 'user': 1, 'redirect_uri': 'http://example.com/callback', 'response_type': 'code', 'scope': 'scope1 scope2', }) self.assertFalse(self.authorizator.is_app_authorized([ 'scope1', 'scope2', ], { 'client_id': 1, 'user': {'_id': 1}, 'redirect_uri': 'http://example.com/callback-new', 'response_type': 'code', })) def test_is_app_authorized_different_response_type(self): self.db.authorized_apps.insert({ 'client_id': 1, 'user': 1, 'redirect_uri': 'http://example.com/callback', 'response_type': 'code', 'scope': 'scope1 scope2', }) self.assertFalse(self.authorizator.is_app_authorized([ 'scope1', 'scope2', ], { 'client_id': 1, 'user': {'_id': 1}, 'redirect_uri': 'http://example.com/callback', 'response_type': 'token', })) def test_is_app_authorized_different_scopes(self): self.db.authorized_apps.insert({ 'client_id': 1, 'user': 1, 'redirect_uri': 'http://example.com/callback', 'response_type': 'code', 'scope': 'scope1 scope2', }) self.assertFalse(self.authorizator.is_app_authorized([ 'scope1', 'scope2', 'scope3', ], { 'client_id': 1, 'user': {'_id': 1}, 'redirect_uri': 'http://example.com/callback', 'response_type': 'code', })) def test_is_app_authorized_everything_equal(self): self.db.authorized_apps.insert({ 'client_id': 1, 'user': 1, 'redirect_uri': 'http://example.com/callback', 'response_type': 'code', 'scope': 'scope1 scope2', }) self.assertTrue(self.authorizator.is_app_authorized([ 'scope1', 'scope2', ], { 'client_id': 1, 'user': {'_id': 1}, 'redirect_uri': 'http://example.com/callback', 'response_type': 'code', })) def test_store_user_authorization_no_previous_authorization(self): self.assertEqual(self.db.authorized_apps.count(), 0) self.authorizator.store_user_authorization([ 'scope1', 'scope2', ], { 'client_id': 1, 'user': {'_id': 1}, 'redirect_uri': 'http://example.com/callback', 'response_type': 'code', }) self.assertEqual(self.db.authorized_apps.count(), 1) def test_store_user_authorization_previous_authorization(self): self.assertEqual(self.db.authorized_apps.count(), 0) self.authorizator.store_user_authorization([ 'scope1', 'scope2', ], { 'client_id': 1, 'user': {'_id': 1}, 'redirect_uri': 'http://example.com/callback', 'response_type': 'code', }) self.assertEqual(self.db.authorized_apps.count(), 1) # Store the same authorization again self.authorizator.store_user_authorization([ 'scope1', 'scope2', ], { 'client_id': 1, 'user': {'_id': 1}, 'redirect_uri': 'http://example.com/callback', 'response_type': 'code', }) # still only one record self.assertEqual(self.db.authorized_apps.count(), 1) def test_get_user_authorizations_empty(self): auths = self.authorizator.get_user_authorizations({'_id': 1}) self.assertEqual(auths.count(), 0) def test_get_user_authorizations_one_authorization(self): self.authorizator.store_user_authorization([ 'scope1', 'scope2', ], { 'client_id': 1, 'user': {'_id': 1}, 'redirect_uri': 'http://example.com/callback', 'response_type': 'code', }) auths = self.authorizator.get_user_authorizations({'_id': 1}) self.assertEqual(auths.count(), 1) self.assertEqual(auths[0]['client_id'], 1) self.assertEqual(auths[0]['redirect_uri'], 'http://example.com/callback') self.assertEqual(auths[0]['response_type'], 'code') self.assertEqual(auths[0]['scope'], 'scope1 scope2') def test_get_user_authorizations_two_authorization(self): self.authorizator.store_user_authorization([ 'scope1', 'scope2', ], { 'client_id': 1, 'user': {'_id': 1}, 'redirect_uri': 'http://example.com/callback', 'response_type': 'code', }) self.authorizator.store_user_authorization([ 'scope1', 'scope2', ], { 'client_id': 2, 'user': {'_id': 1}, 'redirect_uri': 'http://example.com/callback2', 'response_type': 'code', }) self.authorizator.store_user_authorization([ 'scope1', 'scope2', ], { 'client_id': 2, 'user': {'_id': 2}, 'redirect_uri': 'http://example.com/callback2', 'response_type': 'code', }) auths = self.authorizator.get_user_authorizations({'_id': 1}) self.assertEqual(auths.count(), 2) self.assertEqual(auths[0]['client_id'], 1) self.assertEqual(auths[0]['redirect_uri'], 'http://example.com/callback') self.assertEqual(auths[0]['response_type'], 'code') self.assertEqual(auths[0]['scope'], 'scope1 scope2') self.assertEqual(auths[1]['client_id'], 2) self.assertEqual(auths[1]['redirect_uri'], 'http://example.com/callback2') self.assertEqual(auths[1]['response_type'], 'code') self.assertEqual(auths[1]['scope'], 'scope1 scope2') def test_remove_user_authorization(self): auths = self.authorizator.get_user_authorizations({'_id': 1}) self.assertEqual(auths.count(), 0) self.authorizator.store_user_authorization([ 'scope1', 'scope2', ], { 'client_id': 1, 'user': {'_id': 1}, 'redirect_uri': 'http://example.com/callback', 'response_type': 'code', }) auths = self.authorizator.get_user_authorizations({'_id': 1}) self.assertEqual(auths.count(), 1) self.authorizator.remove_user_authorization({'_id': 1}, 1) auths = self.authorizator.get_user_authorizations({'_id': 1}) self.assertEqual(auths.count(), 0) def test_remove_all_user_authorizations(self): auths = self.authorizator.get_user_authorizations({'_id': 1}) self.assertEqual(auths.count(), 0) self.authorizator.store_user_authorization([ 'scope1', 'scope2', ], { 'client_id': 1, 'user': {'_id': 1}, 'redirect_uri': 'http://example.com/callback/1', 'response_type': 'code', }) self.authorizator.store_user_authorization([ 'scope1', 'scope2', ], { 'client_id': 2, 'user': {'_id': 1}, 'redirect_uri': 'http://example.com/callback/2', 'response_type': 'code', }) auths = self.authorizator.get_user_authorizations({'_id': 1}) self.assertEqual(auths.count(), 2) self.authorizator.remove_all_user_authorizations({'_id': 1}) auths = self.authorizator.get_user_authorizations({'_id': 1}) self.assertEqual(auths.count(), 0)
def authorization_endpoint(request): response_type = request.params.get('response_type') if response_type is None: return HTTPBadRequest('Missing required response_type') if response_type != 'code': return HTTPNotImplemented('Only code is supported') client_id = request.params.get('client_id') if client_id is None: return HTTPBadRequest('Missing required client_type') app = request.db.applications.find_one({'client_id': client_id}) if app is None: return HTTPNotFound() redirect_uri = request.params.get('redirect_uri') if redirect_uri is None: redirect_uri = app['callback_url'] else: if redirect_uri != app['callback_url']: return HTTPBadRequest( 'Redirect URI does not match registered callback URL') scope = request.params.get('scope', DEFAULT_SCOPE) state = request.params.get('state') user = assert_authenticated_user_is_registered(request) authorizator = Authorizator(request.db, app) if 'submit' in request.POST: if not authorizator.is_app_authorized(request.user): authorizator.store_user_authorization(request.user) code = authorizator.auth_codes.create( request.user['_id'], app['client_id'], scope) url = authorizator.auth_codes.get_redirect_url( code, redirect_uri, state) return HTTPFound(location=url) elif 'cancel' in request.POST: return HTTPFound(app['main_url']) else: if authorizator.is_app_authorized(user): code = authorizator.auth_codes.create( user['_id'], app['client_id'], scope) url = authorizator.auth_codes.get_redirect_url( code, redirect_uri, state) return HTTPFound(location=url) else: authorship_information = '' owner_id = app.get('owner', None) if owner_id is not None: owner = request.db.users.find_one({'_id': owner_id}) if owner: email = owner.get('email', None) if email: authorship_information = _('By ${owner}', mapping={'owner': email}) scopes = [SCOPE_NAMES.get(scope, scope) for scope in scope.split(' ')] return { 'response_type': response_type, 'client_id': client_id, 'redirect_uri': redirect_uri, 'scope': scope, 'state': state, 'app': app, 'scopes': scopes, 'authorship_information': authorship_information, }
def test_identity_providers(self): # this view required authentication res = self.testapp.get('/identity-providers') self.assertEqual(res.status, '200 OK') res.mustcontain('Log in') authorizator = Authorizator(self.db) # Log in user1_id = self.db.users.insert({ 'twitter_id': 'twitter1', 'screen_name': 'John Doe', 'first_name': 'John', 'last_name': 'Doe', 'email': '*****@*****.**', 'email_verified': True, }) authorizator.store_user_authorization(['scope1'], { 'client_id': 'app1', 'user': {'_id': user1_id}, 'redirect_uri': 'http://example.com/callback/1', 'response_type': 'code', }) authorizator.store_user_authorization(['scope1'], { 'client_id': 'app2', 'user': {'_id': user1_id}, 'redirect_uri': 'http://example.com/callback/2', 'response_type': 'code', }) self.testapp.get('/__login/' + str(user1_id)) self.db.passwords.insert({ 'owner': user1_id, 'password': '******', }) # one account is not enough for merging res = self.testapp.post('/identity-providers', { 'submit': 'Merge my accounts', }, status=400) self.assertEqual(res.status, '400 Bad Request') res.mustcontain('You do not have enough accounts to merge') # so let's create another account with the same email user2_id = self.db.users.insert({ 'google_id': 'google1', 'screen_name': 'John Doe', 'first_name': 'John', 'last_name': 'Doe', 'email': '*****@*****.**', 'email_verified': True, }) authorizator.store_user_authorization(['scope1'], { 'client_id': 'app2', 'user': {'_id': user2_id}, 'redirect_uri': 'http://example.com/callback/2', 'response_type': 'code', }) authorizator.store_user_authorization(['scope1'], { 'client_id': 'app3', 'user': {'_id': user2_id}, 'redirect_uri': 'http://example.com/callback/3', 'response_type': 'code', }) self.db.passwords.insert({ 'owner': user2_id, 'password': '******', }) # now the profile view should say I can merge my accounts res = self.testapp.get('/identity-providers') self.assertEqual(res.status, '200 OK') res.mustcontain('You are registered with the following accounts', 'Merge my accounts', 'If you merge your accounts') # if only one account is selected or fake accounts # are selected nothing is merged res = self.testapp.post('/identity-providers', { 'account-%s' % str(user1_id): 'on', 'account-000000000000000000000000': 'on', 'submit': 'Merge my accounts', }, status=302) self.assertEqual(res.status, '302 Found') self.assertEqual(res.location, 'http://localhost/identity-providers') self.assertEqual(2, self.db.users.count()) self.assertEqual(1, self.db.passwords.find( {'owner': user1_id}).count()) self.assertEqual(1, self.db.passwords.find( {'owner': user2_id}).count()) # let's merge them res = self.testapp.post('/identity-providers', { 'account-%s' % str(user1_id): 'on', 'account-%s' % str(user2_id): 'on', 'submit': 'Merge my accounts', }, status=302) self.assertEqual(res.status, '302 Found') self.assertEqual(res.location, 'http://localhost/identity-providers') # the accounts have been merged self.assertEqual(1, self.db.users.count()) user1_refreshed = self.db.users.find_one({'_id': user1_id}) self.assertEqual(user1_refreshed['google_id'], 'google1') auths = authorizator.get_user_authorizations(user1_refreshed) for real, expected in zip(auths, ['app1', 'app2', 'app3']): self.assertEqual(real['client_id'], expected) user2_refreshed = self.db.users.find_one({'_id': user2_id}) self.assertEqual(user2_refreshed, None) self.assertEqual(2, self.db.passwords.find( {'owner': user1_id}).count())