def test_account_setup(app, example_cern, models_fixture):
    """Test account setup after login."""
    with app.test_client() as c:
        ioc = app.extensions['oauthlib.client']

        # Ensure remote apps have been loaded (due to before first request)
        resp = c.get(url_for('invenio_oauthclient.login', remote_app='cern'))
        assert resp.status_code == 302

        example_response, example_token, example_account_info = example_cern

        mock_response(app.extensions['oauthlib.client'], 'cern',
                      example_token)
        mock_remote_get(ioc, 'cern', example_response)

        resp = c.get(url_for(
            'invenio_oauthclient.authorized',
            remote_app='cern', code='test',
            state=get_state('cern')))
        assert resp.status_code == 302
        assert resp.location == ('http://localhost/account/settings/'
                                 'linkedaccounts/')
        assert len(g.identity.provides) == 7

    datastore = app.extensions['invenio-accounts'].datastore
    user = datastore.find_user(email='*****@*****.**')
    assert user

    with app.test_request_context():
        resp = disconnect_handler(ioc.remote_apps['cern'])
        assert resp.status_code >= 300

        login_user(user)
        assert len(g.identity.provides) == 7
        disconnect_handler(ioc.remote_apps['cern'])
Example #2
0
 async def send(request, **_):
     if "resource" not in request.query:
         # availability probe
         return mock_response(status_code=400, json_payload={})
     return mock_response(status_code=code, json_payload={"error": error_message})
Example #3
0
async def test_azure_arc(tmpdir):
    """Azure Arc 2019-11-01"""
    access_token = "****"
    api_version = "2019-11-01"
    expires_on = 42
    identity_endpoint = "http://localhost:42/token"
    imds_endpoint = "http://localhost:42"
    scope = "scope"
    secret_key = "XXXX"

    key_file = tmpdir.mkdir("key").join("key_file.key")
    key_file.write(secret_key)
    assert key_file.read() == secret_key
    key_path = os.path.join(key_file.dirname, key_file.basename)

    transport = async_validating_transport(
        requests=[
            Request(
                base_url=identity_endpoint,
                method="GET",
                required_headers={"Metadata": "true"},
                required_params={
                    "api-version": api_version,
                    "resource": scope
                },
            ),
            Request(
                base_url=identity_endpoint,
                method="GET",
                required_headers={
                    "Metadata": "true",
                    "Authorization": "Basic {}".format(secret_key)
                },
                required_params={
                    "api-version": api_version,
                    "resource": scope
                },
            ),
        ],
        responses=[
            # first response gives path to authentication key
            mock_response(status_code=401,
                          headers={
                              "WWW-Authenticate":
                              "Basic realm={}".format(key_path)
                          }),
            mock_response(
                json_payload={
                    "access_token": access_token,
                    "expires_on": expires_on,
                    "resource": scope,
                    "token_type": "Bearer",
                }),
        ],
    )

    with mock.patch(
            "os.environ",
        {
            EnvironmentVariables.IDENTITY_ENDPOINT: identity_endpoint,
            EnvironmentVariables.IMDS_ENDPOINT: imds_endpoint
        },
    ):
        token = await ManagedIdentityCredential(transport=transport
                                                ).get_token(scope)
        assert token.token == access_token
        assert token.expires_on == expires_on
Example #4
0
async def test_app_service_user_assigned_identity():
    """App Service 2017-09-01: MSI_ENDPOINT, MSI_SECRET set"""

    expected_token = "****"
    expires_on = 42
    client_id = "some-guid"
    endpoint = "http://localhost:42/token"
    secret = "expected-secret"
    scope = "scope"
    param_name, param_value = "foo", "bar"

    transport = async_validating_transport(
        requests=[
            Request(
                base_url=endpoint,
                method="GET",
                required_headers={
                    "secret": secret,
                    "User-Agent": USER_AGENT
                },
                required_params={
                    "api-version": "2017-09-01",
                    "clientid": client_id,
                    "resource": scope
                },
            ),
            Request(
                base_url=endpoint,
                method="GET",
                required_headers={
                    "secret": secret,
                    "User-Agent": USER_AGENT
                },
                required_params={
                    "api-version": "2017-09-01",
                    "resource": scope,
                    param_name: param_value
                },
            ),
        ],
        responses=[
            mock_response(
                json_payload={
                    "access_token": expected_token,
                    "expires_on": "01/01/1970 00:00:{} +00:00".format(
                        expires_on),
                    "resource": scope,
                    "token_type": "Bearer",
                })
        ] * 2,
    )

    with mock.patch.dict(
            MANAGED_IDENTITY_ENVIRON,
        {
            EnvironmentVariables.MSI_ENDPOINT: endpoint,
            EnvironmentVariables.MSI_SECRET: secret
        },
            clear=True,
    ):
        credential = ManagedIdentityCredential(client_id=client_id,
                                               transport=transport)
        token = await credential.get_token(scope)
        assert token.token == expected_token
        assert token.expires_on == expires_on

        credential = ManagedIdentityCredential(
            client_id=client_id,
            transport=transport,
            identity_config={param_name: param_value})
        token = await credential.get_token(scope)
        assert token.token == expected_token
        assert token.expires_on == expires_on
Example #5
0
async def test_prefers_app_service_2017_09_01():
    """When the environment is configured for both App Service versions, the credential should prefer 2017-09-01

    Support for 2019-08-01 was removed due to https://github.com/Azure/azure-sdk-for-python/issues/14670. This test
    should be removed when that support is added back.
    """

    access_token = "****"
    expires_on = 42
    expected_token = AccessToken(access_token, expires_on)
    url = "http://localhost:42/token"
    secret = "expected-secret"
    scope = "scope"

    transport = async_validating_transport(
        requests=[
            Request(
                url,
                method="GET",
                required_headers={
                    "secret": secret,
                    "User-Agent": USER_AGENT
                },
                required_params={
                    "api-version": "2017-09-01",
                    "resource": scope
                },
            )
        ] * 2,
        responses=[
            mock_response(
                json_payload={
                    "access_token": access_token,
                    "expires_on": "01/01/1970 00:00:{} +00:00".format(
                        expires_on),  # linux format
                    "resource": scope,
                    "token_type": "Bearer",
                }),
            mock_response(
                json_payload={
                    "access_token": access_token,
                    "expires_on": "1/1/1970 12:00:{} AM +00:00".format(
                        expires_on),  # windows format
                    "resource": scope,
                    "token_type": "Bearer",
                }),
        ],
    )

    with mock.patch.dict(
            MANAGED_IDENTITY_ENVIRON,
        {
            EnvironmentVariables.IDENTITY_ENDPOINT: url,
            EnvironmentVariables.IDENTITY_HEADER: secret,
            EnvironmentVariables.MSI_ENDPOINT: url,
            EnvironmentVariables.MSI_SECRET: secret,
        },
            clear=True,
    ):
        credential = ManagedIdentityCredential(transport=transport)
        token = await credential.get_token(scope)
        assert token == expected_token
        assert token.expires_on == expires_on

        credential = ManagedIdentityCredential(transport=transport)
        token = await credential.get_token(scope)
        assert token == expected_token
        assert token.expires_on == expires_on
Example #6
0
 async def mock_send(request, **kwargs):
     actual = urlparse(request.url)
     assert actual.scheme == "https"
     assert actual.netloc == expected_netloc
     assert actual.path.startswith("/" + tenant_id)
     return mock_response(json_payload={"token_type": "Bearer", "expires_in": 42, "access_token": access_token})
Example #7
0
def test_authorized_already_authenticated(models_fixture, example_github):
    """Test authorized callback with sign-up."""
    app = models_fixture

    datastore = app.extensions['invenio-accounts'].datastore
    login_manager = app.login_manager

    existing_email = '*****@*****.**'
    user = datastore.find_user(email=existing_email)

    @login_manager.user_loader
    def load_user(user_id):
        return user

    @app.route('/foo_login')
    def login():
        login_user(user)
        return 'Logged In'

    with mock.patch('github3.login') as MockLogin:
        MockLogin.return_value = MockGh(email='*****@*****.**')

        with app.test_client() as client:

            # make a fake login (using my login function)
            client.get('/foo_login', follow_redirects=True)

            # Ensure remote apps have been loaded (due to before first
            # request)
            client.get(
                url_for('invenio_oauthclient.login', remote_app='github'))

            # Mock access token request
            mock_response(app.extensions['oauthlib.client'], 'github',
                          example_github)

            # User then goes to 'Linked accounts' and clicks 'Connect'
            resp = client.get(
                url_for('invenio_oauthclient.login',
                        remote_app='github',
                        next='/someurl/'))
            assert resp.status_code == 302

            # User authorized the requests and is redirected back
            resp = client.get(
                url_for('invenio_oauthclient.authorized',
                        remote_app='github',
                        code='test',
                        state=_get_state()))

            # Assert database state (Sign-up complete)
            u = User.query.filter_by(email=existing_email).one()
            remote = RemoteAccount.query.filter_by(user_id=u.id).one()
            RemoteToken.query.filter_by(id_remote_account=remote.id).one()

            # Disconnect link
            resp = client.get(
                url_for('invenio_oauthclient.disconnect', remote_app='github'))
            assert resp.status_code == 302

            # User exists
            u = User.query.filter_by(email=existing_email).one()
            assert 0 == UserIdentity.query.filter_by(method='orcid',
                                                     id_user=u.id,
                                                     id='githubuser').count()
            assert RemoteAccount.query.filter_by(user_id=u.id).count() == 0
            assert RemoteToken.query.count() == 0
def test_authorized_already_authenticated(models_fixture, example_github):
    """Test authorized callback with sign-up."""
    app = models_fixture

    datastore = app.extensions['invenio-accounts'].datastore
    login_manager = app.login_manager

    existing_email = '*****@*****.**'
    user = datastore.find_user(email=existing_email)

    @login_manager.user_loader
    def load_user(user_id):
        return user

    @app.route('/foo_login')
    def login():
        login_user(user)
        return 'Logged In'

    with mock.patch('github3.login') as MockLogin:
        MockLogin.return_value = MockGh(email='*****@*****.**')

        with app.test_client() as client:

            # make a fake login (using my login function)
            client.get('/foo_login', follow_redirects=True)

            # Ensure remote apps have been loaded (due to before first
            # request)
            client.get(url_for('invenio_oauthclient.login',
                               remote_app='github'))

            # Mock access token request
            mock_response(app.extensions['oauthlib.client'], 'github',
                          example_github)

            # User then goes to 'Linked accounts' and clicks 'Connect'
            resp = client.get(
                url_for('invenio_oauthclient.login', remote_app='github',
                        next='/someurl/')
            )
            assert resp.status_code == 302

            # User authorized the requests and is redirected back
            resp = client.get(
                url_for('invenio_oauthclient.authorized',
                        remote_app='github', code='test',
                        state=_get_state()))

            # Assert database state (Sign-up complete)
            u = User.query.filter_by(email=existing_email).one()
            remote = RemoteAccount.query.filter_by(user_id=u.id).one()
            RemoteToken.query.filter_by(id_remote_account=remote.id).one()

            # Disconnect link
            resp = client.get(
                url_for('invenio_oauthclient.disconnect', remote_app='github'))
            assert resp.status_code == 302

            # User exists
            u = User.query.filter_by(email=existing_email).one()
            assert 0 == UserIdentity.query.filter_by(
                method='orcid', id_user=u.id,
                id='githubuser'
            ).count()
            assert RemoteAccount.query.filter_by(user_id=u.id).count() == 0
            assert RemoteToken.query.count() == 0
Example #9
0
 def mock_send(request, **_):
     if not request.body:
         return get_discovery_response()
     assert request.url.startswith("https://localhost/" + expected_tenant_id)
     return mock_response(json_payload=build_aad_response(access_token="*"))
Example #10
0
def test_token_exchange(tmpdir):
    exchange_token = "exchange-token"
    token_file = tmpdir.join("token")
    token_file.write(exchange_token)
    access_token = "***"
    authority = "https://localhost"
    default_client_id = "default_client_id"
    tenant = "tenant_id"
    scope = "scope"

    success_response = mock_response(
        json_payload={
            "access_token": access_token,
            "expires_in": 3600,
            "ext_expires_in": 3600,
            "expires_on": int(time.time()) + 3600,
            "not_before": int(time.time()),
            "resource": scope,
            "token_type": "Bearer",
        })
    transport = validating_transport(
        requests=[
            Request(
                base_url=authority,
                method="POST",
                required_data={
                    "client_assertion": exchange_token,
                    "client_assertion_type":
                    "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
                    "client_id": default_client_id,
                    "grant_type": "client_credentials",
                    "scope": scope,
                },
            )
        ],
        responses=[success_response],
    )

    mock_environ = {
        EnvironmentVariables.AZURE_AUTHORITY_HOST: authority,
        EnvironmentVariables.AZURE_CLIENT_ID: default_client_id,
        EnvironmentVariables.AZURE_TENANT_ID: tenant,
        EnvironmentVariables.AZURE_FEDERATED_TOKEN_FILE: token_file.strpath,
    }
    # credential should default to AZURE_CLIENT_ID
    with mock.patch.dict("os.environ", mock_environ, clear=True):
        credential = ManagedIdentityCredential(transport=transport)
        token = credential.get_token(scope)
        assert token.token == access_token

    # client_id kwarg should override AZURE_CLIENT_ID
    nondefault_client_id = "non" + default_client_id
    transport = validating_transport(
        requests=[
            Request(
                base_url=authority,
                method="POST",
                required_data={
                    "client_assertion": exchange_token,
                    "client_assertion_type":
                    "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
                    "client_id": nondefault_client_id,
                    "grant_type": "client_credentials",
                    "scope": scope,
                },
            )
        ],
        responses=[success_response],
    )

    with mock.patch.dict("os.environ", mock_environ, clear=True):
        credential = ManagedIdentityCredential(client_id=nondefault_client_id,
                                               transport=transport)
        token = credential.get_token(scope)
    assert token.token == access_token

    # AZURE_CLIENT_ID may not have a value, in which case client_id is required
    transport = validating_transport(
        requests=[
            Request(
                base_url=authority,
                method="POST",
                required_data={
                    "client_assertion": exchange_token,
                    "client_assertion_type":
                    "urn:ietf:params:oauth:client-assertion-type:jwt-bearer",
                    "client_id": nondefault_client_id,
                    "grant_type": "client_credentials",
                    "scope": scope,
                },
            )
        ],
        responses=[success_response],
    )

    with mock.patch.dict(
            "os.environ",
        {
            EnvironmentVariables.AZURE_AUTHORITY_HOST: authority,
            EnvironmentVariables.AZURE_TENANT_ID: tenant,
            EnvironmentVariables.AZURE_FEDERATED_TOKEN_FILE:
            token_file.strpath,
        },
            clear=True,
    ):
        with pytest.raises(ValueError):
            ManagedIdentityCredential()

        credential = ManagedIdentityCredential(client_id=nondefault_client_id,
                                               transport=transport)
        token = credential.get_token(scope)
    assert token.token == access_token
def test_authorized_signup_username_already_exists(app, example_github, user):
    """Test authorized callback with sign-up."""
    example_email = '*****@*****.**'

    with app.test_client() as c:
        # User login with email 'info'
        with mock.patch('github3.login') as MockLogin:
            MockLogin.return_value = MockGh(email=example_email)

            # Ensure remote apps have been loaded (due to before first
            # request)
            resp = c.get(url_for('invenio_oauthclient.login',
                                 remote_app='github'))

            assert resp.status_code == 302

            mock_response(app.extensions['oauthlib.client'], 'github',
                          example_github)

            # User authorized the requests and is redirect back
            resp = c.get(
                url_for('invenio_oauthclient.authorized',
                        remote_app='github', code='test',
                        state=_get_state()))
            assert resp.status_code == 302
            assert resp.location == (
                'http://localhost' +
                url_for('invenio_oauthclient.signup', remote_app='github')
            )

            # User fills form to register
            resp = c.post(
                resp.headers['Location'],
                data={
                    'email': example_email,
                    'password': '******',
                    'profile.username': '******',
                    'profile.full_name': 'pluto',
                }
            )
            assert resp.status_code == 302

            # Assert database state (Sign-up complete)
            my_user = User.query.filter_by(email=example_email).one()
            remote = RemoteAccount.query.filter_by(user_id=my_user.id).one()
            RemoteToken.query.filter_by(id_remote_account=remote.id).one()
            assert my_user.active

            # Disconnect link
            # should not work, because it's the user's only means of login
            resp = c.get(
                url_for('invenio_oauthclient.disconnect', remote_app='github')
            )
            assert resp.status_code == 400

            my_user = User.query.filter_by(email=example_email).one()
            assert 1 == UserIdentity.query.filter_by(
                method='github', id_user=my_user.id,
                id='githubuser'
            ).count()

            # set a password for the user
            my_user.password = hash_password("1234")
            db.session.commit()

            # Disconnect again
            resp = c.get(
                url_for('invenio_oauthclient.disconnect', remote_app='github'))
            assert resp.status_code == 302

            my_user = User.query.filter_by(email=example_email).one()
            assert 0 == UserIdentity.query.filter_by(
                method='github', id_user=my_user.id,
                id='githubuser'
            ).count()
            assert RemoteAccount.query.filter_by(
                user_id=my_user.id).count() == 0
            assert RemoteToken.query.count() == 0
            assert User.query.count() == 2
def test_authorized_signup(userprofiles_app, example_orcid, orcid_bio):
    """Test authorized callback with sign-up."""
    app = userprofiles_app
    example_data, example_account_info = example_orcid
    example_email = '*****@*****.**'

    with app.test_client() as c:
        # Ensure remote apps have been loaded (due to before first
        # request)
        c.get(url_for('invenio_oauthclient.login', remote_app='orcid'))

        mock_response(app.extensions['oauthlib.client'], 'orcid', example_data)

        # User authorized the requests and is redirect back
        resp = c.get(
            url_for('invenio_oauthclient.authorized',
                    remote_app='orcid', code='test',
                    state=get_state('orcid')))
        assert resp.status_code == 302
        assert resp.location == (
            'http://localhost' +
            url_for('invenio_oauthclient.signup', remote_app='orcid')
        )

        # User load sign-up page.
        resp = c.get(url_for('invenio_oauthclient.signup', remote_app='orcid'))
        assert resp.status_code == 200

        account_info = session[token_session_key('orcid') + '_account_info']
        data = {
            'email': example_email,
            'password': '******',
            'profile.username': '******',
            'profile.full_name': account_info['user']['profile']['full_name'],
        }

        # Mock request to ORCID to get user bio.
        httpretty.enable()
        httpretty.register_uri(
            httpretty.GET,
            'http://orcid.org/{0}/orcid-bio'.format(example_data['orcid']),
            body=orcid_bio,
            content_type='application/orcid+json; qs=2;charset=UTF-8',
        )

        # User fills form to register
        resp = c.post(
            url_for('invenio_oauthclient.signup', remote_app='orcid'),
            data=data,
        )
        assert resp.status_code == 302
        httpretty.disable()

        # Assert database state (Sign-up complete)
        user = User.query.filter_by(email=example_email).one()
        UserIdentity.query.filter_by(
            method='orcid', id_user=user.id,
            id=example_data['orcid']
        ).one()
        # FIXME see contrib/orcid.py line 167
        assert user.profile.full_name == 'Josiah Carberry'
        #  assert user.given_names == 'Josiah'
        #  assert user.family_name == 'Carberry'
        # check that the user's email is not yet validated
        assert user.active
        # check that the validation email has been sent
        #  assert hasattr(locmem, 'outbox') and len(locmem.outbox) == 1

        # Disconnect link
        resp = c.get(
            url_for('invenio_oauthclient.disconnect', remote_app='orcid'))
        assert resp.status_code == 302

        # User exists
        user = User.query.filter_by(email=example_email).one()
        # UserIdentity removed.
        assert 0 == UserIdentity.query.filter_by(
            method='orcid', id_user=user.id,
            id=example_data['orcid']
        ).count()
        assert RemoteAccount.query.filter_by(user_id=user.id).count() == 0
        assert RemoteToken.query.count() == 0
def test_authorized_already_authenticated(models_fixture, example_orcid,
                                          orcid_bio):
    """Test authorized callback with sign-up."""
    app = models_fixture

    datastore = app.extensions['invenio-accounts'].datastore
    login_manager = app.login_manager

    example_data, example_account_info = example_orcid
    existing_email = '*****@*****.**'
    user = datastore.find_user(email=existing_email)

    @login_manager.user_loader
    def load_user(user_id):
        return user

    @app.route('/foo_login')
    def login():
        login_user(user)
        return 'Logged In'

    with app.test_client() as client:

        # make a fake login (using my login function)
        client.get('/foo_login', follow_redirects=True)

        # Ensure remote apps have been loaded (due to before first
        # request)
        client.get(url_for('invenio_oauthclient.login', remote_app='orcid'))

        # Mock access token request
        mock_response(app.extensions['oauthlib.client'], 'orcid', example_data)

        # Mock request to ORCID to get user bio.
        httpretty.enable()
        httpretty.register_uri(
            httpretty.GET,
            'https://pub.orcid.org/v1.2/{0}/orcid-bio'.format(
                example_data['orcid']),
            body=orcid_bio,
            content_type='application/orcid+json; qs=2;charset=UTF-8',
        )

        # User then goes to 'Linked accounts' and clicks 'Connect'
        resp = client.get(
            url_for('invenio_oauthclient.login', remote_app='orcid',
                    next='/someurl/')
        )
        assert resp.status_code == 302

        # User authorized the requests and is redirected back
        resp = client.get(
            url_for('invenio_oauthclient.authorized',
                    remote_app='orcid', code='test',
                    state=get_state('orcid')))
        httpretty.disable()

        # Assert database state (Sign-up complete)
        u = User.query.filter_by(email=existing_email).one()
        UserIdentity.query.filter_by(
            method='orcid', id_user=u.id,
            id=example_data['orcid']
        ).one()
        # FIXME see contrib/orcid.py line 167
        # assert u.given_names == 'Josiah'
        # assert u.family_name == 'Carberry'

        # Disconnect link
        resp = client.get(
            url_for('invenio_oauthclient.disconnect', remote_app='orcid'))
        assert resp.status_code == 302

        # User exists
        u = User.query.filter_by(email=existing_email).one()
        # UserIdentity removed.
        assert 0 == UserIdentity.query.filter_by(
            method='orcid', id_user=u.id,
            id=example_data['orcid']
        ).count()
def test_authorized_signup_valid_user(app, example_github):
    """Test authorized callback with sign-up."""
    example_email = '*****@*****.**'

    with app.test_client() as c:
        # User login with email 'info'
        with mock.patch('github3.login') as MockLogin:
            MockLogin.return_value = MockGh(email='*****@*****.**')

            # Ensure remote apps have been loaded (due to before first
            # request)
            resp = c.get(url_for('invenio_oauthclient.login',
                                 remote_app='github'))

            assert resp.status_code == 302

            mock_response(app.extensions['oauthlib.client'], 'github',
                          example_github)

            # User authorized the requests and is redirect back
            resp = c.get(
                url_for('invenio_oauthclient.authorized',
                        remote_app='github', code='test',
                        state=_get_state()))
            assert resp.status_code == 302
            assert resp.location == ('http://localhost/account/settings/' +
                                     'linkedaccounts/')

            # Assert database state (Sign-up complete)
            user = User.query.filter_by(email=example_email).one()
            remote = RemoteAccount.query.filter_by(user_id=user.id).one()
            RemoteToken.query.filter_by(id_remote_account=remote.id).one()
            assert user.active

            # Disconnect link
            resp = c.get(
                url_for('invenio_oauthclient.disconnect', remote_app='github'))
            assert resp.status_code == 302

            # User exists
            user = User.query.filter_by(email=example_email).one()
            assert 0 == UserIdentity.query.filter_by(
                method='orcid', id_user=user.id,
                id='githubuser'
            ).count()
            assert RemoteAccount.query.filter_by(user_id=user.id).count() == 0
            assert RemoteToken.query.count() == 0

        # User login with another email ('info2')
        with mock.patch('github3.login') as MockLogin:
            MockLogin.return_value = MockGh(email='*****@*****.**')

            # User authorized the requests and is redirect back
            resp = c.get(
                url_for('invenio_oauthclient.authorized',
                        remote_app='github', code='test',
                        state=_get_state()))
            assert resp.status_code == 302
            assert resp.location == (
                'http://localhost/' +
                'account/settings/linkedaccounts/'
            )

            # check that exist only one account
            user = User.query.filter_by(email=example_email).one()
            assert User.query.count() == 1
Example #15
0
 def send(request, **_):
     parsed = urlparse(request.url)
     tenant = parsed.path.split("/")[1]
     token = expected_token if tenant == expected_tenant else expected_token * 2
     return mock_response(json_payload=build_aad_response(access_token=token, refresh_token="**"))
Example #16
0
def test_account_setup(app_rest, example_cern, models_fixture):
    """Test account setup after login."""
    with app_rest.test_client() as c:
        ioc = app_rest.extensions['oauthlib.client']

        # Ensure remote apps have been loaded (due to before first request)
        resp = c.get(
            url_for('invenio_oauthclient.rest_login', remote_app='cern'))
        assert resp.status_code == 302

        example_response, example_token, example_account_info = example_cern

        mock_response(app_rest.extensions['oauthlib.client'], 'cern',
                      example_token)
        mock_remote_get(ioc, 'cern', example_response)

        resp = c.get(
            url_for('invenio_oauthclient.rest_authorized',
                    remote_app='cern',
                    code='test',
                    state=get_state('cern')))
        assert resp.status_code == 302
        expected_url_args = {
            "message": "Successfully authorized.",
            "code": 200,
        }
        check_response_redirect_url_args(resp, expected_url_args)
        assert len(g.identity.provides) == 7

    datastore = app_rest.extensions['invenio-accounts'].datastore
    user = datastore.find_user(email='*****@*****.**')
    user.password = hash_password("1234")
    assert user

    with app_rest.test_request_context():
        resp = disconnect_rest_handler(ioc.remote_apps['cern'])
        assert resp.status_code >= 300

        # simulate login (account_info fetch)
        g.oauth_logged_in_with_remote = ioc.remote_apps['cern']

        login_user(user)
        assert isinstance(g.identity, Identity)
        assert g.identity.provides == set([
            UserNeed(4),
            UserNeed('*****@*****.**'),
            RoleNeed('*****@*****.**'),
            RoleNeed('*****@*****.**'),
            RoleNeed('*****@*****.**'),
            RoleNeed('*****@*****.**'),
            RoleNeed('*****@*****.**'),
        ])

        logout_user()
        assert isinstance(g.identity, AnonymousIdentity)
        # NOTE: Wrong role, g.identity.provides = {Need(['id', 4])} read more
        # https://github.com/inveniosoftware/invenio-access/blob/e28e76d5361a29202b94d498f1968454c24c5c80/tests/test_loaders.py#L47
        assert len(g.identity.provides) == 1

        assert "cern_resource" not in session
        assert OAUTHCLIENT_CERN_SESSION_KEY not in session

        # Login again to test the disconnect handler
        g.oauth_logged_in_with_remote = ioc.remote_apps['cern']
        login_user(user)
        assert isinstance(g.identity, Identity)
        assert len(g.identity.provides) == 7

        disconnect_rest_handler(ioc.remote_apps['cern'])
 async def send(*_, **__):
     return mock_response(json_payload=build_aad_response(
         access_token="**"))
Example #18
0
def test_authenticate():
    client_id = "client-id"
    environment = "localhost"
    issuer = "https://" + environment
    tenant_id = "some-tenant"
    authority = issuer + "/" + tenant_id

    access_token = "***"
    scope = "scope"

    # mock AAD response with id token
    object_id = "object-id"
    home_tenant = "home-tenant-id"
    username = "******"
    id_token = build_id_token(aud=client_id,
                              iss=issuer,
                              object_id=object_id,
                              tenant_id=home_tenant,
                              username=username)
    auth_response = build_aad_response(uid=object_id,
                                       utid=home_tenant,
                                       access_token=access_token,
                                       refresh_token="**",
                                       id_token=id_token)

    transport = validating_transport(
        requests=[Request(url_substring=issuer)] * 3,
        responses=[get_discovery_response(authority)] * 2 +
        [mock_response(json_payload=auth_response)],
    )

    # mock local server fakes successful authentication by immediately returning a well-formed response
    oauth_state = "state"
    auth_code_response = {"code": "authorization-code", "state": [oauth_state]}
    server_class = Mock(return_value=Mock(
        wait_for_redirect=lambda: auth_code_response))

    with patch(InteractiveBrowserCredential.__module__ + ".uuid.uuid4",
               lambda: oauth_state):
        with patch(WEBBROWSER_OPEN, lambda _: True):
            credential = InteractiveBrowserCredential(
                _cache=TokenCache(),
                authority=environment,
                client_id=client_id,
                server_class=server_class,
                tenant_id=tenant_id,
                transport=transport,
            )
            record = credential.authenticate(scopes=(scope, ))

    assert record.authority == environment
    assert record.home_account_id == object_id + "." + home_tenant
    assert record.tenant_id == home_tenant
    assert record.username == username

    # credential should have a cached access token for the scope used in authenticate
    with patch(
            WEBBROWSER_OPEN,
            Mock(side_effect=Exception(
                "credential should authenticate silently"))):
        token = credential.get_token(scope)
    assert token.token == access_token
Example #19
0
async def test_same_username_different_tenants():
    """two cached accounts, same username, different tenants"""

    access_token_a = "access-token-a"
    access_token_b = "access-token-b"
    refresh_token_a = "refresh-token-a"
    refresh_token_b = "refresh-token-b"

    upn = "spam@eggs"
    tenant_a = "tenant-a"
    tenant_b = "tenant-b"
    account_a = get_account_event(username=upn,
                                  uid="another-guid",
                                  utid=tenant_a,
                                  refresh_token=refresh_token_a)
    account_b = get_account_event(username=upn,
                                  uid="more-guid",
                                  utid=tenant_b,
                                  refresh_token=refresh_token_b)
    cache = populated_cache(account_a, account_b)

    # with no tenant specified the credential can't select an identity
    transport = Mock(
        side_effect=Exception())  # (so it shouldn't use the network)
    credential = SharedTokenCacheCredential(username=upn,
                                            _cache=cache,
                                            transport=transport)
    with pytest.raises(CredentialUnavailableError) as ex:
        await credential.get_token("scope")

    assert ex.value.message.startswith(
        MULTIPLE_MATCHING_ACCOUNTS[:MULTIPLE_MATCHING_ACCOUNTS.index("{")])
    assert upn in ex.value.message

    # with tenant specified, the credential should auth the matching account
    scope = "scope"
    transport = async_validating_transport(
        requests=[
            Request(required_data={
                "refresh_token": refresh_token_a,
                "scope": scope
            })
        ],
        responses=[
            mock_response(json_payload=build_aad_response(
                access_token=access_token_a))
        ],
    )
    credential = SharedTokenCacheCredential(tenant_id=tenant_a,
                                            _cache=cache,
                                            transport=transport)
    token = await credential.get_token(scope)
    assert token.token == access_token_a

    transport = async_validating_transport(
        requests=[
            Request(required_data={
                "refresh_token": refresh_token_b,
                "scope": scope
            })
        ],
        responses=[
            mock_response(json_payload=build_aad_response(
                access_token=access_token_b))
        ],
    )
    credential = SharedTokenCacheCredential(tenant_id=tenant_b,
                                            _cache=cache,
                                            transport=transport)
    token = await credential.get_token(scope)
    assert token.token == access_token_b
 def mock_send(request, **kwargs):
     validate_url(request.url)
     return mock_response(json_payload={"token_type": "Bearer", "expires_in": 42, "access_token": "***"})
Example #21
0
def test_authorized_signup_valid_user(app, example_github):
    """Test authorized callback with sign-up."""
    example_email = '*****@*****.**'

    with app.test_client() as c:
        # User login with email 'info'
        with mock.patch('github3.login') as MockLogin:
            MockLogin.return_value = MockGh(email='*****@*****.**')

            # Ensure remote apps have been loaded (due to before first
            # request)
            resp = c.get(
                url_for('invenio_oauthclient.login', remote_app='github'))

            assert resp.status_code == 302

            mock_response(app.extensions['oauthlib.client'], 'github',
                          example_github)

            # User authorized the requests and is redirect back
            resp = c.get(
                url_for('invenio_oauthclient.authorized',
                        remote_app='github',
                        code='test',
                        state=_get_state()))
            assert resp.status_code == 302
            assert resp.location == ('http://localhost/account/settings/' +
                                     'linkedaccounts/')

            # Assert database state (Sign-up complete)
            user = User.query.filter_by(email=example_email).one()
            remote = RemoteAccount.query.filter_by(user_id=user.id).one()
            RemoteToken.query.filter_by(id_remote_account=remote.id).one()
            assert user.active

            # Disconnect link
            resp = c.get(
                url_for('invenio_oauthclient.disconnect', remote_app='github'))
            assert resp.status_code == 302

            # User exists
            user = User.query.filter_by(email=example_email).one()
            assert 0 == UserIdentity.query.filter_by(method='orcid',
                                                     id_user=user.id,
                                                     id='githubuser').count()
            assert RemoteAccount.query.filter_by(user_id=user.id).count() == 0
            assert RemoteToken.query.count() == 0

        # User login with another email ('info2')
        with mock.patch('github3.login') as MockLogin:
            MockLogin.return_value = MockGh(email='*****@*****.**')

            # User authorized the requests and is redirect back
            resp = c.get(
                url_for('invenio_oauthclient.authorized',
                        remote_app='github',
                        code='test',
                        state=_get_state()))
            assert resp.status_code == 302
            assert resp.location == ('http://localhost/' +
                                     'account/settings/linkedaccounts/')

            # check that exist only one account
            user = User.query.filter_by(email=example_email).one()
            assert User.query.count() == 1
Example #22
0
 def mock_send(request, **kwargs):
     token = expected_tokens[request.data["resource"]]
     return mock_response(json_payload=token)
Example #23
0
async def test_cloud_shell_user_assigned_identity():
    """Cloud Shell environment: only MSI_ENDPOINT set"""

    expected_token = "****"
    expires_on = 42
    client_id = "some-guid"
    endpoint = "http://localhost:42/token"
    scope = "scope"
    param_name, param_value = "foo", "bar"

    transport = async_validating_transport(
        requests=[
            Request(
                base_url=endpoint,
                method="POST",
                required_headers={
                    "Metadata": "true",
                    "User-Agent": USER_AGENT
                },
                required_data={
                    "client_id": client_id,
                    "resource": scope
                },
            ),
            Request(
                base_url=endpoint,
                method="POST",
                required_headers={
                    "Metadata": "true",
                    "User-Agent": USER_AGENT
                },
                required_data={
                    "resource": scope,
                    param_name: param_value
                },
            ),
        ],
        responses=[
            mock_response(
                json_payload={
                    "access_token": expected_token,
                    "expires_in": 0,
                    "expires_on": expires_on,
                    "not_before": int(time.time()),
                    "resource": scope,
                    "token_type": "Bearer",
                })
        ] * 2,
    )

    with mock.patch.dict(MANAGED_IDENTITY_ENVIRON,
                         {EnvironmentVariables.MSI_ENDPOINT: endpoint},
                         clear=True):
        credential = ManagedIdentityCredential(client_id=client_id,
                                               transport=transport)
        token = await credential.get_token(scope)
        assert token.token == expected_token
        assert token.expires_on == expires_on

        credential = ManagedIdentityCredential(
            transport=transport, identity_config={param_name: param_value})
        token = await credential.get_token(scope)
        assert token.token == expected_token
        assert token.expires_on == expires_on
def test_authorized_already_authenticated(app, models_fixture, example_orcid,
                                          orcid_bio):
    """Test authorized callback with sign-up."""
    datastore = app.extensions['invenio-accounts'].datastore
    login_manager = app.login_manager

    example_data, example_account_info = example_orcid
    existing_email = '*****@*****.**'
    user = datastore.find_user(email=existing_email)

    @login_manager.user_loader
    def load_user(user_id):
        return user

    @app.route('/foo_login')
    def login():
        login_user(user)
        return 'Logged In'

    with app.test_client() as client:

        # make a fake login (using my login function)
        client.get('/foo_login', follow_redirects=True)

        # Ensure remote apps have been loaded (due to before first
        # request)
        client.get(url_for('invenio_oauthclient.login', remote_app='orcid'))

        # Mock access token request
        mock_response(app.extensions['oauthlib.client'], 'orcid', example_data)

        # Mock request to ORCID to get user bio.
        httpretty.enable()
        httpretty.register_uri(
            httpretty.GET,
            'https://pub.orcid.org/v1.2/{0}/orcid-bio'.format(
                example_data['orcid']),
            body=orcid_bio,
            content_type='application/orcid+json; qs=2;charset=UTF-8',
        )

        # User then goes to 'Linked accounts' and clicks 'Connect'
        resp = client.get(
            url_for('invenio_oauthclient.login',
                    remote_app='orcid',
                    next='/someurl/'))
        assert resp.status_code == 302

        # User authorized the requests and is redirected back
        resp = client.get(
            url_for('invenio_oauthclient.authorized',
                    remote_app='orcid',
                    code='test',
                    state=get_state('orcid')))
        httpretty.disable()

        # Assert database state (Sign-up complete)
        u = User.query.filter_by(email=existing_email).one()
        UserIdentity.query.filter_by(method='orcid',
                                     id_user=u.id,
                                     id=example_data['orcid']).one()
        # FIXME see contrib/orcid.py line 167
        # assert u.given_names == 'Josiah'
        # assert u.family_name == 'Carberry'

        # Disconnect link
        resp = client.get(
            url_for('invenio_oauthclient.disconnect', remote_app='orcid'))
        assert resp.status_code == 302

        # User exists
        u = User.query.filter_by(email=existing_email).one()
        # UserIdentity removed.
        assert 0 == UserIdentity.query.filter_by(
            method='orcid', id_user=u.id, id=example_data['orcid']).count()
Example #25
0
async def test_app_service_2017_09_01():
    """test parsing of App Service MSI 2017-09-01's eccentric platform-dependent expires_on strings"""

    access_token = "****"
    expires_on = 42
    expected_token = AccessToken(access_token, expires_on)
    url = "http://localhost:42/token"
    secret = "expected-secret"
    scope = "scope"

    transport = async_validating_transport(
        requests=[
            Request(
                url,
                method="GET",
                required_headers={
                    "secret": secret,
                    "User-Agent": USER_AGENT
                },
                required_params={
                    "api-version": "2017-09-01",
                    "resource": scope
                },
            )
        ] * 2,
        responses=[
            mock_response(
                json_payload={
                    "access_token": access_token,
                    "expires_on": "01/01/1970 00:00:{} +00:00".format(
                        expires_on),  # linux format
                    "resource": scope,
                    "token_type": "Bearer",
                }),
            mock_response(
                json_payload={
                    "access_token": access_token,
                    "expires_on": "1/1/1970 12:00:{} AM +00:00".format(
                        expires_on),  # windows format
                    "resource": scope,
                    "token_type": "Bearer",
                }),
        ],
    )

    with mock.patch.dict(
            MANAGED_IDENTITY_ENVIRON,
        {
            EnvironmentVariables.MSI_ENDPOINT: url,
            EnvironmentVariables.MSI_SECRET: secret
        },
            clear=True,
    ):
        token = await ManagedIdentityCredential(transport=transport
                                                ).get_token(scope)
        assert token == expected_token
        assert token.expires_on == expires_on

        token = await ManagedIdentityCredential(transport=transport
                                                ).get_token(scope)
        assert token == expected_token
        assert token.expires_on == expires_on
def test_authorized_signup(app_with_userprofiles, example_orcid, orcid_bio):
    """Test authorized callback with sign-up."""
    app = app_with_userprofiles
    example_data, example_account_info = example_orcid
    example_email = '*****@*****.**'

    with app.test_client() as c:
        # Ensure remote apps have been loaded (due to before first
        # request)
        c.get(url_for('invenio_oauthclient.login', remote_app='orcid'))

        mock_response(app.extensions['oauthlib.client'], 'orcid', example_data)

        # User authorized the requests and is redirect back
        resp = c.get(
            url_for('invenio_oauthclient.authorized',
                    remote_app='orcid',
                    code='test',
                    state=get_state('orcid')))
        assert resp.status_code == 302
        assert resp.location == (
            'http://localhost' +
            url_for('invenio_oauthclient.signup', remote_app='orcid'))

        # User load sign-up page.
        resp = c.get(url_for('invenio_oauthclient.signup', remote_app='orcid'))
        assert resp.status_code == 200

        account_info = session[token_session_key('orcid') + '_account_info']
        data = {
            'email': example_email,
            'password': '******',
            'profile.username': '******',
            'profile.full_name': account_info['user']['profile']['full_name'],
        }

        # Mock request to ORCID to get user bio.
        httpretty.enable()
        httpretty.register_uri(
            httpretty.GET,
            'http://orcid.org/{0}/orcid-bio'.format(example_data['orcid']),
            body=orcid_bio,
            content_type='application/orcid+json; qs=2;charset=UTF-8',
        )

        # User fills form to register
        resp = c.post(
            url_for('invenio_oauthclient.signup', remote_app='orcid'),
            data=data,
        )
        assert resp.status_code == 302
        httpretty.disable()

        # Assert database state (Sign-up complete)
        user = User.query.filter_by(email=example_email).one()
        UserIdentity.query.filter_by(method='orcid',
                                     id_user=user.id,
                                     id=example_data['orcid']).one()
        # FIXME see contrib/orcid.py line 167
        assert user.profile.full_name == 'Josiah Carberry'
        #  assert user.given_names == 'Josiah'
        #  assert user.family_name == 'Carberry'
        # check that the user's email is not yet validated
        assert user.active
        # check that the validation email has been sent
        #  assert hasattr(locmem, 'outbox') and len(locmem.outbox) == 1

        # Disconnect link
        # should not work, because it's the user's only means of login
        resp = c.get(
            url_for('invenio_oauthclient.disconnect', remote_app='orcid'))
        assert resp.status_code == 400

        user = User.query.filter_by(email=example_email).one()
        assert 1 == UserIdentity.query.filter_by(
            method='orcid', id_user=user.id, id=example_data['orcid']).count()

        # set a password for the user
        user.password = hash_password("1234")
        db.session.commit()

        # Disconnect again
        resp = c.get(
            url_for('invenio_oauthclient.disconnect', remote_app='orcid'))
        assert resp.status_code == 302

        # User exists
        user = User.query.filter_by(email=example_email).one()
        # UserIdentity removed.
        assert 0 == UserIdentity.query.filter_by(
            method='orcid', id_user=user.id, id=example_data['orcid']).count()
        assert RemoteAccount.query.filter_by(user_id=user.id).count() == 0
        assert RemoteToken.query.count() == 0
Example #27
0
async def test_azure_ml():
    """Azure ML: MSI_ENDPOINT, MSI_SECRET set (like App Service 2017-09-01 but with a different response format)"""

    expected_token = AccessToken("****", int(time.time()) + 3600)
    url = "http://localhost:42/token"
    secret = "expected-secret"
    scope = "scope"
    client_id = "client"

    transport = async_validating_transport(
        requests=[
            Request(
                url,
                method="GET",
                required_headers={
                    "secret": secret,
                    "User-Agent": USER_AGENT
                },
                required_params={
                    "api-version": "2017-09-01",
                    "resource": scope
                },
            ),
            Request(
                url,
                method="GET",
                required_headers={
                    "secret": secret,
                    "User-Agent": USER_AGENT
                },
                required_params={
                    "api-version": "2017-09-01",
                    "resource": scope,
                    "clientid": client_id
                },
            ),
        ],
        responses=[
            mock_response(
                json_payload={
                    "access_token": expected_token.token,
                    "expires_in": 3600,
                    "expires_on": expected_token.expires_on,
                    "resource": scope,
                    "token_type": "Bearer",
                })
        ] * 2,
    )

    with mock.patch.dict(
            MANAGED_IDENTITY_ENVIRON,
        {
            EnvironmentVariables.MSI_ENDPOINT: url,
            EnvironmentVariables.MSI_SECRET: secret
        },
            clear=True,
    ):
        credential = ManagedIdentityCredential(transport=transport)
        token = await credential.get_token(scope)
        assert token.token == expected_token.token
        assert token.expires_on == expected_token.expires_on

        credential = ManagedIdentityCredential(transport=transport,
                                               client_id=client_id)
        token = await credential.get_token(scope)
        assert token.token == expected_token.token
        assert token.expires_on == expected_token.expires_on
def test_authorized_already_authenticated(app, models_fixture, example_globus):
    """Test authorized callback with sign-up."""
    datastore = app.extensions['invenio-accounts'].datastore
    login_manager = app.login_manager

    existing_email = '*****@*****.**'
    user = datastore.find_user(email=existing_email)

    @login_manager.user_loader
    def load_user(user_id):
        return user

    @app.route('/foo_login')
    def login():
        login_user(user)
        return 'Logged In'

    with app.test_client() as client:

        # make a fake login (using my login function)
        client.get('/foo_login', follow_redirects=True)
        # Ensure remote apps have been loaded (due to before first request)
        client.get(url_for('invenio_oauthclient.login', remote_app='globus'))

        ioc = app.extensions['oauthlib.client']
        example_info, example_token, example_account_id = example_globus
        mock_response(app.extensions['oauthlib.client'], 'globus',
                      example_token)
        example_info.update(example_account_id)
        oauth_resp = OAuthResponse(resp=None,
                                   content=json.dumps(example_info),
                                   content_type='application/json')
        mock_remote_get(ioc, 'globus', oauth_resp)

        # User then goes to 'Linked accounts' and clicks 'Connect'
        resp = client.get(
            url_for('invenio_oauthclient.login',
                    remote_app='globus',
                    next='/someurl/'))
        assert resp.status_code == 302

        # User authorized the requests and is redirected back
        resp = client.get(
            url_for('invenio_oauthclient.authorized',
                    remote_app='globus',
                    code='test',
                    state=_get_state()))

        # Assert database state (Sign-up complete)
        u = User.query.filter_by(email=existing_email).one()
        remote = RemoteAccount.query.filter_by(user_id=u.id).one()
        RemoteToken.query.filter_by(id_remote_account=remote.id).one()

        # Disconnect link
        resp = client.get(
            url_for('invenio_oauthclient.disconnect', remote_app='globus'))
        assert resp.status_code == 302

        # User exists
        u = User.query.filter_by(email=existing_email).one()
        assert 0 == UserIdentity.query.filter_by(method='globus',
                                                 id_user=u.id,
                                                 id='globususer').count()
        assert RemoteAccount.query.filter_by(user_id=u.id).count() == 0
        assert RemoteToken.query.count() == 0
def test_interactive_credential(mock_open):
    mock_open.side_effect = _validate_auth_request_url
    oauth_state = "state"
    client_id = "client-id"
    expected_refresh_token = "refresh-token"
    expected_token = "access-token"
    expires_in = 3600
    authority = "authority"
    tenant_id = "tenant_id"
    endpoint = "https://{}/{}".format(authority, tenant_id)

    discovery_response = get_discovery_response(endpoint=endpoint)
    transport = validating_transport(
        requests=[Request(url_substring=endpoint)] * 3
        + [
            Request(
                authority=authority, url_substring=endpoint, required_data={"refresh_token": expected_refresh_token}
            )
        ],
        responses=[
            discovery_response,  # instance discovery
            discovery_response,  # tenant discovery
            mock_response(
                json_payload=build_aad_response(
                    access_token=expected_token,
                    expires_in=expires_in,
                    refresh_token=expected_refresh_token,
                    uid="uid",
                    utid=tenant_id,
                    id_token=build_id_token(aud=client_id, object_id="uid", tenant_id=tenant_id, iss=endpoint),
                    token_type="Bearer",
                )
            ),
            mock_response(
                json_payload=build_aad_response(access_token=expected_token, expires_in=expires_in, token_type="Bearer")
            ),
        ],
    )

    # mock local server fakes successful authentication by immediately returning a well-formed response
    auth_code_response = {"code": "authorization-code", "state": [oauth_state]}
    server_class = Mock(return_value=Mock(wait_for_redirect=lambda: auth_code_response))

    credential = InteractiveBrowserCredential(
        authority=authority,
        tenant_id=tenant_id,
        client_id=client_id,
        server_class=server_class,
        transport=transport,
        instance_discovery=False,
        validate_authority=False,
        _cache=TokenCache(),
    )

    # The credential's auth code request includes a uuid which must be included in the redirect. Patching to
    # set the uuid requires less code here than a proper mock server.
    with patch("azure.identity._credentials.browser.uuid.uuid4", lambda: oauth_state):
        token = credential.get_token("scope")
    assert token.token == expected_token
    assert mock_open.call_count == 1

    # token should be cached, get_token shouldn't prompt again
    token = credential.get_token("scope")
    assert token.token == expected_token
    assert mock_open.call_count == 1

    # As of MSAL 1.0.0, applications build a new client every time they redeem a refresh token.
    # Here we patch the private method they use for the sake of test coverage.
    # TODO: this will probably break when this MSAL behavior changes
    app = credential._get_app()
    app._build_client = lambda *_: app.client  # pylint:disable=protected-access
    now = time.time()

    # expired access token -> credential should use refresh token instead of prompting again
    with patch("time.time", lambda: now + expires_in):
        token = credential.get_token("scope")
    assert token.token == expected_token
    assert mock_open.call_count == 1

    # ensure all expected requests were sent
    assert transport.send.call_count == 4
def test_authorized_signup_valid_user(app, example_globus):
    """Test authorized callback with sign-up."""

    with app.test_client() as c:
        # User login with email 'info'
        ioc = app.extensions['oauthlib.client']

        # Ensure remote apps have been loaded (due to before first request)
        resp = c.get(url_for('invenio_oauthclient.login', remote_app='globus'))
        assert resp.status_code == 302

        example_info, example_token, example_account_id = example_globus
        mock_response(app.extensions['oauthlib.client'], 'globus',
                      example_token)
        example_info.update(example_account_id)
        oauth_resp = OAuthResponse(resp=None,
                                   content=json.dumps(example_info),
                                   content_type='application/json')
        mock_remote_get(ioc, 'globus', oauth_resp)

        # User authorized the requests and is redirect back
        resp = c.get(
            url_for('invenio_oauthclient.authorized',
                    remote_app='globus',
                    code='test',
                    state=_get_state()))
        assert resp.status_code == 302
        assert resp.location == ('http://localhost/account/settings/' +
                                 'linkedaccounts/')

        # Assert database state (Sign-up complete)
        user = User.query.filter_by(email='*****@*****.**').one()
        remote = RemoteAccount.query.filter_by(user_id=user.id).one()
        RemoteToken.query.filter_by(id_remote_account=remote.id).one()
        assert user.active

        # Disconnect link
        # should not work, because it's the user's only means of login
        resp = c.get(
            url_for('invenio_oauthclient.disconnect', remote_app='globus'))
        assert resp.status_code == 400

        user = User.query.filter_by(email='*****@*****.**').one()
        assert 1 == UserIdentity.query.filter_by(
            method='globus',
            id_user=user.id,
        ).count()

        # set a password for the user
        user.password = hash_password("1234")
        db.session.commit()

        # Disconnect again
        resp = c.get(
            url_for('invenio_oauthclient.disconnect', remote_app='globus'))
        assert resp.status_code == 302

        # User exists
        user = User.query.filter_by(email='*****@*****.**').one()
        assert 0 == UserIdentity.query.filter_by(
            method='globus',
            id_user=user.id,
        ).count()
        assert RemoteAccount.query.filter_by(user_id=user.id).count() == 0
        assert RemoteToken.query.count() == 0

        # User authorized the requests and is redirect back
        resp = c.get(
            url_for('invenio_oauthclient.authorized',
                    remote_app='globus',
                    code='test',
                    state=_get_state()))
        assert resp.status_code == 302
        assert resp.location == ('http://localhost/' +
                                 'account/settings/linkedaccounts/')

        # check that exist only one account
        user = User.query.filter_by(email='*****@*****.**').one()
        assert User.query.count() == 1
Example #31
0
 def send(request, **_):
     parsed = urlparse(request.url)
     tenant = parsed.path.split("/")[1]
     assert tenant in (first_tenant, second_tenant), 'unexpected tenant "{}"'.format(tenant)
     token = first_token if tenant == first_tenant else second_token
     return mock_response(json_payload=build_aad_response(access_token=token, refresh_token="**"))
async def test_default_credential_authority():
    authority = "authority.com"
    expected_access_token = "***"
    response = mock_response(
        json_payload={
            "access_token": expected_access_token,
            "expires_in": 0,
            "expires_on": 42,
            "not_before": 0,
            "resource": "scope",
            "token_type": "Bearer",
        })

    async def exercise_credentials(authority_kwarg, expected_authority=None):
        expected_authority = expected_authority or authority_kwarg

        async def send(request, **_):
            url = urlparse(request.url)
            assert url.scheme == "https", "Unexpected scheme '{}'".format(
                url.scheme)
            assert url.netloc == expected_authority, "Expected authority '{}', actual was '{}'".format(
                expected_authority, url.netloc)
            return response

        # environment credential configured with client secret should respect authority
        environment = {
            EnvironmentVariables.AZURE_CLIENT_ID: "client_id",
            EnvironmentVariables.AZURE_CLIENT_SECRET: "secret",
            EnvironmentVariables.AZURE_TENANT_ID: "tenant_id",
        }
        with patch("os.environ", environment):
            transport = Mock(send=send)
            if authority_kwarg:
                credential = DefaultAzureCredential(authority=authority_kwarg,
                                                    transport=transport)
            else:
                credential = DefaultAzureCredential(transport=transport)
            access_token, _ = await credential.get_token("scope")
            assert access_token == expected_access_token

        # managed identity credential should ignore authority
        with patch("os.environ",
                   {EnvironmentVariables.MSI_ENDPOINT: "https://some.url"}):
            transport = Mock(send=asyncio.coroutine(lambda *_, **__: response))
            if authority_kwarg:
                credential = DefaultAzureCredential(authority=authority_kwarg,
                                                    transport=transport)
            else:
                credential = DefaultAzureCredential(transport=transport)
            access_token, _ = await credential.get_token("scope")
            assert access_token == expected_access_token

        # shared cache credential should respect authority
        upn = os.environ.get(EnvironmentVariables.AZURE_USERNAME,
                             "spam@eggs")  # preferring environment values to
        tenant = os.environ.get(EnvironmentVariables.AZURE_TENANT_ID,
                                "tenant")  # prevent failure during live runs
        account = get_account_event(username=upn,
                                    uid="guid",
                                    utid=tenant,
                                    authority=authority_kwarg)
        cache = populated_cache(account)
        with patch.object(SharedTokenCacheCredential, "supported"):
            credential = DefaultAzureCredential(_cache=cache,
                                                authority=authority_kwarg,
                                                transport=Mock(send=send))
        access_token, _ = await credential.get_token("scope")
        assert access_token == expected_access_token

    # all credentials not representing managed identities should use a specified authority or default to public cloud
    await exercise_credentials("authority.com")
    await exercise_credentials(None, KnownAuthorities.AZURE_PUBLIC_CLOUD)
Example #33
0
def test_auth_code_credential():
    client_id = "client id"
    tenant_id = "tenant"
    expected_code = "auth code"
    redirect_uri = "https://localhost"
    expected_access_token = "access"
    expected_refresh_token = "refresh"
    expected_scope = "scope"

    auth_response = build_aad_response(access_token=expected_access_token, refresh_token=expected_refresh_token)
    transport = validating_transport(
        requests=[
            Request(  # first call should redeem the auth code
                url_substring=tenant_id,
                required_data={
                    "client_id": client_id,
                    "code": expected_code,
                    "grant_type": "authorization_code",
                    "redirect_uri": redirect_uri,
                    "scope": expected_scope,
                },
            ),
            Request(  # third call should redeem the refresh token
                url_substring=tenant_id,
                required_data={
                    "client_id": client_id,
                    "grant_type": "refresh_token",
                    "refresh_token": expected_refresh_token,
                    "scope": expected_scope,
                },
            ),
        ],
        responses=[mock_response(json_payload=auth_response)] * 2,
    )
    cache = msal.TokenCache()

    credential = AuthorizationCodeCredential(
        client_id=client_id,
        tenant_id=tenant_id,
        authorization_code=expected_code,
        redirect_uri=redirect_uri,
        transport=transport,
        cache=cache,
    )

    # first call should redeem the auth code
    token = credential.get_token(expected_scope)
    assert token.token == expected_access_token
    assert transport.send.call_count == 1

    # no auth code -> credential should return cached token
    token = credential.get_token(expected_scope)
    assert token.token == expected_access_token
    assert transport.send.call_count == 1

    # no auth code, no cached token -> credential should redeem refresh token
    cached_access_token = cache.find(cache.CredentialType.ACCESS_TOKEN)[0]
    cache.remove_at(cached_access_token)
    token = credential.get_token(expected_scope)
    assert token.token == expected_access_token
    assert transport.send.call_count == 2
 async def send(request, **_):
     assert request.data["refresh_token"] == invalid_token
     return mock_response(json_payload={"error": "invalid_grant"},
                          status_code=400)
async def test_same_tenant_different_usernames():
    """two cached accounts, same tenant, different usernames"""

    access_token_a = "access-token-a"
    access_token_b = "access-token-b"
    refresh_token_a = "refresh-token-a"
    refresh_token_b = "refresh-token-b"

    upn_a = "spam@eggs"
    upn_b = "eggs@spam"
    tenant_id = "the-tenant"
    account_a = get_account_event(username=upn_a,
                                  uid="another-guid",
                                  utid=tenant_id,
                                  refresh_token=refresh_token_a)
    account_b = get_account_event(username=upn_b,
                                  uid="more-guid",
                                  utid=tenant_id,
                                  refresh_token=refresh_token_b)
    cache = populated_cache(account_a, account_b)

    # with no username specified the credential can't select an identity
    transport = Mock(
        side_effect=Exception())  # (so it shouldn't use the network)
    credential = SharedTokenCacheCredential(tenant_id=tenant_id,
                                            _cache=cache,
                                            transport=transport)
    with pytest.raises(ClientAuthenticationError) as ex:
        await credential.get_token("scope")
    # error message should indicate multiple matching accounts, and list discovered accounts
    assert ex.value.message.startswith(
        MULTIPLE_MATCHING_ACCOUNTS[:MULTIPLE_MATCHING_ACCOUNTS.index("{")])
    discovered_accounts = ex.value.message.splitlines()[-1]
    assert discovered_accounts.count(tenant_id) == 2
    assert upn_a in discovered_accounts and upn_b in discovered_accounts

    # with a username specified, the credential should auth the matching account
    scope = "scope"
    transport = async_validating_transport(
        requests=[
            Request(required_data={
                "refresh_token": refresh_token_b,
                "scope": scope
            })
        ],
        responses=[
            mock_response(json_payload=build_aad_response(
                access_token=access_token_a))
        ],
    )
    credential = SharedTokenCacheCredential(username=upn_b,
                                            _cache=cache,
                                            transport=transport)
    token = await credential.get_token(scope)
    assert token.token == access_token_a

    transport = async_validating_transport(
        requests=[
            Request(required_data={
                "refresh_token": refresh_token_a,
                "scope": scope
            })
        ],
        responses=[
            mock_response(json_payload=build_aad_response(
                access_token=access_token_a))
        ],
    )
    credential = SharedTokenCacheCredential(username=upn_a,
                                            _cache=cache,
                                            transport=transport)
    token = await credential.get_token(scope)
    assert token.token == access_token_a
def test_authorized_signup_username_already_exists(app, example_github, user):
    """Test authorized callback with sign-up."""
    example_email = '*****@*****.**'

    with app.test_client() as c:
        # User login with email 'info'
        with mock.patch('github3.login') as MockLogin:
            MockLogin.return_value = MockGh(email=example_email)

            # Ensure remote apps have been loaded (due to before first
            # request)
            resp = c.get(url_for('invenio_oauthclient.login',
                                 remote_app='github'))

            assert resp.status_code == 302

            mock_response(app.extensions['oauthlib.client'], 'github',
                          example_github)

            # User authorized the requests and is redirect back
            resp = c.get(
                url_for('invenio_oauthclient.authorized',
                        remote_app='github', code='test',
                        state=_get_state()))
            assert resp.status_code == 302
            assert resp.location == (
                'http://localhost' +
                url_for('invenio_oauthclient.signup', remote_app='github')
            )

            # User fills form to register
            resp = c.post(
                resp.headers['Location'],
                data={
                    'email': example_email,
                    'password': '******',
                    'profile.username': '******',
                    'profile.full_name': 'pluto',
                }
            )
            assert resp.status_code == 302

            # Assert database state (Sign-up complete)
            my_user = User.query.filter_by(email=example_email).one()
            remote = RemoteAccount.query.filter_by(user_id=my_user.id).one()
            RemoteToken.query.filter_by(id_remote_account=remote.id).one()
            assert my_user.active

            # Disconnect link
            resp = c.get(
                url_for('invenio_oauthclient.disconnect', remote_app='github'))
            assert resp.status_code == 302

            # User exists
            my_user = User.query.filter_by(email=example_email).one()
            assert 0 == UserIdentity.query.filter_by(
                method='orcid', id_user=my_user.id,
                id='githubuser'
            ).count()
            assert RemoteAccount.query.filter_by(
                user_id=my_user.id).count() == 0
            assert RemoteToken.query.count() == 0
            assert User.query.count() == 2