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)
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}