예제 #1
0
def authorization_response():
    # parse authentication response
    query_string = request.query_string.decode('utf-8')
    current_app.logger.debug('query_string: {!s}'.format(query_string))
    authn_resp = current_app.oidc_client.parse_response(AuthorizationResponse,
                                                        info=query_string,
                                                        sformat='urlencoded')
    current_app.logger.debug(
        'Authorization response received: {!s}'.format(authn_resp))

    if authn_resp.get('error'):
        current_app.logger.error(
            'AuthorizationError {!s} - {!s} ({!s})'.format(
                request.host, authn_resp['error'],
                authn_resp.get('error_message'), authn_resp.get('error_uri')))
        return make_response('OK', 200)

    user_oidc_state = authn_resp['state']
    proofing_state = current_app.proofing_statedb.get_state_by_oidc_state(
        user_oidc_state)
    if not proofing_state:
        msg = 'The \'state\' parameter ({!s}) does not match a user state.'.format(
            user_oidc_state)
        current_app.logger.error(msg)
        return make_response('OK', 200)
    current_app.logger.debug('Proofing state {!s} for user {!s} found'.format(
        proofing_state.state, proofing_state.eppn))

    # Check if the token from the QR code matches the token we created when making the auth request
    authorization_header = request.headers.get('Authorization')
    if authorization_header != 'Bearer {}'.format(proofing_state.token):
        current_app.logger.error(
            'The authorization token ({!s}) did not match the expected'.format(
                authorization_header))
        return make_response('FORBIDDEN', 403)

    # TODO: We should save the auth response code to the proofing state to be able to continue a failed attempt
    # do token request
    args = {
        'code':
        authn_resp['code'],
        'redirect_uri':
        url_for('oidc_proofing.authorization_response', _external=True)
    }
    current_app.logger.debug('Trying to do token request: {!s}'.format(args))
    # TODO: What and where should be save from the token response
    token_resp = current_app.oidc_client.do_access_token_request(
        scope='openid',
        state=authn_resp['state'],
        request_args=args,
        authn_method='client_secret_basic')
    current_app.logger.debug(
        'token response received: {!s}'.format(token_resp))
    id_token = token_resp['id_token']
    if id_token['nonce'] != proofing_state.nonce:
        current_app.logger.error(
            'The \'nonce\' parameter does not match for user {!s}.'.format(
                proofing_state.eppn))
        return make_response('OK', 200)

    # do userinfo request
    current_app.logger.debug('Trying to do userinfo request:')
    # TODO: Do we need to save anything else from the userinfo response
    userinfo = current_app.oidc_client.do_user_info_request(
        method=current_app.config['USERINFO_ENDPOINT_METHOD'],
        state=authn_resp['state'])
    current_app.logger.debug('userinfo received: {!s}'.format(userinfo))
    if userinfo['sub'] != id_token['sub']:
        current_app.logger.error(
            'The \'sub\' of userinfo does not match \'sub\' of ID Token for user {!s}.'
            .format(proofing_state.eppn))
        return make_response('OK', 200)

    # Check proofed nin against self proclaimed OidcProofingState.nin.number
    number = userinfo['identity']
    if proofing_state.nin.number == number:
        nin = Nin(data=proofing_state.nin.to_dict())
        nin.verified = True

        # TODO: Break out in parts to be able to continue the proofing process after a successful authorization response
        # TODO: even if the token request, userinfo request or something internal fails
        am_user = current_app.central_userdb.get_user_by_eppn(
            proofing_state.eppn)
        user = ProofingUser(data=am_user.to_dict())

        # Check if the user has more than one verified nin
        if user.nins.primary is None:
            # No primary NIN found, make the only verified NIN primary
            nin.is_primary = True
        user.nins.add(nin)

        # XXX: Send proofing data to some kind of proofing log

        # User from central db is as up to date as it can be no need to check for modified time
        user.modified_ts = True
        # Save user to private db
        current_app.proofing_userdb.save(user, check_sync=False)

        # TODO: Need to decide where to "steal" NIN if multiple users have the NIN verified
        # Ask am to sync user to central db
        try:
            current_app.logger.info('Request sync for user {!s}'.format(user))
            result = current_app.am_relay.request_user_sync(user)
            current_app.logger.info('Sync result for user {!s}: {!s}'.format(
                user, result))
        except Exception as e:
            current_app.logger.error(
                'Sync request failed for user {!s}'.format(user))
            current_app.logger.error('Exception: {!s}'.format(e))
            # TODO: Need to able to retry this
            return make_response('OK', 200)

        # TODO: Remove saving of proof
        # Save proof for demo purposes
        proof_data = {
            'eduPersonPrincipalName': proofing_state.eppn,
            'authn_resp': authn_resp.to_dict(),
            'token_resp': token_resp.to_dict(),
            'userinfo': userinfo.to_dict()
        }
        current_app.proofdb.save(Proof(data=proof_data))

    # Remove users proofing state
    current_app.proofing_statedb.remove_state(proofing_state)
    return make_response('OK', 200)
예제 #2
0
def verify_code(user, verification_code):
    user = ProofingUser(data=user.to_dict())
    current_app.logger.info('Verifying code for user {!r}'.format(user))
    proofing_state = current_app.proofing_statedb.get_state_by_eppn(
        user.eppn, raise_on_missing=False)

    if not proofing_state:
        return {'_status': 'error', 'message': 'No proofing state found'}

    # Check if provided code matches the one in the letter
    if not verification_code == proofing_state.nin.verification_code:
        current_app.logger.error(
            'Verification code for user {!r} does not match'.format(user))
        # TODO: Throttling to discourage an adversary to try brute force
        return {'_status': 'error', 'message': 'Wrong code'}

    # Update proofing state to use to create nin element
    proofing_state.nin.is_verified = True
    proofing_state.nin.verified_by = 'eduid-idproofing-letter'
    proofing_state.nin.verified_ts = True
    nin = Nin(data=proofing_state.nin.to_dict())

    # Save user to private db
    if user.nins.primary is None:  # No primary NIN found, make the only verified NIN primary
        nin.is_primary = True
    user.nins.add(nin)

    # XXX: Do not add letter_proofing_data after we update to new db models in central user db
    letter_proofing_data = proofing_state.nin.to_dict()
    letter_proofing_data[
        'official_address'] = proofing_state.proofing_letter.address
    letter_proofing_data[
        'transaction_id'] = proofing_state.proofing_letter.transaction_id
    user.add_letter_proofing_data(letter_proofing_data)

    # User from central db is as up to date as it can be no need to check for modified time
    user.modified_ts = True
    current_app.proofing_userdb.save(user, check_sync=False)

    # TODO: Need to decide where to "steal" NIN if multiple users have the NIN verified
    # Ask am to sync user to central db
    try:
        # XXX: Send proofing data to some kind of proofing log
        current_app.logger.info('Request sync for user {!s}'.format(user))
        result = current_app.am_relay.request_user_sync(user)
        current_app.logger.info('Sync result for user {!s}: {!s}'.format(
            user, result))
    except Exception as e:
        current_app.logger.error(
            'Sync request failed for user {!s}'.format(user))
        current_app.logger.error('Exception: {!s}'.format(e))
        # XXX: Probably not str(e) as message?
        return {'_status': 'error', 'message': 'Sync request failed for user'}

    # XXX: Remove dumping data to log
    current_app.logger.info('Logging data for user: {!r}'.format(user))
    current_app.logger.info(
        json.dumps(
            schemas.LetterProofingDataSchema().dump(letter_proofing_data)))
    current_app.logger.info('End data')

    current_app.logger.info('Verified code for user {!r}'.format(user))
    # Remove proofing state
    current_app.proofing_statedb.remove_document(
        {'eduPersonPrincipalName': proofing_state.eppn})
    return {'success': True}