def verify_web_api(req): try: client_api_key = _extract_api_key(req) if not client_api_key: raise ValueError('Missing authorization credentials') app_id = request.view_args['app_id'] (api_key, ips, ops) = (db.session.query( Apps.api_key, Apps.allowed_ips, Apps.options).filter_by(_id=app_id, _deleted=0).one_or_none()) if client_api_key != api_key: raise ValueError('API key does not match') if ips: allowed_ips = ips.split('|') remote_ip = get_remote_ip(req) if remote_ip not in allowed_ips and remote_ip != '127.0.0.1': logger.info('IP is not allowed', ip=remote_ip, whitelist=allowed_ips) raise PermissionError() app = Apps() app._id = app_id app.is_authenticated = True app.options = ops return app except PermissionError: abort(403, 'Your IP is not allowed to access this API') except Exception as e: logger.debug('API authorization failed. ' + repr(e)) return None
def _make_web_authorize_success_response(self, token: Tokens): parent_resp = super()._make_web_authorize_success_response(token) amz_pay_enabled = self.channel.option_enabled('amazon_pay') if not amz_pay_enabled or self.log.intent != AuthLogs.INTENT_PAY_WITH_AMAZON: return parent_resp resp = make_response(parent_resp) cookie_object = { "access_token": token.access_token, "max_age": 3300, "expiration_date": unix_time_millis(token.expires_at), "client_id": self.channel.client_id, "scope": self._perms_for_pay() } domain = self.session.lpwa_domain or self.extract_domain_for_cookie( url=self.log.callback_uri) resp.set_cookie(key='amazon_Login_state_cache', value=up.quote(json.dumps(cookie_object), safe=''), domain=domain, expires=None, max_age=None) resp.set_cookie(key='amazon_Login_accessToken', value=token.access_token, domain=domain, expires=None, max_age=3300) logger.debug('Set cookie for amazon pay', domain=domain or 'localhost') return resp
def _parse_oauth_state(self, state: str) -> Tuple[AuthLogs, OAuthSessionParams]: try: log_id, args = jwt_token_helper.decode(token=state) log: Optional[AuthLogs] = AuthLogs.query.filter_by( _id=log_id).one_or_none() if not log or log.nonce != args.get('_nonce'): logger.debug('Invalid OAuth state or nonce does not match') raise BadRequestError('Invalid OAuth state') if log.status != AuthLogs.STATUS_UNKNOWN: logger.debug( 'Validate OAuth state failed. Illegal auth log status.', status=log.status, expected=AuthLogs.STATUS_UNKNOWN) raise BadRequestError('Invalid OAuth state') if log.provider != self.provider: logger.warn( 'Provider in OAuth state does not match with current provider', provider=log.provider, expected=self.provider) raise PermissionDeniedError( 'OAuth state invalid, provider does not match') return log, OAuthSessionParams(data=args) except TokenParseError as e: logger.warning('Parse OAuth state failed', error=e.message, token=state) raise BadRequestError('Invalid OAuth state')
def handle_authorize_error(self, params: OAuthCallbackParams): self.log, self.session = self._parse_oauth_state(params.state) self.log.status = AuthLogs.STATUS_FAILED logger.debug('Parse OAuth state result', sub=self.log._id, **self.session.to_dict()) error, desc = self._get_error(params.to_dict(), action='authorize') logger.debug('Authorize with provider failed', provider=self.provider.upper(), error=error, message=desc) error_data = { 'error': self.ERROR_AUTHORIZE_FAILED, 'msg': '{}, {}'.format(error, desc), 'nonce': self.session.nonce, 'provider': self.provider } if self.log.callback_if_failed: redirect_url = add_params_to_uri(uri=self.log.callback_if_failed, **error_data) return redirect(redirect_url) else: return jsonify(error_data)
def get_authorized_profile(): auth_token = request.form.get('auth_token') try: body = oauth_serv.get_authorized_profile(auth_token=auth_token, params=request.form) logger.debug('Profile authenticated', style='hybrid', **body) return jsonify(body) except TokenParseError as e: logger.warning('Parse auth token failed', error=e.message, auth_token=auth_token) abort(400, 'Invalid auth token')
def decode(self, token: str) -> Tuple[str, Dict[str, Any]]: try: payload = jwt.decode(token, key=self.key, issuer=self.issuer, algorithms=['HS256']) if int(payload['exp']) < int(time.time()): raise TokenParseError('Token expired') return payload['sub'], payload['data'] except TokenParseError: raise except Exception as e: logger.debug('Decore JWT token error', error=str(e)) raise TokenParseError('Token malformed')
def _allow_get_scope_id(self): ss = SystemSettings.all_as_dict() return_scoped_id = ss.get('return_scoped_id', 'never') logger.debug('System variables', return_scoped_id=return_scoped_id) if return_scoped_id == 'always': return True elif return_scoped_id == 'never': return False level = (db.session.query(Admins.level).join( Apps, and_(Admins._id == Apps.owner_id, Apps._id == self.app_id)).scalar()) return Admins.check_has_plus_level(provider=self.provider, level=level)
def parse_auth_token(auth_token: str) -> Tuple[AuthLogs, Dict[str, Any]]: log_id, args = easy_token_helper.decode(token=auth_token) log: Optional[AuthLogs] = AuthLogs.query.filter_by(_id=log_id).one_or_none() if not log or log.nonce != args.get('_nonce'): logger.debug('Invalid auth token or nonce does not match') raise BadRequestError('Invalid auth token') if log.status not in [AuthLogs.STATUS_AUTHORIZED, AuthLogs.STATUS_WAIT_REGISTER]: logger.debug('Validate auth token failed. Illegal auth log status.', status=log.status, expected=[AuthLogs.STATUS_AUTHORIZED, AuthLogs.STATUS_WAIT_REGISTER]) raise BadRequestError('Invalid auth token') return log, args
def parse_associate_token(associate_token: str): social_id, args = easy_token_helper.decode(token=associate_token) log: Optional[AssociateLogs] = AssociateLogs.query \ .filter_by(dst_social_id=social_id) \ .order_by(AssociateLogs._id.desc()).first() if not log or log.nonce != args.get('_nonce'): logger.debug('Invalid associate token or nonce does not match') raise BadRequestError('Invalid associate token') if log.status != AssociateLogs.STATUS_NEW: logger.debug('Illegal associate log status', status=log.status, expected=AssociateLogs.STATUS_NEW) raise BadRequestError('Invalid associate token') return log
def decode(self, token: str) -> Tuple[str, Dict[str, Any]]: try: data = base64decode(token[4:], urlsafe=True) payload = self.deserialize(packed=data) sign = payload['sign'] del payload['sign'] raw = urlparse.urlencode(payload) expected_sign = calculate_hmac(self.key, raw, digestmod=hashlib.sha256) if sign != expected_sign: msg = 'Invalid signature, expected sign: {}, actual sign: {}' raise TokenParseError(msg.format(expected_sign, sign, token)) if payload['exp'] < int(time.time()): raise TokenParseError('Token expired') return payload['sub'], payload['data'] except TokenParseError: raise except Exception as e: logger.debug('Decode easy token error', error=str(e)) raise TokenParseError('Token malformed')
def handle_authorize_success(self, params: OAuthCallbackParams): self.log, self.session = self._parse_oauth_state(state=params.state) logger.debug('Parse OAuth state result', sub=self.log._id, **self.session.to_dict()) try: self.channel = Channels.query.filter_by( app_id=self.log.app_id, provider=self.provider).one_or_none() profile, token = self._handle_authentication(params=params) except Exception: self.log.status = AuthLogs.STATUS_FAILED db.session.commit() raise intent = self.log.intent if intent == AuthLogs.INTENT_ASSOCIATE: if self.session.provider != self.provider: self._raise_error(error='permission_denied', msg='Target provider does not match') elif profile.user_id: self._raise_error(error='conflict', msg='Profile has linked with another user') profile.merge_with(alias=self.session.dst_social_id) elif intent == AuthLogs.INTENT_LOGIN and not self.log.is_login: self._raise_error( error='invalid_request', msg='Social profile does not exist, should register instead') elif intent == AuthLogs.INTENT_REGISTER and self.log.is_login: self._raise_error( error='invalid_request', msg='Social profile already existed, should login instead') if self._is_mobile(): return self._make_mobile_authorize_success_response(token) else: return self._make_web_authorize_success_response(token)
def authorize(self, params: OAuthAuthorizeParams): # Verify request params and extract session data self.session = self._verify_and_parse_session(params=params) oauth_app = Apps.query.filter_by(_id=params.app_id, _deleted=0).one_or_none() self.channel = Channels.query.filter_by( app_id=params.app_id, provider=self.provider).one_or_none() if not oauth_app or not self.channel: raise NotFoundError(msg='Application or channel not found') if not self._is_mobile(): allowed_uris = [ up.unquote_plus(uri) for uri in oauth_app.get_callback_uris() ] logger.debug('Verify callback URI', style='hybrid', allowed_uris=allowed_uris, succ_callback=params.success_callback, fail_callback=params.failed_callback) illegal_callback_msg = ( 'Invalid callback_uri value. ' 'Check if it is registered in EasyLogin developer site') if not verify_callback_uri(allowed_uris, params.success_callback): raise PermissionDeniedError(msg=illegal_callback_msg) if params.failed_callback and not verify_callback_uri( allowed_uris, params.failed_callback): raise PermissionDeniedError(msg=illegal_callback_msg) self.log = AuthLogs(provider=self.provider, app_id=params.app_id, nonce=gen_random_token(nbytes=32), intent=params.intent, platform=self.session.platform, callback_uri=params.success_callback, callback_if_failed=params.failed_callback) db.session.add(self.log) db.session.flush() db.session.add( JournalLogs(ua=request.headers.get('User-Agent'), ip=get_remote_ip(request), path=request.full_path, ref_id=self.log._id)) oauth_state = generate_oauth_state(self.log, **self.session.to_dict()) if self._is_mobile(): return jsonify({ 'channel': { 'client_id': self.channel.client_id, 'options': self.channel.get_options(), 'scopes': self.channel.get_permissions() }, 'state': oauth_state }) else: url = self._build_authorize_uri(state=oauth_state) logger.debug('Authorize URL', url) return redirect(url)