def test_token_generation(self): """ Verify token generation using known hashes and signature """ test_user = self.TEST_USER not_user = self.NOT_USER known_hashhash = ('5106273f7789f1e26b4a212789992f75c15433f402f3e94a' 'd18e7c80aee80faf') with self.app.app_context(): token = generate_token(test_user) # Verify hashhash against known value hashhash = get_hashhash(test_user) self.assertEqual(hashhash, known_hashhash) # Now that we verified our hashhash, independently verify # the data with a serializer from config (not trusting # get_signature here). serializer = Serializer(self.app.config['FLASK_SECRET']) self.assertEqual(serializer.loads(token)['hashhash'], hashhash) # Now go ahead and verify the reverse serializer = get_signature() data = serializer.loads(token) self.assertEqual(data['username'], test_user) self.assertEqual(data['hashhash'], hashhash) # Verify no user handling (don't really care what # exception gets raised). with self.assertRaises(Exception): token = generate_token(not_user)
def get_by_authorization(self_class, token, svc_account): if token: token = token.replace('Basic ', '', 1) s = JSONWebSignatureSerializer(flask.current_app.config['SECRET_KEY']) data = s.loads(token) return User.get(data['id'], svc_account) return None
def get_credential(a_token): s = JSONWebSignatureSerializer(server_secret_key) try: credential = s.loads(a_token) return credential except: return None
def decode_auth_token(token): try: s = JSONWebSignatureSerializer(app.secret_key) print token return s.loads(token) except: return None
def VerifyToken(token, secret): s = Serializer(secret) try: data = s.loads(token) except BadSignature as e: raise InvalidAuth('bad_token', e.message) return data
def confirm(token): if g.user.is_confirmed(): return redirect(url_for('admin.home')) s = JSONWebSignatureSerializer(current_app.config['SECRET_KEY']) data = None try: data = s.loads(token) except: abort(404) if data.get('id'): id = data.get('id') else: id = 0 user = User.query.get_or_404(id) if user.id == g.user.id: user.confirmed = True db.session.add(user) db.session.commit() flash('You have successfully confirmed your account!') else: flash('Invalid token') return render_template("admin/confirm.html", title='Confirm Account')
class JWSBackend: def __init__(self, secret, token_name="Token"): self.token_regex = re.compile(r"^{}: (.*)$".format(token_name)) self.serializer = JSONWebSignatureSerializer(secret) def parse(self, request): authorization = request.headers.get("Authorization", None) if authorization is None: return None match = re.match(self.token_regex, authorization) if match is None: return None sign = match.group(1) try: data = self.serializer.loads(sign) return data except Exception: return None def authenticate(self, request, data): request.identity = data return request
def reset_password(token): if g.user is not None and g.user.is_authenticated: return redirect(url_for('admin.home')) s = JSONWebSignatureSerializer(current_app.config['SECRET_KEY']) data = None try: data = s.loads(token) except: abort(404) if data.get('id'): id = data.get('id') else: id = 0 user = User.query.get_or_404(id) form = ResetForm() if form.validate_on_submit(): user.password = form.password.data.strip() db.session.add(user) db.session.commit() flash('Your password has now been reset.') return redirect(url_for('main.login')) return render_template('reset.html', title='Reset Password', form=form, user=user, token=token)
def verify_auth_token(token): s = Serializer(current_app.config['SECRET_KEY']) try: data = s.loads(token) except: return None return User.query.get(data['id'])
def check_authorization(*args, **kwargs): instance = args[0] # self argument request_args = instance.auth_parser.parse_args() token = request_args.get('api_token') # parse token JWT = JWS(os.environ.get('SECRET_KEY')) try: token_data = JWT.loads(token) except BadSignature: return jsonify({ 'status': 'auth error', 'message': 'invalid token' }) # check validation if datetime.datetime.now().timestamp() < token_data.get('expires'): # token valid and not expired # add current user id to instance as an attribute # this allows linking items to users with re-parsing the tokens instance.user_id = token_data.get('id') return f(*args, **kwargs) else: # token expired return jsonify({ 'status': 'auth error', 'message': 'token expired', 'help': 'get a new token generated from login endpoint' })
def verify_token(token): s = SigSerializer(SECRET_KEY) try: data = s.loads(token) except (BadSignature): return None return data
def verify_auth_token(token): s = Serializer(app.config['SECRET_KEY']) try: return s.loads(token) except BadSignature: return None
def verify_group_token(token): s = JSONWebSignatureSerializer(current_app.config['SECRET_KEY']) try: data = s.loads(token) except: return None group = Group.query.get(data['id']) return group
def unsign(self, value): if value is None: return value s = JSONWebSignatureSerializer(self.secret_key) try: return s.loads(value) except BadSignature: return {}
def unsign(self, value): if value is None: return value s = JSONWebSignatureSerializer(self.secret_key, algorithm_name='HS256') try: return s.loads(value) except BadSignature: return {}
def verify_auth_token(token): s = JSONWebSignatureSerializer(app.config['SECRET_KEY']) try: data = s.loads(token) except BadSignature: return None user = User.query.get(data['id']) return user
def verify_auth_token(token): s = Serializer(app.config['SECRET_KEY']) try: data = s.loads(token) except SignatureExpired: return None # valid token, but expired except BadSignature: return None # invalid token return User.query.get(data['id'])
def confirm_reset_token(self, token): s = JSONWebSignatureSerializer(current_app.config['SECRET_KEY']) try: data = s.loads(token) except: return False if data.get('reset') != self.id: return False return True
def verify_auth_token(self, token): s = Serializer(SECRET_KEY) try: data = s.loads(token) except SignatureExpired: return None # valid token, but expired except BadSignature: return None # invalid token return [data['username'], data['password']]
def verify_auth_token(token): """verify the user with token""" s = Serializer(current_app.config['SECRET_KEY']) try: data = s.loads(token) except: return None # get id return User.query.get_or_404(data['id'])
def get_event_page_no(token): """convert token into the pagenumber arg: token: token return: return the page number to convert it. """ page_no = PageSerializer(config.SECRET_KEY) return page_no.loads(token)
def verify_auth_token(token): s = Serializer(app.config['SECRET_KEY']) print 'token:' + token try: data = s.loads(token) except: return None print 'data:{}'.format(data) user = User.get_one(email=data) return user
def af_reset_recieve(cls_, token, password): serializer = JSONWebSignatureSerializer(current_app.config['API_KEY']) try: payload = serializer.loads(token) user = cls_.get(payload['id']) if user is None or payload['signature'] != user.signature or user.status != const.STATUS_ACTIVE: raise ApiValidationError(ValidationError('User reset not allowed')) user.update(password=password, signature=random_string()) return user.jsonify(acl=const.ACL_OWNER) except (itsdangerous.SignatureExpired, itsdangerous.BadSignature): raise ApiValidationError(ValidationError('Token is broken'))
def confirm(self,token): s = Serializer(current_app.config['SECRET_KEY']) try: data = s.loads(token) except: return false if data.get('confirm') != self.id: return false self.confirmed = True db.session.add(self) return True
def change_email(self, token): s = JSONWebSignatureSerializer(current_app.config['SECRET_KEY']) try: data = s.loads(token) except: return False if data.get('id') != self.id: return False if data.get('new_email') is None: return False self.email = data.get('new_email') self.save() return True
def confirm(token): s=Serializer(configs[ENV].SECRET_KEY) try: recvData = s.loads(token) except: abort(404) RecvId = recvData.get('confirm') u = User.query.filter_by(id=RecvId).first_or_404() u.confirmed = True db.session.add(u) db.session.commit() flash(u'恭喜您完成账号激活') return redirect(url_for('auth.passport'))
def validate_token(token): if not isinstance(token, str): raise TypeError('token은 반드시 `str` 이여야합니다.') secret_key = get_secret_key() s = JSONWebSignatureSerializer(secret_key) try: data = s.loads(token.encode('utf-8')) except BadSignature as e: raise InvalidTokenError('잘못된 token입니다.') expired_at = Arrow.fromtimestamp(data['expired_at']) now = utcnow() if expired_at < now: raise ExpiredTokenError('만료된 token입니다.') return data
def request_loader(request): token = request.headers.get('Authentication') if token: s = Serializer(app.config['SECRET_KEY']) try: user_id = s.loads(token)['user_id'] except BadSignature: pass else: user = User.query.filter(User.id == user_id).first() if user and user.token == token: return user return None
def verify_auth_token(token): token = bytes(token, encoding='utf-8') s = Serializer(SECRET_KEY) try: data = s.loads(token) except SignatureExpired as e: return None except BadSignature as e: return None user = User.query.get(data['id']) if user: return True else: return False
def is_csrf_token_bad(token, csrf_secret): try: jsw = JSONWebSignatureSerializer(app.secret_key) tobj = jsw.loads(token) nonce_int = bytes_to_int(b64decode(tobj["n"])) key_int = bytes_to_int(b64decode(tobj["k"])) user_secret = int_to_bytes(nonce_int ^ key_int) return not constant_time_compare( user_secret, csrf_secret ) except Exception: return True
def verify_api_key(api_key): """Verify an API key based on the user name Returns: Actinia Core_api.resources.common.user.ActiniaUser: A user object is success or None """ s = JSONWebSignatureSerializer(global_config.SECRET_KEY) try: data = s.loads(api_key) except BadSignature: return None user = ActiniaUser(data["user_id"]) if user.exists(): return user return None
def verify_token(token): s = JSONWebSignatureSerializer(app_config.malwarecage.secret_key) try: data = s.loads(token) except SignatureExpired: return None except BadSignature: return None if "api_key_id" not in data: return None try: api_key_obj = APIKey.query.filter( APIKey.id == uuid.UUID(data['api_key_id'])).one() except NoResultFound: return None return api_key_obj.user
class BaseAuthMech: def __init__(self, issuer_identity, secret, expires=86400, renew_window=3600): self.serialiser = JSONWebSignatureSerializer(secret) self.expires = expires self.issuer_identity = issuer_identity self.renew_window = renew_window def generateToken(self, user_id): params = { 'iat': int(time()), 'iss': self.issuer_identity, 'aud': cherrypy.request.address, 'exp': int(time()) + self.expires, 'sub': user_id, 'jti': str(uuid.uuid4()) } token = self.serialiser.dumps(params) return token def verifyToken(self, token): (bearer, token) = token.split(' ') if bearer != "Bearer": raise AuthenticationFailure("Invalid authentication token") try: params = self.serialiser.loads(token) except BadSignature: raise AuthenticationFailure("Invalid Signature") if int(params['exp']) < int(time()): raise AuthenticationFailure("Token Expired") if params['aud'] != cherrypy.request.address: raise AuthenticationFailure("Invalid audience") if int(params['exp']) - self.renew_window > int(time()): raise RenewToken(self.renewToken(params)) def renewToken(self, params): params['exp'] = int(time()) + self.expires token = self.serialiser.dumps(params) return token
class AuthenticationToken: def __init__(self, secret_key, expires_in): self.secret_key = secret_key self.expires_in = expires_in self.serializer = JSONWebSignatureSerializer(secret_key) def generate_token(self, username): info = {'username': username, 'creation_time': time()} token = self.serializer.dumps(info) return token.decode() def validate_token(self, token): info = self.serializer.loads(token.encode()) if time() - info['creation_time'] > self.expires_in: raise SignatureExpired( "The Token has been expired; get a new token") return info['username']
class Encryptor(object): def __init__(self,private_key): self.serial = JSONWebSignatureSerializer(private_key) def encrypt(self,username, password): result = { 'username': username, 'password': password, 'creation-time': datetime.now().strftime(timeformat) } #convert it into a token(bytes) -> decode to convert to string return self.serial.dumps(result).decode() def decrypt(self,token): # converts the token back from string to bytes and then loading it using the JSON serialiser payload = self.serial.loads(token.encode()) time_stamp = payload.get('creation-time') if datetime.now() > datetime.strptime(time_stamp, timeformat) + timedelta(minutes=time_expiry): raise SignatureExpired("Token created more than 60 min ago.") return payload
class Encipher(object): __hashMethod = get_secret('pwdhash_method.pkl') __reRawHash = re.compile(r"^.*?:.*?:.*?\$(?P<salt>.*?)\$(?P<code>.*?)$") def __init__(self, secret_key): self.__secret_key = secret_key self.__serializer = Serializer(self.__secret_key) def __parse(self, rawResult): return self.__reRawHash.match(rawResult).group(2, 1) def __join(self, code, salt): return "{}${}${}".format(self.__hashMethod, salt, code) def encode(self, raw): return self.__parse(generate_password_hash(raw, self.__hashMethod)) def check(self, code, salt, raw): return check_password_hash(self.__join(code, salt), raw) def tokenize(self, *args): return self.__serializer.dumps(args).decode("utf-8") def untokenize(self, token): return self.__serializer.loads(token.encode("utf-8")) def get_token(self, raw): if isinstance(raw, str): return self.tokenize(*self.encode(raw)) else: # 允许直接序列化一个对象 return self.tokenize(raw) def get_raw(self, token): return self.untokenize(token) def verify(self, token, raw): try: return self.check(*self.get_raw(token), raw) except TypeError: # 否则,序列化前为对象 return self.get_raw(token)[0] == raw
def confirm(username): confirm_code = request.headers['CONFIRM'] json_data = request.get_json() user = Users.find_by_username(username) if json_data['confirm_code'] and confirm_code: serial = Serializer(current_app.config['SECRET_KEY']) try: loaded_data = serial.loads(confirm_code.encode('utf-8')) except: return {"msg": "couldn't load confirm"} if json_data['confirm_code'] == loaded_data['confirm_code']: user.confirmed = True db.session.commit() next = url_for('/change-password') if next: redirect(next) return { "msg": "Confirmation done, now change password.", "next_url": url_for('/home'), } return {"msg": "please make sure the confirmation number is correct."}, 400
def reset_password(token): if current_user.is_authenticated: return redirect(url_for('home')) s = Serializer(app.config['SECRET_KEY'], '1800') try: user_id = s.loads(token)['user_id'] except: user_id = None if not (user_id): flash('That is an invalid or expired token', 'warning') return redirect(url_for('reset_password_request')) user = User.objects(user_id=user_id).first() form = ResetPasswordForm() if form.validate_on_submit(): hashed_password = bcrypt.generate_password_hash( form.password.data).decode('utf-8') user.password = hashed_password user.save() flash('Your password has been updated! You are now able to log in', 'success') return redirect(url_for('login')) return render_template('reset_password.html', form=form)
def decode_token(token: str, scope: str): """ Decode and validate a JWS token received by a view, and return the Hydra login request dict and login challenge, along with any additional params. :param token: the token to decode :param scope: the scope for which the token is valid :return: tuple(login_request: dict, login_challenge: str, view_params: dict) :raises HydraAdminError: if the encoded login challenge is invalid """ if not token: abort(403) # HTTP 403 Forbidden try: serializer = JSONWebSignatureSerializer(current_app.secret_key, salt=scope) params = serializer.loads(token) challenge = params.pop('challenge', '') login_request = hydra_admin.get_login_request(challenge) return login_request, challenge, params except BadData: abort(403) # HTTP 403 Forbidden
def get_limit_time(request): TIME_KEY = 'limit_dead' try: time_org = red.get(TIME_KEY) if not time_org: # 没有时间,代表服务已过期 return JsonResponse({'status': 0, 'msg': '服务已过期, 请及时续费'}) else: # 有时间,则判断是否符合标准 datetime#使用时长 s = JSONWebSignatureSerializer('8labbal8_') info_list = s.loads(time_org).get('key', '') date_list = info_list.split('#') if len(date_list) != 2: # 日期格式不对,表示服务已过期 return JsonResponse({'status': 0, 'msg': '服务已过期, 请及时续费'}) else: start_time = date_list[0] time_range = date_list[1] start_time = datetime.datetime.strptime(start_time, '%Y-%m-%d') surplus_time = (datetime.datetime.now() - start_time).days if (surplus_time > 0) and (surplus_time > int(time_range)): # 表示已过期 return JsonResponse({'status': 0, 'msg': '服务已过期, 请及时续费'}) elif surplus_time < 0: # 表示已过期 return JsonResponse({'status': 0, 'msg': '服务已过期, 请及时续费'}) else: return JsonResponse({'status': 1, 'msg': '使用正常'}) except Exception as e: logger.error(e) return JsonResponse({'status': 0, 'msg': '服务已过期, 请及时续费'})
def checktask(post, conn): task_first=None if 'task' in post: s=JSONWebSignatureSerializer(config.key_encrypt) arr_file=s.loads(post['task']) if 'file' in arr_file: task_file=arr_file['file'] # Import module task_file=task_file.replace('/', '.') task_file=task_file.replace('.py', '') try: task_execute=import_module(task_file) if config.reloader: reload(task_execute) task_first=task_execute.MakeTask(conn) return (task_first, task_file) except: return (False, False) else: return (False, False) else: return (False, False)
def validate(self, attrs): # 验证openid tjs = TJS(settings.SECRET_KEY, 300) # 解密 try: data = tjs.loads(attrs['openid']) except: raise serializers.ValidationError("openid错误") openid = data.get('openid') attrs['open_id'] = openid # 验证短信验证码 conn = get_redis_connection('verify') # 取出验证码 real_sms = conn.get('sms_{}'.format(attrs["mobile"])) # 验证码比对 if not real_sms: raise serializers.ValidationError("验证码已经过期") if real_sms.decode() != attrs["sms_code"]: raise serializers.ValidationError("验证码不匹配") # 验证用户是否注册过 try: user = User.objects.get(mobile=attrs['mobile']) except: # 用户未注册 return attrs else: # 用户已经注册 # 密码校验 if not user.check_password(attrs['password']): raise serializers.ValidationError("密码不正确") attrs['user'] = user return attrs
def decom_step3(id): ## Get the actions list actions_data = request.form['actions'] ## Decode it signer = JSONWebSignatureSerializer(app.config['SECRET_KEY']) try: actions = signer.loads(actions_data) except itsdangerous.BadSignature as ex: abort(400) # Build the options to send on to the task options = {'actions': []} if request.form.get("runaction", None) is not None: for action in request.form.getlist("runaction"): options['actions'].append(actions[int(action)]) options['wfconfig'] = workflow.config # Connect to NeoCortex and start the task neocortex = cortex.lib.core.neocortex_connect() task_id = neocortex.create_task(__name__, session['username'], options, description="Decommissions a system") # Redirect to the status page for the task return redirect(url_for('task_status', id=task_id))
def loads(self, s, salt=None, return_header=False): payload, header = JSONWebSignatureSerializer.loads(self, s, salt, return_header=True) # 生成新token new_token = None if 'exp' not in header: raise BadSignature('Missing expiry date', payload=payload) int_date_error = BadHeader('Expiry date is not an IntDate', payload=payload) try: header['exp'] = int(header['exp']) except ValueError: raise int_date_error if header['exp'] < 0: raise int_date_error if header['refresh_exp'] < self.now(): # 超过了刷新时间,抛出异常 if header['exp'] < self.now(): raise SignatureExpired( 'Signature expired', payload=payload, date_signed=self.get_issue_date(header), ) # 还在可刷新时间内,生成新的token else: ser = Serializer(current_app.config['SECRET_KEY'], expires_in=self.expires_in) new_token = ser.dumps(payload) if return_header: return payload, new_token, header return payload, new_token
def login(): form = LoginForm().validate_for_api() # 校对验证码 if current_app.config.get("LOGIN_CAPTCHA"): tag = request.headers.get("tag") secret_key = current_app.config.get("SECRET_KEY") serializer = JWSSerializer(secret_key) if form.captcha.data != serializer.loads(tag): raise Failed("验证码校验失败") user = manager.user_model.verify(form.username.data, form.password.data) # 用户未登录,此处不能用装饰器记录日志 Log.create_log( message=f"{user.username}登录成功获取了令牌", user_id=user.id, username=user.username, status_code=200, method="post", path="/cms/user/login", permission="", commit=True, ) access_token, refresh_token = get_tokens(user) return {"access_token": access_token, "refresh_token": refresh_token}
def parse_token(token: str) -> str: s = JSONWebSignatureSerializer(current_app.secret_key, salt='auth') try: return s.loads(token) except BadSignature: return None
class OpenIDConnect(object): """ The core OpenID Connect client object. """ def __init__( self, app=None, credentials_store=None, http=None, tokens_store=None, bad_tokens_store=None, time=None, urandom=None, ): self.credentials_store = (credentials_store if credentials_store is not None else MemoryCredentials()) self.tokens_store = tokens_store if tokens_store is not None else MemoryTokens( ) self.bad_tokens_store = (bad_tokens_store if bad_tokens_store is not None else MemoryBadTokens()) if http is not None: warn("HTTP argument is deprecated and unused", DeprecationWarning) if time is not None: warn("time argument is deprecated and unused", DeprecationWarning) if urandom is not None: warn("urandom argument is deprecated and unused", DeprecationWarning) # By default, we do not have a custom callback self._custom_callback = None # get stuff from the app's config, which may override stuff set above if app is not None: self.init_app(app) def init_app(self, app): """ Do setup that requires a Flask app. :param app: The application to initialize. :type app: Flask """ secrets = self.load_secrets(app) self.client_secrets = list(secrets.values())[0] secrets_cache = DummySecretsCache(secrets) # Set some default configuration options app.config.setdefault("OIDC_SCOPES", ["openid", "email"]) app.config.setdefault("OIDC_GOOGLE_APPS_DOMAIN", None) app.config.setdefault("OIDC_ID_TOKEN_COOKIE_NAME", "oidc_id_token") app.config.setdefault("OIDC_ID_TOKEN_COOKIE_PATH", "/") app.config.setdefault("OIDC_ID_TOKEN_COOKIE_TTL", 7 * 86400) # 7 days # should ONLY be turned off for local debugging app.config.setdefault("OIDC_COOKIE_SECURE", True) app.config.setdefault( "OIDC_VALID_ISSUERS", (self.client_secrets.get("issuer") or GOOGLE_ISSUERS)) app.config.setdefault("OIDC_CLOCK_SKEW", 60) # 1 minute app.config.setdefault("OIDC_REQUIRE_VERIFIED_EMAIL", False) app.config.setdefault("OIDC_OPENID_REALM", None) app.config.setdefault("OIDC_USER_INFO_ENABLED", True) app.config.setdefault("OIDC_CALLBACK_ROUTE", "/oidc_callback") app.config.setdefault("OVERWRITE_REDIRECT_URI", False) app.config.setdefault("OIDC_EXTRA_REQUEST_AUTH_PARAMS", {}) app.config.setdefault("OIDC_EXTRA_REQUEST_HEADERS", {}) # Configuration for resource servers app.config.setdefault("OIDC_RESOURCE_SERVER_ONLY", False) app.config.setdefault("OIDC_RESOURCE_CHECK_AUD", False) # We use client_secret_post, because that's what the Google # oauth2client library defaults to app.config.setdefault("OIDC_INTROSPECTION_AUTH_METHOD", "client_secret_post") app.config.setdefault("OIDC_TOKEN_TYPE_HINT", "access_token") if not "openid" in app.config["OIDC_SCOPES"]: raise ValueError('The value "openid" must be in the OIDC_SCOPES') # register callback route and cookie-setting decorator if not app.config["OIDC_RESOURCE_SERVER_ONLY"]: app.route(app.config["OIDC_CALLBACK_ROUTE"])(self._oidc_callback) app.before_request(self._before_request) app.after_request(self._after_request) # Initialize oauth2client self.flow = flow_from_clientsecrets( app.config["OIDC_CLIENT_SECRETS"], scope=app.config["OIDC_SCOPES"], cache=secrets_cache, ) assert isinstance(self.flow, OAuth2WebServerFlow) # TODO use configurable salt string instead of hardcoded value to improve security # create signers using the Flask secret key self.extra_data_serializer = JSONWebSignatureSerializer( app.config["SECRET_KEY"], salt="flask-oidc-extra-data") self.cookie_serializer = JSONWebSignatureSerializer( app.config["SECRET_KEY"], salt="flask-oidc-cookie") try: self.credentials_store = app.config["OIDC_CREDENTIALS_STORE"] except KeyError: pass def load_secrets(self, app): # Load client_secrets.json to pre-initialize some configuration content = app.config["OIDC_CLIENT_SECRETS"] if isinstance(content, dict): return content else: return _json_loads(open(content, "r").read()) @property def user_loggedin(self): """ Represents whether the user is currently logged in. Returns: bool: Whether the user is logged in with Flask-OIDC. .. versionadded:: 1.0 """ return g.oidc_id_token is not None def user_getfield(self, field, access_token=None): """ Request a single field of information about the user. :param field: The name of the field requested. :type field: str :returns: The value of the field. Depending on the type, this may be a string, list, dict, or something else. :rtype: object .. versionadded:: 1.0 """ info = self.user_getinfo([field], access_token) return info.get(field) def user_getinfo(self, fields, access_token=None): """ Request multiple fields of information about the user. :param fields: The names of the fields requested. :type fields: list :returns: The values of the current user for the fields requested. The keys are the field names, values are the values of the fields as indicated by the OpenID Provider. Note that fields that were not provided by the Provider are absent. :rtype: dict :raises Exception: If the user was not authenticated. Check this with user_loggedin. .. versionadded:: 1.0 """ if g.oidc_id_token is None and access_token is None: raise Exception("User was not authenticated") info = {} all_info = None for field in fields: if access_token is None and field in g.oidc_id_token: info[field] = g.oidc_id_token[field] elif current_app.config["OIDC_USER_INFO_ENABLED"]: # This was not in the id_token. Let's get user information if all_info is None: all_info = self._retrieve_userinfo(access_token) if all_info is None: # To make sure we don't retry for every field all_info = {} if field in all_info: info[field] = all_info[field] else: # We didn't get this information pass return info def get_access_token(self): """Method to return the current requests' access_token. :returns: Access token or None :rtype: str .. versionadded:: 1.2 """ try: credentials = OAuth2Credentials.from_json( self.credentials_store[g.oidc_id_token["sub"]]) return credentials.access_token except KeyError: logger.debug("Expired ID token, credentials missing", exc_info=True) return None def get_refresh_token(self): """Method to return the current requests' refresh_token. :returns: Access token or None :rtype: str .. versionadded:: 1.2 """ try: credentials = OAuth2Credentials.from_json( self.credentials_store[g.oidc_id_token["sub"]]) return credentials.refresh_token except KeyError: logger.debug("Expired ID token, credentials missing", exc_info=True) return None def _retrieve_userinfo(self, access_token=None): """ Requests extra user information from the Provider's UserInfo and returns the result. :returns: The contents of the UserInfo endpoint. :rtype: dict """ if "userinfo_uri" not in self.client_secrets: logger.debug("Userinfo uri not specified") raise AssertionError("UserInfo URI not specified") # Cache the info from this request if "_oidc_userinfo" in g: return g._oidc_userinfo http = httplib2.Http() if access_token is None: try: credentials = OAuth2Credentials.from_json( self.credentials_store[g.oidc_id_token["sub"]]) except KeyError: logger.debug("Expired ID token, credentials missing", exc_info=True) return None credentials.authorize(http) resp, content = http.request(self.client_secrets["userinfo_uri"]) else: # We have been manually overriden with an access token headers = current_app.config["OIDC_EXTRA_REQUEST_HEADERS"] headers["Content-type"] = "application/x-www-form-urlencoded" resp, content = http.request( self.client_secrets["userinfo_uri"], "POST", body=urlencode({"access_token": access_token}), headers=headers, ) logger.debug("Retrieved user info: %s" % content) info = _json_loads(content) g._oidc_userinfo = info return info def get_cookie_id_token(self): """ .. deprecated:: 1.0 Use :func:`user_getinfo` instead. """ warn( "You are using a deprecated function (get_cookie_id_token). " "Please reconsider using this", DeprecationWarning, ) return self._get_cookie_id_token() def _get_cookie_id_token(self): try: id_token_cookie = request.cookies.get( current_app.config["OIDC_ID_TOKEN_COOKIE_NAME"]) if not id_token_cookie: # Do not error if we were unable to get the cookie. # The user can debug this themselves. return None return self.cookie_serializer.loads(id_token_cookie) except SignatureExpired: logger.debug("Invalid ID token cookie", exc_info=True) return None except BadSignature: logger.info("Signature invalid for ID token cookie", exc_info=True) return None def set_cookie_id_token(self, id_token): """ .. deprecated:: 1.0 """ warn( "You are using a deprecated function (set_cookie_id_token). " "Please reconsider using this", DeprecationWarning, ) return self._set_cookie_id_token(id_token) def _set_cookie_id_token(self, id_token): """ Cooperates with @after_request to set a new ID token cookie. """ g.oidc_id_token = id_token g.oidc_id_token_dirty = True def _after_request(self, response): """ Set a new ID token cookie if the ID token has changed. """ # This means that if either the new or the old are False, we set # insecure cookies. # We don't define OIDC_ID_TOKEN_COOKIE_SECURE in init_app, because we # don't want people to find it easily. cookie_secure = current_app.config[ "OIDC_COOKIE_SECURE"] and current_app.config.get( "OIDC_ID_TOKEN_COOKIE_SECURE", True) if getattr(g, "oidc_id_token_dirty", False): if g.oidc_id_token: signed_id_token = self.cookie_serializer.dumps(g.oidc_id_token) response.set_cookie( current_app.config["OIDC_ID_TOKEN_COOKIE_NAME"], signed_id_token, secure=cookie_secure, httponly=True, max_age=current_app.config["OIDC_ID_TOKEN_COOKIE_TTL"], ) else: # This was a log out response.set_cookie( current_app.config["OIDC_ID_TOKEN_COOKIE_NAME"], "", path=current_app.config["OIDC_ID_TOKEN_COOKIE_PATH"], secure=cookie_secure, httponly=True, expires=0, ) return response def _before_request(self): g.oidc_id_token = None self.authenticate_or_redirect() def authenticate_or_redirect(self): """ Helper function suitable for @app.before_request and @check. Sets g.oidc_id_token to the ID token if the user has successfully authenticated, else returns a redirect object so they can go try to authenticate. :returns: A redirect object, or None if the user is logged in. :rtype: Redirect .. deprecated:: 1.0 Use :func:`require_login` instead. """ # the auth callback and error pages don't need user to be authenticated if request.endpoint in frozenset(["_oidc_callback", "_oidc_error"]): return None # retrieve signed ID token cookie id_token = self._get_cookie_id_token() if id_token is None: return self.redirect_to_auth_server(request.url) # ID token expired # when Google is the IdP, this happens after one hour if time.time() >= id_token["exp"]: # get credentials from store try: credentials = OAuth2Credentials.from_json( self.credentials_store[id_token["sub"]]) except KeyError: logger.debug("Expired ID token, credentials missing", exc_info=True) return self.redirect_to_auth_server(request.url) # refresh and store credentials try: credentials.refresh(httplib2.Http()) if credentials.id_token: id_token = credentials.id_token else: # It is not guaranteed that we will get a new ID Token on # refresh, so if we do not, let's just update the id token # expiry field and reuse the existing ID Token. if credentials.token_expiry is None: logger.debug("Expired ID token, no new expiry. Falling" " back to assuming 1 hour") id_token["exp"] = time.time() + 3600 else: id_token["exp"] = calendar.timegm( credentials.token_expiry.timetuple()) self.credentials_store[id_token["sub"]] = credentials.to_json() self._set_cookie_id_token(id_token) except AccessTokenRefreshError: # Can't refresh. Wipe credentials and redirect user to IdP # for re-authentication. logger.debug("Expired ID token, can't refresh credentials", exc_info=True) del self.credentials_store[id_token["sub"]] return self.redirect_to_auth_server(request.url) # make ID token available to views g.oidc_id_token = id_token return None def require_login(self, view_func): """ Use this to decorate view functions that require a user to be logged in. If the user is not already logged in, they will be sent to the Provider to log in, after which they will be returned. .. versionadded:: 1.0 This was :func:`check` before. """ @wraps(view_func) def decorated(*args, **kwargs): if g.oidc_id_token is None: return self.redirect_to_auth_server(request.url) return view_func(*args, **kwargs) return decorated # Backwards compatibility check = require_login """ .. deprecated:: 1.0 Use :func:`require_login` instead. """ def require_keycloak_role(self, client, role): """ Function to check for a KeyCloak client role in JWT access token. This is intended to be replaced with a more generic 'require this value in token or claims' system, at which point backwards compatibility will be added. .. versionadded:: 1.5.0 """ def wrapper(view_func): @wraps(view_func) def decorated(*args, **kwargs): pre, tkn, post = self.get_access_token().split(".") access_token = json.loads(b64decode(tkn)) if role in access_token["resource_access"][client]["roles"]: return view_func(*args, **kwargs) else: return abort(403) return decorated return wrapper def flow_for_request(self): """ .. deprecated:: 1.0 Use :func:`require_login` instead. """ warn( "You are using a deprecated function (flow_for_request). " "Please reconsider using this", DeprecationWarning, ) return self._flow_for_request() def _flow_for_request(self): """ Build a flow with the correct absolute callback URL for this request. :return: """ flow = copy(self.flow) redirect_uri = current_app.config["OVERWRITE_REDIRECT_URI"] if not redirect_uri: flow.redirect_uri = url_for("_oidc_callback", _external=True) else: flow.redirect_uri = redirect_uri return flow def redirect_to_auth_server(self, destination=None, customstate=None): """ Set a CSRF token in the session, and redirect to the IdP. :param destination: The page that the user was going to, before we noticed they weren't logged in. :type destination: Url to return the client to if a custom handler is not used. Not available with custom callback. :param customstate: The custom data passed via the ODIC state. Note that this only works with a custom_callback, and this will ignore destination. :type customstate: Anything that can be serialized :returns: A redirect response to start the login process. :rtype: Flask Response .. deprecated:: 1.0 Use :func:`require_login` instead. """ if not self._custom_callback and customstate: raise ValueError( "Custom State is only avilable with a custom handler") if "oidc_csrf_token" not in session: csrf_token = urlsafe_b64encode(os.urandom(24)).decode("utf-8") session["oidc_csrf_token"] = csrf_token state = { "csrf_token": session["oidc_csrf_token"], } statefield = "destination" statevalue = destination if customstate is not None: statefield = "custom" statevalue = customstate state[statefield] = self.extra_data_serializer.dumps( statevalue).decode("utf-8") extra_params = { "state": urlsafe_b64encode(json.dumps(state).encode("utf-8")), } extra_params.update( current_app.config["OIDC_EXTRA_REQUEST_AUTH_PARAMS"]) if current_app.config["OIDC_GOOGLE_APPS_DOMAIN"]: extra_params["hd"] = current_app.config["OIDC_GOOGLE_APPS_DOMAIN"] if current_app.config["OIDC_OPENID_REALM"]: extra_params["openid.realm"] = current_app.config[ "OIDC_OPENID_REALM"] flow = self._flow_for_request() auth_url = "{url}&{extra_params}".format( url=flow.step1_get_authorize_url(), extra_params=urlencode(extra_params)) # if the user has an ID token, it's invalid, or we wouldn't be here self._set_cookie_id_token(None) return redirect(auth_url) def _is_id_token_valid(self, id_token): """ Check if `id_token` is a current ID token for this application, was issued by the Apps domain we expected, and that the email address has been verified. @see: http://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation """ if not id_token: return False # step 2: check issuer if id_token["iss"] not in current_app.config["OIDC_VALID_ISSUERS"]: logger.error("id_token issued by non-trusted issuer: %s" % id_token["iss"]) return False if isinstance(id_token["aud"], list) and len(id_token["aud"]) == 1: id_token["aud"] = id_token["aud"][0] if isinstance(id_token["aud"], list): # step 3 for audience list if self.flow.client_id not in id_token["aud"]: logger.error("We are not a valid audience") return False # step 4 if "azp" not in id_token and len(id_token["aud"]) > 1: logger.error("Multiple audiences and not authorized party") return False else: # step 3 for single audience if id_token["aud"] != self.flow.client_id: logger.error("We are not the audience") return False # step 5 if "azp" in id_token and id_token["azp"] != self.flow.client_id: logger.error("Authorized Party is not us") return False # step 6-8: TLS checked # step 9: check exp if int(time.time()) >= int(id_token["exp"]): logger.error("Token has expired") return False # step 10: check iat if id_token["iat"] < (time.time() - current_app.config["OIDC_CLOCK_SKEW"]): logger.error("Token issued in the past") return False # (not required if using HTTPS?) step 11: check nonce # step 12-13: not requested acr or auth_time, so not needed to test # additional steps specific to our usage if (current_app.config["OIDC_GOOGLE_APPS_DOMAIN"] and id_token.get("hd") != current_app.config["OIDC_GOOGLE_APPS_DOMAIN"]): logger.error("Invalid google apps domain") return False if (not id_token.get("email_verified", False) and current_app.config["OIDC_REQUIRE_VERIFIED_EMAIL"]): logger.error("Email not verified") return False return True WRONG_GOOGLE_APPS_DOMAIN = "WRONG_GOOGLE_APPS_DOMAIN" def custom_callback(self, view_func): """ Wrapper function to use a custom callback. The custom OIDC callback will get the custom state field passed in with redirect_to_auth_server. """ @wraps(view_func) def decorated(*args, **kwargs): plainreturn, data = self._process_callback("custom") if plainreturn: return data else: return view_func(data, *args, **kwargs) self._custom_callback = decorated return decorated def _oidc_callback(self): plainreturn, data = self._process_callback("destination") if plainreturn: return data else: return redirect(data) def _process_callback(self, statefield): """ Exchange the auth code for actual credentials, then redirect to the originally requested page. """ # retrieve session and callback variables try: session_csrf_token = session.get("oidc_csrf_token") state = _json_loads( urlsafe_b64decode(request.args["state"].encode("utf-8"))) csrf_token = state["csrf_token"] code = request.args["code"] except (KeyError, ValueError): logger.debug("Can't retrieve CSRF token, state, or code", exc_info=True) return True, self._oidc_error() # check callback CSRF token passed to IdP # against session CSRF token held by user if csrf_token != session_csrf_token: logger.debug("CSRF token mismatch") return True, self._oidc_error() # make a request to IdP to exchange the auth code for OAuth credentials flow = self._flow_for_request() credentials = flow.step2_exchange(code) id_token = credentials.id_token if not self._is_id_token_valid(id_token): logger.debug("Invalid ID token") if id_token.get( "hd") != current_app.config["OIDC_GOOGLE_APPS_DOMAIN"]: return ( True, self._oidc_error( "You must log in with an account from the {0} domain.". format(current_app.config["OIDC_GOOGLE_APPS_DOMAIN"]), self.WRONG_GOOGLE_APPS_DOMAIN, ), ) return True, self._oidc_error() # store credentials by subject # when Google is the IdP, the subject is their G+ account number self.credentials_store[id_token["sub"]] = credentials.to_json() # Retrieve the extra statefield data try: response = self.extra_data_serializer.loads(state[statefield]) except BadSignature: logger.error("State field was invalid") return True, self._oidc_error() # set a persistent signed cookie containing the ID token # and redirect to the final destination self._set_cookie_id_token(id_token) return False, response def _oidc_error(self, message="Not Authorized", code=None): return message, 401, { "Content-Type": "text/plain", } def logout(self): """ Request the browser to please forget the cookie we set, to clear the current session. Note that as described in [1], this will not log out in the case of a browser that doesn't clear cookies when requested to, and the user could be automatically logged in when they hit any authenticated endpoint. [1]: https://github.com/puiterwijk/flask-oidc/issues/5#issuecomment-86187023 .. versionadded:: 1.0 """ # TODO: Add single logout self._set_cookie_id_token(None) # Below here is for resource servers to validate tokens def validate_token(self, token, scopes_required=None): """ This function can be used to validate tokens. Note that this only works if a token introspection url is configured, as that URL will be queried for the validity and scopes of a token. :param scopes_required: List of scopes that are required to be granted by the token before returning True. :type scopes_required: list :returns: True if the token was valid and contained the required scopes. An ErrStr (subclass of string for which bool() is False) if an error occured. :rtype: Boolean or String .. versionadded:: 1.1 """ valid = self._validate_token(token, scopes_required) if valid is True: return True else: return ErrStr(valid) def _validate_token(self, token, scopes_required=None): """The actual implementation of validate_token.""" if scopes_required is None: scopes_required = [] scopes_required = set(scopes_required) token_info = None valid_token = False has_required_scopes = False if token: try: token_info = self._get_token_info(token) except Exception as ex: token_info = {"active": False} logger.error("ERROR: Unable to get token info") logger.error(str(ex)) valid_token = token_info.get("active", False) if "aud" in token_info and current_app.config[ "OIDC_RESOURCE_CHECK_AUD"]: valid_audience = False aud = token_info["aud"] clid = self.client_secrets["client_id"] if isinstance(aud, list): valid_audience = clid in aud else: valid_audience = clid == aud if not valid_audience: logger.error("Refused token because of invalid audience") valid_token = False if valid_token: token_scopes = token_info.get("scope", "").split(" ") else: token_scopes = [] has_required_scopes = scopes_required.issubset(set(token_scopes)) if not has_required_scopes: logger.debug("Token missed required scopes") if valid_token and has_required_scopes: self.tokens_store[token] = token_info g.oidc_token_info = token_info return True if not valid_token: self.bad_tokens_store[token] = token return "Token required but invalid" elif not has_required_scopes: self.bad_tokens_store[token] = token return "Token does not have required scopes" else: self.bad_tokens_store[token] = token return "Something went wrong checking your token" def accept_token(self, require_token=False, scopes_required=None, render_errors=True): """ Use this to decorate view functions that should accept OAuth2 tokens, this will most likely apply to API functions. Tokens are accepted as part of the query URL (access_token value) or a POST form value (access_token). Note that this only works if a token introspection url is configured, as that URL will be queried for the validity and scopes of a token. :param require_token: Whether a token is required for the current function. If this is True, we will abort the request if there was no token provided. :type require_token: bool :param scopes_required: List of scopes that are required to be granted by the token before being allowed to call the protected function. :type scopes_required: list :param render_errors: Whether or not to eagerly render error objects as JSON API responses. Set to False to pass the error object back unmodified for later rendering. :type render_errors: callback(obj) or None .. versionadded:: 1.0 """ def wrapper(view_func): @wraps(view_func) def decorated(*args, **kwargs): token = None if "Authorization" in request.headers and request.headers[ "Authorization"].startswith("Bearer "): token = request.headers["Authorization"].split( None, 1)[1].strip() if "access_token" in request.form: token = request.form["access_token"] elif "access_token" in request.args: token = request.args["access_token"] validity = self.validate_token(token, scopes_required) if (validity is True) or (not require_token): return view_func(*args, **kwargs) else: response_body = { "error": "invalid_token", "error_description": validity, } if render_errors: response_body = json.dumps(response_body) return response_body, 401, {"WWW-Authenticate": "Bearer"} return decorated return wrapper def clear_tokens_store(self): self.tokens_store = {} def clear_bad_tokens_store(self): self.bad_tokens_store = {} def is_expired(self, token): current_time = time.time() cached_token = self.tokens_store[token] if cached_token.get("exp"): if current_time >= cached_token["exp"]: self.tokens_store.pop(token, False) return True return False def _get_token_info(self, token): # We hardcode to use client_secret_post, because that's what the Google # oauth2client library defaults to request = {"token": token} headers = current_app.config["OIDC_EXTRA_REQUEST_HEADERS"] headers["Content-type"] = "application/x-www-form-urlencoded" hint = current_app.config["OIDC_TOKEN_TYPE_HINT"] if hint != "none": request["token_type_hint"] = hint auth_method = current_app.config["OIDC_INTROSPECTION_AUTH_METHOD"] if auth_method == "client_secret_basic": basic_auth_string = "%s:%s" % ( self.client_secrets["client_id"], self.client_secrets["client_secret"], ) basic_auth_bytes = bytearray(basic_auth_string, "utf-8") headers["Authorization"] = "Basic %s" % b64encode( basic_auth_bytes).decode("utf-8") elif auth_method == "bearer": headers["Authorization"] = "Bearer %s" % token elif auth_method == "client_secret_post": request["client_id"] = self.client_secrets["client_id"] if self.client_secrets["client_secret"] is not None: request["client_secret"] = self.client_secrets["client_secret"] if self.bad_tokens_store and self.bad_tokens_store.get(token): raise ValueError( "Attempting to authenticate using token that failed validation recently. " "Generate a new token and try again.") if (not self.tokens_store or not self.tokens_store.get(token) or self.is_expired(token)): resp, content_string = httplib2.Http().request( self.client_secrets["token_introspection_uri"], "POST", urlencode(request), headers=headers, ) content = _json_loads(content_string) else: # using cached token content = self.tokens_store[token] return content
def deserialize_data(data): s = JSONWebSignatureSerializer(current_app.config['SECRET_KEY']) return s.loads(data)
def decrypt(text): s = JSONWebSignatureSerializer(SECRET_KEY) try: return s.loads(text) except BadSignature: return {}
def decrypt(key, sig): s = JSONWebSignatureSerializer(key) decrypted_data = s.loads(sig) return decrypted_data
def decrypt_sign(val, secret, salt=None): if not val or not secret: return val s = JSONWebSignatureSerializer(secret_key=secret, salt=salt) return type(val)(s.loads(val))
def unsign(self, value): s = JSONWebSignatureSerializer(self.secret_key) return s.loads(value)
import fileinput from itsdangerous import JSONWebSignatureSerializer s = JSONWebSignatureSerializer("secret") for sig in fileinput.input(): print(s.loads(sig.strip()))
def special_pagination(body, body_to_paginate, model_cls): body = body or dict() count = body.get("count", 10) current_token = body.get("paginationToken") current_page = body.get("page") filters = UserFilters( model_cls, None, ) identity_json = json.dumps(dict( filter=repr(filters), summary=False, field_names=tuple(), ), sort_keys=True) identity = MD5.new(base64.b64encode(identity_json.encode())).hexdigest() key = current_app.config['SEARCH_KEY'] serializer = JSONWebSignatureSerializer(key) offset = 0 if current_token: try: current_token_payload = serializer.loads(current_token) except BadSignature: logger.warning("Bad signature") raise ProblemException(title='Invalid request', detail="Bad pagination token") logger.debug('payload=%r identity=%r', current_token_payload, identity) if identity == current_token_payload.get('identity'): offset = current_token_payload.get('offset', 0) if current_page is not None: offset = (current_page - 1) * count else: logger.debug('payload=%r identity=%r', None, identity) total = len(body_to_paginate) results = body_to_paginate[offset:offset + count] has_next_result = offset + count < len(body_to_paginate) last_result = None if has_next_result: next_token_payload = dict( identity=identity, last_id=None, offset=offset + count, ) else: next_token_payload = dict( identity=identity, last_id=None, offset=(offset + count) if last_result else offset, ) next_token = serializer.dumps(next_token_payload, header_fields={ 'v': 1 }).decode('ascii') output = dict( results=results, pagination=dict( nextToken=next_token, count=len(results), offset=offset + 1, total=total, more=bool(has_next_result), page=offset // count + 1, ), ) return output, 200
class OpenIDConnect(object): """ The core OpenID Connect client object. """ def __init__(self, app=None, credentials_store=None, httpFactory=None, time=None, urandom=None): logger.debug('__init__') self.credentials_store = credentials_store\ if credentials_store is not None\ else MemoryCredentials() if httpFactory is not None: if callable(httpFactory): logger.debug('custom httpFactory installed') self.httpFactory = httpFactory else: raise Exception('The httpFactory argument must be a function') else: self.httpFactory = lambda: httplib2.Http() if time is not None: warn('time argument is deprecated and unused', DeprecationWarning) if urandom is not None: warn('urandom argument is deprecated and unused', DeprecationWarning) # By default, we do not have a custom callback self._custom_callback = None # get stuff from the app's config, which may override stuff set above if app is not None: self.init_app(app) def init_app(self, app): """ Do setup that requires a Flask app. :param app: The application to initialize. :type app: Flask """ secrets_cache = None secrets = self.load_secrets(app) if secrets is not None: self.client_secrets = list(secrets.values())[0] secrets_cache = DummySecretsCache(secrets) else: self.client_secrets = dict() if 'OIDC_PROVIDER' in app.config: self.client_secrets['issuer'] = app.config['OIDC_PROVIDER'] # Set some default configuration options app.config.setdefault('OIDC_SCOPES', ['openid', 'email']) app.config.setdefault('OIDC_GOOGLE_APPS_DOMAIN', None) app.config.setdefault('OIDC_ID_TOKEN_COOKIE_NAME', 'oidc_id_token') app.config.setdefault('OIDC_ID_TOKEN_COOKIE_PATH', '/') app.config.setdefault('OIDC_ID_TOKEN_COOKIE_TTL', 7 * 86400) # 7 days # should ONLY be turned off for local debugging app.config.setdefault('OIDC_COOKIE_SECURE', True) app.config.setdefault( 'OIDC_VALID_ISSUERS', (self.client_secrets.get('issuer') or self.client_secrets.get('op_uri') or GOOGLE_ISSUERS)) app.config.setdefault('OIDC_PROVIDER', self.client_secrets.get('op_uri')) app.config.setdefault('OIDC_CLOCK_SKEW', 60) # 1 minute app.config.setdefault('OIDC_REQUIRE_VERIFIED_EMAIL', False) app.config.setdefault('OIDC_OPENID_REALM', None) app.config.setdefault('OIDC_USER_INFO_ENABLED', True) app.config.setdefault('OIDC_CALLBACK_ROUTE', '/oidc_callback') app.config.setdefault('OVERWRITE_REDIRECT_URI', False) app.config.setdefault("OIDC_EXTRA_REQUEST_AUTH_PARAMS", {}) # Configuration for resource servers app.config.setdefault('OIDC_RESOURCE_SERVER_ONLY', False) # 'online' (token introspection) or 'offline' (token validation) app.config.setdefault('OIDC_RESOURCE_SERVER_VALIDATION_MODE', 'online') app.config.setdefault('OIDC_RESOURCE_CHECK_AUD', False) # We use client_secret_post, because that's what the Google # oauth2client library defaults to app.config.setdefault('OIDC_INTROSPECTION_AUTH_METHOD', 'client_secret_post') app.config.setdefault('OIDC_TOKEN_TYPE_HINT', 'access_token') if 'openid' not in app.config['OIDC_SCOPES']: raise ValueError('The value "openid" must be in the OIDC_SCOPES') # register callback route and cookie-setting decorator if not app.config['OIDC_RESOURCE_SERVER_ONLY']: app.route(app.config['OIDC_CALLBACK_ROUTE'])(self._oidc_callback) app.before_request(self._before_request) app.after_request(self._after_request) # Initialize oauth2client if secrets_cache is not None: self.flow = flow_from_clientsecrets( app.config['OIDC_CLIENT_SECRETS'], scope=app.config['OIDC_SCOPES'], cache=secrets_cache) assert isinstance(self.flow, OAuth2WebServerFlow) # create signers using the Flask secret key self.extra_data_serializer = JSONWebSignatureSerializer( app.config['SECRET_KEY'], salt='flask-oidc-extra-data') self.cookie_serializer = JSONWebSignatureSerializer( app.config['SECRET_KEY'], salt='flask-oidc-cookie') try: self.credentials_store = app.config['OIDC_CREDENTIALS_STORE'] except KeyError: pass def load_secrets(self, app): # Load client_secrets.json to pre-initialize some configuration if 'OIDC_CLIENT_SECRETS' not in app.config: return None # Could not load client_secrets content = app.config['OIDC_CLIENT_SECRETS'] if isinstance(content, dict): return content else: return _json_loads(open(content, 'r').read()) @property def user_loggedin(self): """ Represents whether the user is currently logged in. Returns: bool: Whether the user is logged in with Flask-OIDC. .. versionadded:: 1.0 """ return g.oidc_id_token is not None def user_getfield(self, field, access_token=None): """ Request a single field of information about the user. :param field: The name of the field requested. :type field: str :returns: The value of the field. Depending on the type, this may be a string, list, dict, or something else. :rtype: object .. versionadded:: 1.0 """ if access_token is None and self._access_token: access_token = self._access_token info = self.user_getinfo([field], access_token) return info.get(field) def user_getinfo(self, fields, access_token=None): """ Request multiple fields of information about the user. :param fields: The names of the fields requested. :type fields: list :returns: The values of the current user for the fields requested. The keys are the field names, values are the values of the fields as indicated by the OpenID Provider. Note that fields that were not provided by the Provider are absent. :rtype: dict :raises Exception: If the user was not authenticated. Check this with user_loggedin. .. versionadded:: 1.0 """ if access_token is None and self._access_token: access_token = self._access_token if g.oidc_id_token is None and access_token is None: raise Exception('User was not authenticated') info = {} all_info = None for field in fields: if access_token is None and field in g.oidc_id_token: info[field] = g.oidc_id_token[field] elif current_app.config['OIDC_USER_INFO_ENABLED']: # This was not in the id_token. Let's get user information if all_info is None: all_info = self._retrieve_userinfo(access_token) if all_info is None: # To make sure we don't retry for every field all_info = {} if field in all_info: info[field] = all_info[field] else: # We didn't get this information pass return info def get_access_token(self): """Method to return the current requests' access_token. :returns: Access token or None :rtype: str .. versionadded:: 1.2 """ try: if self._access_token: logger.debug("Found api access token") return self._access_token credentials = OAuth2Credentials.from_json( self.credentials_store[g.oidc_id_token['sub']]) logger.debug("Getting access token from credential store") logger.debug(str(credentials.to_json())) access_token = credentials.access_token if not self.validate_token(access_token): raise AccessTokenCredentialsError( "The access_token is expired or invalid and can't be refreshed." ) return access_token except (KeyError, AccessTokenCredentialsError) as e: logger.debug("Expired ID token, credentials missing", exc_info=True) if not self.is_api_request(): return self.redirect_to_auth_server(request.url) return None def get_refresh_token(self): """Method to return the current requests' refresh_token. :returns: Access token or None :rtype: str .. versionadded:: 1.2 """ try: credentials = OAuth2Credentials.from_json( self.credentials_store[g.oidc_id_token['sub']]) return credentials.refresh_token except KeyError: logger.debug("Expired ID token, credentials missing", exc_info=True) return None def _retrieve_userinfo(self, access_token=None): """ Requests extra user information from the Provider's UserInfo and returns the result. :returns: The contents of the UserInfo endpoint. :rtype: dict """ if 'userinfo_uri' not in self.client_secrets: logger.debug('Userinfo uri not specified') raise AssertionError('UserInfo URI not specified') # Cache the info from this request if '_oidc_userinfo' in g: return g._oidc_userinfo http = self.httpFactory() if access_token is None and self._access_token: access_token = self._access_token if access_token is None: try: credentials = OAuth2Credentials.from_json( self.credentials_store[g.oidc_id_token['sub']]) except KeyError: logger.debug("Expired ID token, credentials missing", exc_info=True) return None credentials.authorize(http) resp, content = http.request(self.client_secrets['userinfo_uri']) else: # We have been manually overriden with an access token resp, content = http.request( self.client_secrets['userinfo_uri'], "POST", headers={'Authorization': 'Bearer %s' % access_token}) info = _json_loads(content) if access_token and hasattr(g, 'oidc_token_info'): info.update(g.oidc_token_info) g._oidc_userinfo = info logger.debug('Retrieved user info: %s' % info) return info def get_cookie_or_token(self): """ .. deprecated:: 1.0 Use :func:`user_getinfo` instead. """ warn( 'You are using a deprecated function (get_cookie_or_token). ' 'Please reconsider using this', DeprecationWarning) return self._get_cookie_or_token() def _get_cookie_or_token(self): try: id_token_cookie = request.cookies.get( current_app.config['OIDC_ID_TOKEN_COOKIE_NAME']) if not id_token_cookie: # Do not error if we were unable to get the cookie. # The user can debug this themselves. token = None if 'Authorization' in request.headers and request.headers[ 'Authorization'].startswith('Bearer '): token = request.headers['Authorization'].split( None, 1)[1].strip() if 'access_token' in request.form: token = request.form['access_token'] elif 'access_token' in request.args: token = request.args['access_token'] if token: logger.debug("Found token: %s" % token) self._api_request = True issuer = current_app.config['OIDC_PROVIDER'] if issuer is None: raise Exception( 'No \'op_uri\' defined in client_secrets or OIDC_PROVIDER set.' ) try: logger.debug("Cookie not found trying token %s" % token) payload = self.validate_token(token) #, issuer=issuer, audience=self.client_secrets['redirect_uris'], client_ids=self.client_secrets['client_id']) self._access_token = token self._retrieve_userinfo() return None except Exception as e: logger.debug("Error using token") logger.debug(str(e)) abort(401) return None else: cookie = self.cookie_serializer.loads(id_token_cookie) logger.debug("Cookie : %s" % cookie) return cookie except SignatureExpired: logger.debug("Invalid ID token cookie", exc_info=True) return None except BadSignature: logger.info("Signature invalid for ID token cookie", exc_info=True) return None def set_cookie_id_token(self, id_token): """ .. deprecated:: 1.0 """ warn( 'You are using a deprecated function (set_cookie_id_token). ' 'Please reconsider using this', DeprecationWarning) return self._set_cookie_id_token(id_token) def _set_cookie_id_token(self, id_token): """ Cooperates with @after_request to set a new ID token cookie. """ g.oidc_id_token = id_token g.oidc_id_token_dirty = True def _after_request(self, response): """ Set a new ID token cookie if the ID token has changed. """ # This means that if either the new or the old are False, we set # insecure cookies. # We don't define OIDC_ID_TOKEN_COOKIE_SECURE in init_app, because we # don't want people to find it easily. if self._api_request: self._access_token = None return response cookie_secure = (current_app.config['OIDC_COOKIE_SECURE'] and current_app.config.get( 'OIDC_ID_TOKEN_COOKIE_SECURE', True)) if getattr(g, 'oidc_id_token_dirty', False): if g.oidc_id_token: signed_id_token = self.cookie_serializer.dumps(g.oidc_id_token) response.set_cookie( current_app.config['OIDC_ID_TOKEN_COOKIE_NAME'], signed_id_token, secure=cookie_secure, httponly=True, max_age=current_app.config['OIDC_ID_TOKEN_COOKIE_TTL']) else: # This was a log out response.set_cookie( current_app.config['OIDC_ID_TOKEN_COOKIE_NAME'], '', path=current_app.config['OIDC_ID_TOKEN_COOKIE_PATH'], secure=cookie_secure, httponly=True, expires=0) return response def _before_request(self): self._api_request = False self._access_token = None g.oidc_id_token = None self.authenticate_or_redirect() def authenticate_or_redirect(self): """ Helper function suitable for @app.before_request and @check. Sets g.oidc_id_token to the ID token if the user has successfully authenticated, else returns a redirect object so they can go try to authenticate. :returns: A redirect object, or None if the user is logged in. :rtype: Redirect .. deprecated:: 1.0 Use :func:`require_login` instead. """ # the auth callback and error pages don't need user to be authenticated if request.endpoint in frozenset(['_oidc_callback', '_oidc_error']): return None self._api_request = False # retrieve signed ID token cookie id_token = self._get_cookie_or_token() logger.debug("Get ID Token %s" % id_token) if id_token is None: logger.debug("ID Token is NONE") if self._api_request: logger.debug("API Request, skipping further steps") return None return self.redirect_to_auth_server(request.url) # ID token expired # when Google is the IdP, this happens after one hour if time.time() >= id_token['exp']: # get credentials from store try: credentials = OAuth2Credentials.from_json( self.credentials_store[id_token['sub']]) logger.debug(str(credentials.to_json())) except KeyError: logger.debug("Expired ID token, credentials missing", exc_info=True) return self.redirect_to_auth_server(request.url) # refresh and store credentials try: credentials.refresh(self.httpFactory()) if credentials.id_token: id_token = credentials.id_token else: # It is not guaranteed that we will get a new ID Token on # refresh, so if we do not, let's just update the id token # expiry field and reuse the existing ID Token. if credentials.token_expiry is None: logger.debug('Expired ID token, no new expiry. Falling' ' back to assuming 1 hour') id_token['exp'] = time.time() + 3600 else: id_token['exp'] = calendar.timegm( credentials.token_expiry.timetuple()) self.credentials_store[id_token['sub']] = credentials.to_json() print(self.credentials_store[id_token['sub']]) if not self._is_id_token_valid(id_token): raise AccessTokenCredentialsError( "The access_token is expired or invalid and can't be refreshed." ) self._set_cookie_id_token(id_token) except (AccessTokenRefreshError, AccessTokenCredentialsError) as e: # Can't refresh. Wipe credentials and redirect user to IdP # for re-authentication. logger.debug("Expired ID token, can't refresh credentials", exc_info=True) del self.credentials_store[id_token['sub']] return self.redirect_to_auth_server(request.url) # make ID token available to views g.oidc_id_token = id_token self._retrieve_userinfo() return None def require_login(self, view_func): """ Use this to decorate view functions that require a user to be logged in. If the user is not already logged in, they will be sent to the Provider to log in, after which they will be returned. .. versionadded:: 1.0 This was :func:`check` before. """ @wraps(view_func) def decorated(*args, **kwargs): if g.oidc_id_token is None and self._access_token is None: return self.redirect_to_auth_server(request.url) return view_func(*args, **kwargs) return decorated # Backwards compatibility check = require_login """ .. deprecated:: 1.0 Use :func:`require_login` instead. """ def require_keycloak_role(self, client, role): """ Function to check for a KeyCloak client role in JWT access token. This is intended to be replaced with a more generic 'require this value in token or claims' system, at which point backwards compatibility will be added. .. versionadded:: 1.5.0 """ def wrapper(view_func): @wraps(view_func) def decorated(*args, **kwargs): pre, tkn, post = self.get_access_token().split('.') access_token = json.loads(b64decode(tkn)) if role in access_token['resource_access'][client]['roles']: return view_func(*args, **kwargs) else: return abort(403) return decorated return wrapper def flow_for_request(self): """ .. deprecated:: 1.0 Use :func:`require_login` instead. """ warn( 'You are using a deprecated function (flow_for_request). ' 'Please reconsider using this', DeprecationWarning) return self._flow_for_request() def _flow_for_request(self): """ Build a flow with the correct absolute callback URL for this request. :return: """ flow = copy(self.flow) redirect_uri = current_app.config['OVERWRITE_REDIRECT_URI'] if not redirect_uri: flow.redirect_uri = url_for('_oidc_callback', _external=True) else: flow.redirect_uri = redirect_uri return flow def redirect_to_auth_server(self, destination=None, customstate=None): """ Set a CSRF token in the session, and redirect to the IdP. :param destination: The page that the user was going to, before we noticed they weren't logged in. :type destination: Url to return the client to if a custom handler is not used. Not available with custom callback. :param customstate: The custom data passed via the ODIC state. Note that this only works with a custom_callback, and this will ignore destination. :type customstate: Anything that can be serialized :returns: A redirect response to start the login process. :rtype: Flask Response .. deprecated:: 1.0 Use :func:`require_login` instead. """ if self._api_request: logger.debug("API Request skipping redirect and aborting with 401") abort(401) if not self._custom_callback and customstate: raise ValueError('Custom State is only avilable with a custom ' 'handler') if 'oidc_csrf_token' not in session: csrf_token = urlsafe_b64encode(os.urandom(24)).decode('utf-8') session['oidc_csrf_token'] = csrf_token state = { 'csrf_token': session['oidc_csrf_token'], } statefield = 'destination' statevalue = destination if customstate is not None: statefield = 'custom' statevalue = customstate state[statefield] = self.extra_data_serializer.dumps( statevalue).decode('utf-8') extra_params = { 'state': urlsafe_b64encode(json.dumps(state).encode('utf-8')), } extra_params.update( current_app.config['OIDC_EXTRA_REQUEST_AUTH_PARAMS']) if current_app.config['OIDC_GOOGLE_APPS_DOMAIN']: extra_params['hd'] = current_app.config['OIDC_GOOGLE_APPS_DOMAIN'] if current_app.config['OIDC_OPENID_REALM']: extra_params['openid.realm'] = current_app.config[ 'OIDC_OPENID_REALM'] flow = self._flow_for_request() auth_url = '{url}&{extra_params}'.format( url=flow.step1_get_authorize_url(), extra_params=urlencode(extra_params)) # if the user has an ID token, it's invalid, or we wouldn't be here self._set_cookie_id_token(None) return redirect(auth_url) def is_api_request(self): return self._api_request def _is_id_token_valid(self, id_token): """ Check if `id_token` is a current ID token for this application, was issued by the Apps domain we expected, and that the email address has been verified. @see: http://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation """ if not id_token: return False # step 2: check issuer if id_token['iss'] not in current_app.config['OIDC_VALID_ISSUERS']: logger.error('id_token issued by non-trusted issuer: %s' % id_token['iss']) return False if isinstance(id_token['aud'], list): # step 3 for audience list if self.flow.client_id not in id_token['aud']: logger.error('We are not a valid audience') return False # step 4 if 'azp' not in id_token and len(id_token['aud']) > 1: logger.error('Multiple audiences and not authorized party') return False else: # step 3 for single audience if id_token['aud'] != self.flow.client_id: logger.error('We are not the audience') return False # step 5 if 'azp' in id_token and id_token['azp'] != self.flow.client_id: logger.error('Authorized Party is not us') return False # step 6-8: TLS checked # step 9: check exp if int(time.time()) >= int(id_token['exp']): logger.error('Token has expired') return False # step 10: check iat if id_token['iat'] < (time.time() - current_app.config['OIDC_CLOCK_SKEW']): logger.error('Token issued in the past') return False # (not required if using HTTPS?) step 11: check nonce # step 12-13: not requested acr or auth_time, so not needed to test # additional steps specific to our usage if current_app.config['OIDC_GOOGLE_APPS_DOMAIN'] and \ id_token.get('hd') != current_app.config[ 'OIDC_GOOGLE_APPS_DOMAIN']: logger.error('Invalid google apps domain') return False if not id_token.get('email_verified', False) and \ current_app.config['OIDC_REQUIRE_VERIFIED_EMAIL']: logger.error('Email not verified') return False return True WRONG_GOOGLE_APPS_DOMAIN = 'WRONG_GOOGLE_APPS_DOMAIN' def custom_callback(self, view_func): """ Wrapper function to use a custom callback. The custom OIDC callback will get the custom state field passed in with redirect_to_auth_server. """ @wraps(view_func) def decorated(*args, **kwargs): plainreturn, data = self._process_callback('custom') if plainreturn: return data else: return view_func(data, *args, **kwargs) self._custom_callback = decorated return decorated def _oidc_callback(self): plainreturn, data = self._process_callback('destination') if plainreturn: return data else: return redirect(data) def _process_callback(self, statefield): """ Exchange the auth code for actual credentials, then redirect to the originally requested page. """ # retrieve session and callback variables try: session_csrf_token = session.get('oidc_csrf_token') state = _json_loads( urlsafe_b64decode(request.args['state'].encode('utf-8'))) csrf_token = state['csrf_token'] code = request.args['code'] except (KeyError, ValueError): logger.debug("Can't retrieve CSRF token, state, or code", exc_info=True) return True, self._oidc_error() # check callback CSRF token passed to IdP # against session CSRF token held by user if csrf_token != session_csrf_token: logger.debug("CSRF token mismatch") return True, self._oidc_error() # make a request to IdP to exchange the auth code for OAuth credentials flow = self._flow_for_request() credentials = flow.step2_exchange(code) id_token = credentials.id_token if not self._is_id_token_valid(id_token): logger.debug("Invalid ID token") if id_token.get( 'hd') != current_app.config['OIDC_GOOGLE_APPS_DOMAIN']: return True, self._oidc_error( "You must log in with an account from the {0} domain.". format(current_app.config['OIDC_GOOGLE_APPS_DOMAIN']), self.WRONG_GOOGLE_APPS_DOMAIN) return True, self._oidc_error() # store credentials by subject # when Google is the IdP, the subject is their G+ account number self.credentials_store[id_token['sub']] = credentials.to_json() # Retrieve the extra statefield data try: response = self.extra_data_serializer.loads(state[statefield]) except BadSignature: logger.error('State field was invalid') return True, self._oidc_error() # set a persistent signed cookie containing the ID token # and redirect to the final destination self._set_cookie_id_token(id_token) return False, response def _oidc_error(self, message='Not Authorized', code=None): return (message, 401, { 'Content-Type': 'text/plain', }) def logout(self): """ Request the browser to please forget the cookie we set, to clear the current session. Note that as described in [1], this will not log out in the case of a browser that doesn't clear cookies when requested to, and the user could be automatically logged in when they hit any authenticated endpoint. [1]: https://github.com/puiterwijk/flask-oidc/issues/5#issuecomment-86187023 .. versionadded:: 1.0 """ # TODO: Add single logout self._set_cookie_id_token(None) # Below here is for resource servers to validate tokens def validate_token(self, token, scopes_required=None): """ This function can be used to validate tokens. Note that this only works if a token introspection url is configured, as that URL will be queried for the validity and scopes of a token. :param scopes_required: List of scopes that are required to be granted by the token before returning True. :type scopes_required: list :returns: True if the token was valid and contained the required scopes. An ErrStr (subclass of string for which bool() is False) if an error occured. :rtype: Boolean or String .. versionadded:: 1.1 """ valid = self._validate_token(token, scopes_required) if valid is True: return True else: return ErrStr(valid) def _validate_token(self, token, scopes_required=None): """The actual implementation of validate_token.""" if scopes_required is None: scopes_required = [] scopes_required = set(scopes_required) token_info = None valid_token = False has_required_scopes = False if token: try: token_info = self._get_token_info(token) except Exception as ex: token_info = {'active': False} logger.error('ERROR: Unable to get token info') logger.error(str(ex)) valid_token = token_info.get('active', False) if 'aud' in token_info and \ current_app.config['OIDC_RESOURCE_CHECK_AUD']: valid_audience = False aud = token_info['aud'] clid = current_app.config.get('OIDC_CLIENT_ID') if not clid: clid = self.client_secrets.get('client_id') if not clid: raise Exception( 'No \'client_id\' defined in client_secrets or OIDC_CLIENT_ID set.' ) if isinstance(aud, list): valid_audience = clid in aud else: valid_audience = clid == aud if not valid_audience: logger.error('Refused token because of invalid ' 'audience') valid_token = False if valid_token: token_scopes = token_info.get('scope', '').split(' ') else: token_scopes = [] has_required_scopes = scopes_required.issubset(set(token_scopes)) if not has_required_scopes: logger.debug('Token missed required scopes') if (valid_token and has_required_scopes): g.oidc_token_info = token_info return True if not valid_token: return 'Token required but invalid' elif not has_required_scopes: return 'Token does not have required scopes' else: return 'Something went wrong checking your token' def accept_token(self, require_token=False, scopes_required=None, render_errors=True): """ Use this to decorate view functions that should accept OAuth2 tokens, this will most likely apply to API functions. Tokens are accepted as part of the query URL (access_token value) or a POST form value (access_token). Note that this only works if a token introspection url is configured, as that URL will be queried for the validity and scopes of a token. :param require_token: Whether a token is required for the current function. If this is True, we will abort the request if there was no token provided. :type require_token: bool :param scopes_required: List of scopes that are required to be granted by the token before being allowed to call the protected function. :type scopes_required: list :param render_errors: Whether or not to eagerly render error objects as JSON API responses. Set to False to pass the error object back unmodified for later rendering. :type render_errors: callback(obj) or None .. versionadded:: 1.0 """ def wrapper(view_func): @wraps(view_func) def decorated(*args, **kwargs): token = None if 'Authorization' in request.headers and request.headers[ 'Authorization'].startswith('Bearer '): token = request.headers['Authorization'].split( None, 1)[1].strip() if 'access_token' in request.form: token = request.form['access_token'] elif 'access_token' in request.args: token = request.args['access_token'] validity = self.validate_token(token, scopes_required) if (validity is True) or (not require_token): return view_func(*args, **kwargs) else: response_body = { 'error': 'invalid_token', 'error_description': validity } if render_errors: response_body = json.dumps(response_body) return response_body, 401, {'WWW-Authenticate': 'Bearer'} return decorated return wrapper def _get_token_info(self, token): validation_mode = current_app.config[ 'OIDC_RESOURCE_SERVER_VALIDATION_MODE'] clock_skew_seconds = current_app.config['OIDC_CLOCK_SKEW'] if validation_mode == 'online': # We hardcode to use client_secret_post, because that's what the Google # oauth2client library defaults to request = {'token': token} headers = {'Content-type': 'application/x-www-form-urlencoded'} hint = current_app.config['OIDC_TOKEN_TYPE_HINT'] if hint != 'none': request['token_type_hint'] = hint auth_method = current_app.config['OIDC_INTROSPECTION_AUTH_METHOD'] if (auth_method == 'client_secret_basic'): basic_auth_string = '%s:%s' % ( self.client_secrets['client_id'], self.client_secrets['client_secret']) basic_auth_bytes = bytearray(basic_auth_string, 'utf-8') headers['Authorization'] = 'Basic %s' % b64encode( basic_auth_bytes).decode('utf-8') elif (auth_method == 'bearer'): headers['Authorization'] = 'Bearer %s' % token elif (auth_method == 'client_secret_post'): request['client_id'] = self.client_secrets['client_id'] if self.client_secrets['client_secret'] is not None: request['client_secret'] = self.client_secrets[ 'client_secret'] resp, content = self.httpFactory().request( self.client_secrets['token_introspection_uri'], 'POST', urlencode(request), headers=headers) # TODO: Cache this reply return _json_loads(content) elif validation_mode == 'offline': issuer = current_app.config['OIDC_PROVIDER'] if issuer is None: raise Exception( 'No \'op_uri\' defined in client_secrets or OIDC_PROVIDER set.' ) payload = validate_token( token, issuer=issuer, audience=self.client_secrets['redirect_uris'], client_ids=self.client_secrets['client_id']) payload['active'] = True # Fake introspection response return payload else: raise Exception( 'OIDC_RESOURCE_SERVER_VALIDATION_MODE must be set to either \'online\' or \'offline\'' )
class OpenIDConnect(object): """ The core OpenID Connect client object. """ def __init__(self, app=None, credentials_store=None, http=None, time=None, urandom=None): self.credentials_store = credentials_store\ if credentials_store is not None\ else MemoryCredentials() if http is not None: warn('HTTP argument is deprecated and unused', DeprecationWarning) if time is not None: warn('time argument is deprecated and unused', DeprecationWarning) if urandom is not None: warn('urandom argument is deprecated and unused', DeprecationWarning) # get stuff from the app's config, which may override stuff set above if app is not None: self.init_app(app) def init_app(self, app): """ Do setup that requires a Flask app. :param app: The application to initialize. :type app: Flask """ # Load client_secrets.json to pre-initialize some configuration secrets = _json_loads( open(app.config['OIDC_CLIENT_SECRETS'], 'r').read()) self.client_secrets = list(secrets.values())[0] # Set some default configuration options app.config.setdefault('OIDC_SCOPES', ['openid', 'email']) app.config.setdefault('OIDC_GOOGLE_APPS_DOMAIN', None) app.config.setdefault('OIDC_ID_TOKEN_COOKIE_NAME', 'oidc_id_token') app.config.setdefault('OIDC_ID_TOKEN_COOKIE_TTL', 7 * 86400) # 7 days # should ONLY be turned off for local debugging app.config.setdefault('OIDC_COOKIE_SECURE', True) app.config.setdefault( 'OIDC_VALID_ISSUERS', (self.client_secrets.get('issuer') or GOOGLE_ISSUERS)) app.config.setdefault('OIDC_CLOCK_SKEW', 60) # 1 minute app.config.setdefault('OIDC_REQUIRE_VERIFIED_EMAIL', False) app.config.setdefault('OIDC_OPENID_REALM', None) app.config.setdefault('OIDC_USER_INFO_ENABLED', True) app.config.setdefault('OIDC_CALLBACK_ROUTE', '/oidc_callback') app.config.setdefault('OVERWRITE_REDIRECT_URI', False) # Configuration for resource servers app.config.setdefault('OIDC_RESOURCE_SERVER_ONLY', False) app.config.setdefault('OIDC_RESOURCE_CHECK_AUD', False) # We use client_secret_post, because that's what the Google # oauth2client library defaults to app.config.setdefault('OIDC_INTROSPECTION_AUTH_METHOD', 'client_secret_post') # register callback route and cookie-setting decorator if not app.config['OIDC_RESOURCE_SERVER_ONLY']: app.route(app.config['OIDC_CALLBACK_ROUTE'])(self._oidc_callback) app.before_request(self._before_request) app.after_request(self._after_request) # Initialize oauth2client self.flow = flow_from_clientsecrets(app.config['OIDC_CLIENT_SECRETS'], scope=app.config['OIDC_SCOPES']) assert isinstance(self.flow, OAuth2WebServerFlow) # create signers using the Flask secret key self.destination_serializer = JSONWebSignatureSerializer( app.config['SECRET_KEY']) self.cookie_serializer = TimedJSONWebSignatureSerializer( app.config['SECRET_KEY']) try: self.credentials_store = app.config['OIDC_CREDENTIALS_STORE'] except KeyError: pass @property def user_loggedin(self): """ Represents whether the user is currently logged in. Returns: bool: Whether the user is logged in with Flask-OIDC. .. versionadded:: 1.0 """ return g.oidc_id_token is not None def user_getfield(self, field, access_token=None): """ Request a single field of information about the user. :param field: The name of the field requested. :type field: str :returns: The value of the field. Depending on the type, this may be a string, list, dict, or something else. :rtype: object .. versionadded:: 1.0 """ info = self.user_getinfo([field], access_token) return info.get(field) def user_getinfo(self, fields, access_token=None): """ Request multiple fields of information about the user. :param fields: The names of the fields requested. :type fields: list :returns: The values of the current user for the fields requested. The keys are the field names, values are the values of the fields as indicated by the OpenID Provider. Note that fields that were not provided by the Provider are absent. :rtype: dict :raises Exception: If the user was not authenticated. Check this with user_loggedin. .. versionadded:: 1.0 """ if g.oidc_id_token is None and access_token is None: raise Exception('User was not authenticated') info = {} all_info = None for field in fields: if access_token is None and field in g.oidc_id_token: info[field] = g.oidc_id_token[field] elif current_app.config['OIDC_USER_INFO_ENABLED']: # This was not in the id_token. Let's get user information if all_info is None: all_info = self._retrieve_userinfo(access_token) if all_info is None: # To make sure we don't retry for every field all_info = {} if field in all_info: info[field] = all_info[field] else: # We didn't get this information pass return info def _retrieve_userinfo(self, access_token=None): """ Requests extra user information from the Provider's UserInfo and returns the result. :returns: The contents of the UserInfo endpoint. :rtype: dict """ if 'userinfo_uri' not in self.client_secrets: logger.debug('Userinfo uri not specified') raise AssertionError('UserInfo URI not specified') # Cache the info from this request if '_oidc_userinfo' in g: return g._oidc_userinfo http = httplib2.Http() if access_token is None: try: credentials = OAuth2Credentials.from_json( self.credentials_store[g.oidc_id_token['sub']]) except KeyError: logger.debug("Expired ID token, credentials missing", exc_info=True) return None credentials.authorize(http) resp, content = http.request(self.client_secrets['userinfo_uri']) else: # We have been manually overriden with an access token resp, content = http.request( self.client_secrets['userinfo_uri'], "POST", body=urlencode({"access_token": access_token}), headers={'Content-Type': 'application/x-www-form-urlencoded'}) logger.debug('Retrieved user info: %s' % content) info = _json_loads(content) g._oidc_userinfo = info return info def get_cookie_id_token(self): """ .. deprecated:: 1.0 Use :func:`user_getinfo` instead. """ warn( 'You are using a deprecated function (get_cookie_id_token). ' 'Please reconsider using this', DeprecationWarning) return self._get_cookie_id_token() def _get_cookie_id_token(self): try: id_token_cookie = request.cookies.get( current_app.config['OIDC_ID_TOKEN_COOKIE_NAME']) if not id_token_cookie: # Do not error if we were unable to get the cookie. # The user can debug this themselves. return None return self.cookie_serializer.loads(id_token_cookie) except SignatureExpired: logger.debug("Invalid ID token cookie", exc_info=True) return None def set_cookie_id_token(self, id_token): """ .. deprecated:: 1.0 """ warn( 'You are using a deprecated function (set_cookie_id_token). ' 'Please reconsider using this', DeprecationWarning) return self._set_cookie_id_token(id_token) def _set_cookie_id_token(self, id_token): """ Cooperates with @after_request to set a new ID token cookie. """ g.oidc_id_token = id_token g.oidc_id_token_dirty = True def _after_request(self, response): """ Set a new ID token cookie if the ID token has changed. """ # This means that if either the new or the old are False, we set # insecure cookies. # We don't define OIDC_ID_TOKEN_COOKIE_SECURE in init_app, because we # don't want people to find it easily. cookie_secure = (current_app.config['OIDC_COOKIE_SECURE'] and current_app.config.get( 'OIDC_ID_TOKEN_COOKIE_SECURE', True)) if getattr(g, 'oidc_id_token_dirty', False): if g.oidc_id_token: signed_id_token = self.cookie_serializer.dumps(g.oidc_id_token) response.set_cookie( current_app.config['OIDC_ID_TOKEN_COOKIE_NAME'], signed_id_token, secure=cookie_secure, httponly=True, max_age=current_app.config['OIDC_ID_TOKEN_COOKIE_TTL']) else: # This was a log out response.set_cookie( current_app.config['OIDC_ID_TOKEN_COOKIE_NAME'], '', secure=cookie_secure, httponly=True, expires=0) return response def _before_request(self): g.oidc_id_token = None self.authenticate_or_redirect() def authenticate_or_redirect(self): """ Helper function suitable for @app.before_request and @check. Sets g.oidc_id_token to the ID token if the user has successfully authenticated, else returns a redirect object so they can go try to authenticate. :returns: A redirect object, or None if the user is logged in. :rtype: Redirect .. deprecated:: 1.0 Use :func:`require_login` instead. """ # the auth callback and error pages don't need user to be authenticated if request.endpoint in frozenset(['_oidc_callback', '_oidc_error']): return None # retrieve signed ID token cookie id_token = self._get_cookie_id_token() if id_token is None: return self.redirect_to_auth_server(request.url) # ID token expired # when Google is the IdP, this happens after one hour if time.time() >= id_token['exp']: # get credentials from store try: credentials = OAuth2Credentials.from_json( self.credentials_store[id_token['sub']]) except KeyError: logger.debug("Expired ID token, credentials missing", exc_info=True) return self.redirect_to_auth_server(request.url) # refresh and store credentials try: credentials.refresh(httplib2.Http()) if credentials.id_token: id_token = credentials.id_token else: # It is not guaranteed that we will get a new ID Token on # refresh, so if we do not, let's just update the id token # expiry field and reuse the existing ID Token. if credentials.token_expiry is None: logger.debug('Expired ID token, no new expiry. Falling' ' back to assuming 1 hour') id_token['exp'] = time.time() + 3600 else: id_token['exp'] = calendar.timegm( credentials.token_expiry.timetuple()) self.credentials_store[id_token['sub']] = credentials.to_json() self._set_cookie_id_token(id_token) except AccessTokenRefreshError: # Can't refresh. Wipe credentials and redirect user to IdP # for re-authentication. logger.debug("Expired ID token, can't refresh credentials", exc_info=True) del self.credentials_store[id_token['sub']] return self.redirect_to_auth_server(request.url) # make ID token available to views g.oidc_id_token = id_token return None def validate_url_request(self): """ Validate url using IS params: sub - subjectId (uid) uri - local uri domain - domain name (or ip) with port scheme - site scheme (http(s)) method - request method (GET, POST, etc.) from - clientId to - clientId """ ISS = g.oidc_id_token['iss'] #$_SERVER['HTTP_X_USER_ISS'] try: domain = request.environ['HTTP_HOST'] sub = g.oidc_id_token['sub'] uri = request.environ['PATH_INFO'] #request.url_rule# scheme = request.scheme method = request.method from_ = request.environ.get('HTTP_X_DATA_FROM', g.oidc_id_token['aud']) to_ = g.oidc_id_token['aud'] #$idp_info->getClientId(); except (KeyError, ValueError): logger.debug("Can't retrieve some keys", exc_info=True) params = { 'sub': sub, 'domain': domain, 'uri': uri, 'scheme': scheme, 'method': method, 'from': from_, 'to': to_ } handle = ISS + "/Validate/ValidatePermissionOnUrl" try: r = requests.post(handle, params=params) # Check for 403 (file not found). if (r.status_code == 403): return False except requests.ConnectionError: return False return True def require_login(self, view_func): """ Use this to decorate view functions that require a user to be logged in. If the user is not already logged in, they will be sent to the Provider to log in, after which they will be returned. .. versionadded:: 1.0 This was :func:`check` before. """ @wraps(view_func) def decorated(*args, **kwargs): if g.oidc_id_token is None: return self.redirect_to_auth_server(request.url) if not self.validate_url_request(): return self._oidc_forbidden() return view_func(*args, **kwargs) return decorated # Backwards compatibility check = require_login """ .. deprecated:: 1.0 Use :func:`require_login` instead. """ def flow_for_request(self): """ .. deprecated:: 1.0 Use :func:`require_login` instead. """ warn( 'You are using a deprecated function (flow_for_request). ' 'Please reconsider using this', DeprecationWarning) return self._flow_for_request() def _flow_for_request(self): """ Build a flow with the correct absolute callback URL for this request. :return: """ flow = copy(self.flow) redirect_uri = current_app.config['OVERWRITE_REDIRECT_URI'] if not redirect_uri: flow.redirect_uri = url_for('_oidc_callback', _external=True) else: flow.redirect_uri = redirect_uri return flow def getServerHTTPS(self): https = request.environ.get('HTTPS', '') return https != '' and https != "off" def redirect_to_auth_server(self, destination): """ Set a CSRF token in the session, and redirect to the IdP. :param destination: The page that the user was going to, before we noticed they weren't logged in. :type destination: str :returns: A redirect response to start the login process. :rtype: Redirect .. deprecated:: 1.0 Use :func:`require_login` instead. """ destination = self.destination_serializer.dumps(destination).decode( 'utf-8') csrf_token = b64encode(os.urandom(24)).decode('utf-8') session['oidc_csrf_token'] = csrf_token state = { 'csrf_token': csrf_token, 'destination': destination, } extra_params = { 'state': json.dumps(state), } if current_app.config['OIDC_GOOGLE_APPS_DOMAIN']: extra_params['hd'] = current_app.config['OIDC_GOOGLE_APPS_DOMAIN'] if current_app.config['OIDC_OPENID_REALM']: extra_params['openid.realm'] = current_app.config[ 'OIDC_OPENID_REALM'] flow = self._flow_for_request() # try get proxy info protocol = 'https' if self.getServerHTTPS() else 'http' hostname = request.environ.get('HTTP_X_DATA_HOST', '') port = request.environ.get('HTTP_X_DATA_SERVER_PORT', '') proxy_path = request.environ.get('HTTP_X_DATA_REDIRECT_LOCATION', '') if (hostname != ''): flow.redirect_uri = protocol + "://" + hostname + ":" + port + proxy_path + '/oidc_callback' redirect_uri = flow.step1_get_authorize_url() auth_url = '{url}&{extra_params}'.format( url=redirect_uri, extra_params=urlencode(extra_params)) # if the user has an ID token, it's invalid, or we wouldn't be here self._set_cookie_id_token(None) return redirect(auth_url) def _is_id_token_valid(self, id_token): """ Check if `id_token` is a current ID token for this application, was issued by the Apps domain we expected, and that the email address has been verified. @see: http://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation """ if not id_token: return False # step 2: check issuer if id_token['iss'] not in current_app.config['OIDC_VALID_ISSUERS']: logger.error('id_token issued by non-trusted issuer: %s' % id_token['iss']) return False if isinstance(id_token['aud'], list): # step 3 for audience list if self.flow.client_id not in id_token['aud']: logger.error('We are not a valid audience') return False # step 4 if 'azp' not in id_token: logger.error('Multiple audiences and not authorized party') return False else: # step 3 for single audience if id_token['aud'] != self.flow.client_id: logger.error('We are not the audience') return False # step 5 if 'azp' in id_token and id_token['azp'] != self.flow.client_id: logger.error('Authorized Party is not us') return False # step 6-8: TLS checked # step 9: check exp if int(time.time()) >= int(id_token['exp']): logger.error('Token has expired') return False # step 10: check iat if id_token['iat'] < (time.time() - current_app.config['OIDC_CLOCK_SKEW']): logger.error('Token issued in the past') return False # (not required if using HTTPS?) step 11: check nonce # step 12-13: not requested acr or auth_time, so not needed to test # additional steps specific to our usage if current_app.config['OIDC_GOOGLE_APPS_DOMAIN'] and \ id_token.get('hd') != current_app.config[ 'OIDC_GOOGLE_APPS_DOMAIN']: logger.error('Invalid google apps domain') return False if not id_token.get('email_verified', False) and \ current_app.config['OIDC_REQUIRE_VERIFIED_EMAIL']: logger.error('Email not verified') return False return True WRONG_GOOGLE_APPS_DOMAIN = 'WRONG_GOOGLE_APPS_DOMAIN' def _oidc_callback(self): """ Exchange the auth code for actual credentials, then redirect to the originally requested page. """ # retrieve session and callback variables try: session_csrf_token = session.pop('oidc_csrf_token') state = _json_loads(request.args['state']) csrf_token = state['csrf_token'] destination = state['destination'] code = request.args['code'] except (KeyError, ValueError): logger.debug("Can't retrieve CSRF token, state, or code", exc_info=True) return self._oidc_error() # check callback CSRF token passed to IdP # against session CSRF token held by user if csrf_token != session_csrf_token: logger.debug("CSRF token mismatch") return self._oidc_error() # make a request to IdP to exchange the auth code for OAuth credentials flow = self._flow_for_request() credentials = flow.step2_exchange(code) id_token = credentials.id_token if not self._is_id_token_valid(id_token): logger.debug("Invalid ID token") if id_token.get( 'hd') != current_app.config['OIDC_GOOGLE_APPS_DOMAIN']: return self._oidc_error( "You must log in with an account from the {0} domain.". format(current_app.config['OIDC_GOOGLE_APPS_DOMAIN']), self.WRONG_GOOGLE_APPS_DOMAIN) return self._oidc_error() # store credentials by subject # when Google is the IdP, the subject is their G+ account number self.credentials_store[id_token['sub']] = credentials.to_json() # Check whether somebody messed with the destination destination = destination try: response = redirect(self.destination_serializer.loads(destination)) except BadSignature: logger.error('Destination signature did not match. Rogue IdP?') response = redirect('/') # set a persistent signed cookie containing the ID token # and redirect to the final destination self._set_cookie_id_token(id_token) return response def _oidc_error(self, message='Not Authorized', code=None): return (message, 401, { 'Content-Type': 'text/plain', }) def _oidc_forbidden(self, message='Access Denied', code=None): return (message, 403, { 'Content-Type': 'text/plain', }) def logout(self): """ Request the browser to please forget the cookie we set, to clear the current session. Note that as described in [1], this will not log out in the case of a browser that doesn't clear cookies when requested to, and the user could be automatically logged in when they hit any authenticated endpoint. [1]: https://github.com/puiterwijk/flask-oidc/issues/5#issuecomment-86187023 .. versionadded:: 1.0 """ # TODO: Add single logout self._set_cookie_id_token(None) # Below here is for resource servers to validate tokens def validate_token(self, token, scopes_required=None): """ This function can be used to validate tokens. Note that this only works if a token introspection url is configured, as that URL will be queried for the validity and scopes of a token. :param scopes_required: List of scopes that are required to be granted by the token before returning True. :type scopes_required: list :returns: True if the token was valid and contained the required scopes. A string if an error occured. :rtype: Boolean or String .. versionadded:: 1.1 """ if scopes_required is None: scopes_required = [] scopes_required = set(scopes_required) token_info = None valid_token = False has_required_scopes = False if token: try: token_info = self._get_token_info(token) except Exception as ex: token_info = {'active': False} logger.error('ERROR: Unable to get token info') logger.error(str(ex)) valid_token = token_info.get('active', False) if 'aud' in token_info and \ current_app.config['OIDC_RESOURCE_CHECK_AUD']: valid_audience = False aud = token_info['aud'] clid = self.client_secrets['client_id'] if isinstance(aud, list): valid_audience = clid in aud else: valid_audience = clid == aud if not valid_audience: logger.error('Refused token because of invalid ' 'audience') valid_token = False if valid_token: token_scopes = token_info.get('scope', '').split(' ') else: token_scopes = [] has_required_scopes = scopes_required.issubset(set(token_scopes)) if not has_required_scopes: logger.debug('Token missed required scopes') if (valid_token and has_required_scopes): g.oidc_token_info = token_info return True if not valid_token: return 'Token required but invalid' elif not has_required_scopes: return 'Token does not have required scopes' else: return 'Something went wrong checking your token' def accept_token(self, require_token=False, scopes_required=None, render_errors=True): """ Use this to decorate view functions that should accept OAuth2 tokens, this will most likely apply to API functions. Tokens are accepted as part of the query URL (access_token value) or a POST form value (access_token). Note that this only works if a token introspection url is configured, as that URL will be queried for the validity and scopes of a token. :param require_token: Whether a token is required for the current function. If this is True, we will abort the request if there was no token provided. :type require_token: bool :param scopes_required: List of scopes that are required to be granted by the token before being allowed to call the protected function. :type scopes_required: list :param render_errors: Whether or not to eagerly render error objects as JSON API responses. Set to False to pass the error object back unmodified for later rendering. :type render_errors: callback(obj) or None .. versionadded:: 1.0 """ def wrapper(view_func): @wraps(view_func) def decorated(*args, **kwargs): token = None if 'Authorization' in request.headers and request.headers[ 'Authorization'].startswith('Bearer '): token = request.headers['Authorization'].split()[1].strip() if 'access_token' in request.form: token = request.form['access_token'] elif 'access_token' in request.args: token = request.args['access_token'] validity = self.validate_token(token, scopes_required) if (validity is True) or (not require_token): return view_func(*args, **kwargs) else: response_body = { 'error': 'invalid_token', 'error_description': validity } if render_errors: response_body = json.dumps(response_body) return response_body, 401, {'WWW-Authenticate': 'Bearer'} return decorated return wrapper def _get_token_info(self, token): # We hardcode to use client_secret_post, because that's what the Google # oauth2client library defaults to request = {'token': token, 'token_type_hint': 'Bearer'} headers = {'Content-type': 'application/x-www-form-urlencoded'} auth_method = current_app.config['OIDC_INTROSPECTION_AUTH_METHOD'] if (auth_method == 'client_secret_basic'): basic_auth_string = '%s:%s' % ( self.client_secrets['client_id'], self.client_secrets['client_secret']) basic_auth_bytes = bytearray(basic_auth_string, 'utf-8') headers['Authorization'] = 'Basic %s' % b64encode(basic_auth_bytes) elif (auth_method == 'bearer'): headers['Authorization'] = 'Bearer %s' % token elif (auth_method == 'client_secret_post'): request['client_id'] = self.client_secrets['client_id'] request['client_secret'] = self.client_secrets['client_secret'] resp, content = httplib2.Http().request( self.client_secrets['token_introspection_uri'], 'POST', urlencode(request), headers=headers) # TODO: Cache this reply return _json_loads(content)
def gage_new_samples(gid): """ Submit new samples to gage *id* Parameters: id (int): Primary id key number of a gage Samples are formatted in body of request as a JSON Web Signature using the ``Gage.key`` Example sample submitter: :: from itsdangerous import JSONWebSignatureSerializer import requests payload = {'samples':[ {'type':'level', 'value':16.7, 'datetime': str(datetime.datetime.now()) }, {'type':'amps', 'value':367.3, 'datetime': str(datetime.datetime.now()) }, {'type':'voltage', 'value':14.3, 'datetime': str(datetime.datetime.now()) }, {'type':'discharge', 'value':480, 'datetime': str(datetime.datetime.now()) } ], 'gage':{ 'id':5 }} s = JSONWebSignatureSerializer('<gage.key>') def submit(payload): data = s.dumps(payload) url = "http://riverflo.ws/api/1.0/gages/<gage.id>/sample" r = requests.post(url, data=data) if r.status_code is 200: return True else: return False If the key matches the stored key for the gage id in the url, \ the server will iterate over the samples and add them to the database \ creating new sensors for the gage if a new sample type is found. \ Then the server will return JSON with a status code of 200. Example response: :: { 'gage': {u'id': 5, 'location': 'Androscoggin River downstream of I-95 in Auburn ME', 'name': 'Androscoggin River at Auburn', 'url': 'http://riverflo.ws/api/1.0/gages/5'}, 'result': 'created', 'samples': [{'datetime': 'Tue, 04 Nov 2014 20:43:39 GMT', 'id': 10781, 'url': 'http://riverflo.ws/api/1.0/samples/10781', 'value': 16.7}, {'datetime': 'Tue, 04 Nov 2014 20:43:39 GMT', 'id': 10782, 'url': 'http://riverflo.ws/api/1.0/samples/10782', 'value': 367.3}, {'datetime': 'Tue, 04 Nov 2014 20:43:39 GMT', 'id': 10783, 'url': 'http://riverflo.ws/api/1.0/samples/10783', 'value': 14.3}, {'datetime': 'Tue, 04 Nov 2014 20:43:39 GMT', 'id': 10784, 'url': 'http://riverflo.ws/api/1.0/samples/10784', 'value': 480.0}]} If the signature does not match, the server will return JSON \ with a status code of 401 - Unauthorized: :: {'error': 'unauthorized', 'message': 'bad signature'} """ gage = Gage.query.get_or_404(gid) s = JSONWebSignatureSerializer(gage.key) try: req_json = s.loads(request.data) except BadSignature: # If the signature doesn't match return unauthorized('bad signature') except TypeError: # Return unauthorized if no key defined for gage return unauthorized('bad signature') else: samples = req_json['samples'] output = [] print(samples) for sample in samples: result = gage.new_sample(stype=sample['type'].lower(), value=sample['value'], sdatetime=sample['datetime']) result_json = result.to_sensor_json() print(result_json) result_json['sender_id'] = sample['sender_id'] print(result_json) output.append(result_json) return jsonify({ 'gage': gage.to_json(), 'samples': output, 'result': 'created' })
# from ...app.secure import SECRET_KEY from app.models.user import User SECRET_KEY = 'fjsaiofias[w]ewq.eq.eqe,qkojfaljiojsni323922jfodsjfspkfpsvn,x.newejejj092nlc[[l.oo;;q' # url = 'http://0.0.0.0:5000/reset/password' # token = User.generate_token() s = Serializer(SECRET_KEY, '600') # print(s.dumps({'id': 100}).decode('utf-8')) token = s.dumps({'id': 100}).decode('utf-8') print(f"token:{token}") # token=token+'frnak' s2 = Serializer(SECRET_KEY, '600') info = s2.loads(token.encode('utf-8')) print(info) # print(token) # url = url+ token # print(url) # data = requests.post(url) # print(data) if __name__ == '__main__': pass
def unsign(self, value): s = JSONWebSignatureSerializer(self.secret_key) try: return s.loads(value) except BadSignature: return {}
''' @Author: yeshan333 @Date: 2020-03-14 17:58:23 @GitHub: https://github.com/yeshan333 @Contact: [email protected] @License: @LastEditTime: 2020-03-14 20:22:33 @Description: ''' from itsdangerous.serializer import Serializer from itsdangerous import JSONWebSignatureSerializer s = Serializer("secret-key") print(s.dumps([1, 2, 3, 4])) # itsdangerous.exc.BadSignature: Signature b'r7R9RhGgDPvvWl3iNzLuIIfELmo' does not match # print(s.loads('[1, 2, 3].r7R9RhGgDPvvWl3iNzLuIIfELmo')) jws = JSONWebSignatureSerializer("secret-key") print(jws.dumps(0, header_fields={"v": 1})) print( jws.loads( "eyJ2IjoxLCJhbGciOiJIUzUxMiJ9.MA.JO6MA9EEyQPvf_IbRuP6zT9Sa2KjKOxub2ssCpgHCj_ZqsOkCxlFvp1QebNuDHhz8UHiO1EbAG80HrQQyJD7CA", return_header=True))