def fxa_login_url(config, state, next_path=None, action=None, force_two_factor=False, request=None, id_token=None): if next_path and _is_safe_url(next_path, request): state += ':' + force_text( urlsafe_b64encode(next_path.encode('utf-8'))).rstrip('=') query = { 'client_id': config['client_id'], 'scope': 'profile openid', 'state': state, } if action is not None: query['action'] = action if force_two_factor is True: # Specifying AAL2 will require the token to have an authentication # assurance level >= 2 which corresponds to requiring 2FA. query['acr_values'] = 'AAL2' # Requesting 'prompt=none' during authorization, together with passing # a valid id token in 'id_token_hint', allows the user to not have to # re-authenticate with FxA if they still have a valid session (which # they should here: they went through FxA, back to AMO, and now we're # redirecting them to FxA because we want them to have 2FA enabled). if id_token: query['prompt'] = 'none' query['id_token_hint'] = id_token if use_fake_fxa(): base_url = reverse('fake-fxa-authorization') else: base_url = '{host}/authorization'.format(host=settings.FXA_OAUTH_HOST) return '{base_url}?{query}'.format( base_url=base_url, query=urlencode(query))
def check_and_update_fxa_access_token(request): """This function checks access_token_expiry time in `request.session` and refreshes the access_token with the refresh token. IdentificationError from `get_fxa_token` will be raised if there is a problem refreshing, and `request.session` is updated with the new access_token_expiry time otherwise.""" if (not use_fake_fxa() and settings.VERIFY_FXA_ACCESS_TOKEN and (request.session.get('fxa_access_token_expiry') or 0) < time.time()): if not request.session.get('fxa_refresh_token'): raise IdentificationError( 'Could not get access token because refresh token missing from session' ) config_name = (request.session['fxa_config_name'] if request.session.get('fxa_config_name') in settings.ALLOWED_FXA_CONFIGS else settings.DEFAULT_FXA_CONFIG_NAME) # This will raise IdentificationError if there is a problem token_data = get_fxa_token( refresh_token=request.session.get('fxa_refresh_token'), config=settings.FXA_CONFIG[config_name], ) request.session['fxa_access_token_expiry'] = token_data[ 'access_token_expiry']
def fake_fxa_authorization(request): """Fake authentication page to bypass FxA in local development envs.""" if not use_fake_fxa(): raise Http404() interesting_accounts = UserProfile.objects.exclude(groups=None).exclude( deleted=True)[:25] return render(request, 'amo/fake_fxa_authorization.html', {'interesting_accounts': interesting_accounts})
def create_session(self, user, **overrides): """ Creates a session in the database for this user and returns the session key. """ request = HttpRequest() request.user = user # this is pretty much what django.contrib.auth.login does to initialize session fxa_details = ({ 'fxa_access_token_expiry': time.time() + 1000 } if not use_fake_fxa() else {}) initialize_session( request, { auth.SESSION_KEY: user._meta.pk.value_to_string(user), auth.BACKEND_SESSION_KEY: settings.AUTHENTICATION_BACKENDS[0], auth.HASH_SESSION_KEY: user.get_session_auth_hash(), **fxa_details, **overrides, }, ) return request.session.session_key
def inner(self, request): fxa_config = self.get_fxa_config(request) if request.method == 'GET': data = request.query_params else: data = request.data state_parts = data.get('state', '').split(':', 1) state = state_parts[0] next_path = parse_next_path(state_parts, request) if not data.get('code'): log.info('No code provided.') return render_error(request, ERROR_NO_CODE, next_path=next_path, format=format) elif (not request.session.get('fxa_state') or request.session['fxa_state'] != state): log.info( 'State mismatch. URL: {url} Session: {session}'.format( url=data.get('state'), session=request.session.get('fxa_state'), )) return render_error(request, ERROR_STATE_MISMATCH, next_path=next_path, format=format) elif request.user.is_authenticated: response = render_error(request, ERROR_AUTHENTICATED, next_path=next_path, format=format) # If the api token cookie is missing but we're still # authenticated using the session, add it back. if API_TOKEN_COOKIE not in request.COOKIES: log.info( 'User %s was already authenticated but did not ' 'have an API token cookie, adding one.', request.user.pk, ) response = add_api_token_to_response( response, request.user) return response try: if use_fake_fxa() and 'fake_fxa_email' in data: # Bypassing real authentication, we take the email provided # and generate a random fxa id. identity = { 'email': data['fake_fxa_email'], 'uid': 'fake_fxa_id-%s' % force_text(binascii.b2a_hex(os.urandom(16))), } id_token = identity['email'] else: identity, id_token = verify.fxa_identify(data['code'], config=fxa_config) except verify.IdentificationError: log.info('Profile not found. Code: {}'.format(data['code'])) return render_error(request, ERROR_NO_PROFILE, next_path=next_path, format=format) else: user = find_user(identity) # We can't use waffle.flag_is_active() wrapper, because # request.user isn't populated at this point (and we don't want # it to be). flag = waffle.get_waffle_flag_model().get( '2fa-enforcement-for-developers-and-special-users') enforce_2fa_for_developers_and_special_users = flag.is_active( request) or (flag.pk and flag.is_active_for_user(user)) if (user and not identity.get('twoFactorAuthentication') and enforce_2fa_for_developers_and_special_users and (user.is_addon_developer or user.groups_list)): # https://github.com/mozilla/addons/issues/732 # The user is an add-on developer (with other types of # add-ons than just themes) or part of any group (so they # are special in some way, may be an admin or a reviewer), # but hasn't logged in with a second factor. Immediately # redirect them to start the FxA flow again, this time # requesting 2FA to be present - they should be # automatically logged in FxA with the existing token, and # should be prompted to create the second factor before # coming back to AMO. log.info('Redirecting user %s to enforce 2FA', user) return HttpResponseRedirect( fxa_login_url( config=fxa_config, state=request.session['fxa_state'], next_path=next_path, action='signin', force_two_factor=True, request=request, id_token=id_token, )) return fn(self, request, user=user, identity=identity, next_path=next_path)
def fake_fxa_authorization(request): """Fake authentication page to bypass FxA in local development envs.""" if not use_fake_fxa(): raise Http404() return render(request, 'amo/fake_fxa_authorization.html')