def generate_token(a_username,a_password): if valid_lpirc_session(a_username) is None: create_lpirc_session(a_username) else: # Session already created, nothing to do send previously generated token if recycle_db != 1: sess = Session.query.filter_by(username=a_username).first() token = sess.token return token else: # Recycle # Should be a penalty for multiple login attempts delete_lpirc_session(a_username) create_lpirc_session(a_username) sess = Session.query.filter_by(username=a_username).first() s = JSONWebSignatureSerializer(server_secret_key) dt = sess.timestamp dt_str = dt.strftime(datetime_format) print dt_str # Adding session created time for timeout validation token = s.dumps({ff_username: a_username, ff_password: a_password, ff_timestamp: dt_str}) # Update session database sess.token = token if (enable_powermeter == 1) and (a_username != powermeter_user): sess.powermeter_status = powermeter_status_stop # So that, it can be restarted later if a_username == powermeter_user: sess.mytimeout = sys.maxint db.session.commit() return token
def confirm_login(): # Check for state and for 0 errors state = session.get('oauth2_state') if not state or request.values.get('error'): return redirect(url_for('index')) # Fetch token discord = make_session(state=state) discord_token = discord.fetch_token( TOKEN_URL, client_secret=OAUTH2_CLIENT_SECRET, authorization_response=request.url) if not discord_token: return redirect(url_for('index')) # Fetch the user user = get_user(discord_token) # Generate api_key from user_id serializer = JSONWebSignatureSerializer(app.config['SECRET_KEY']) api_key = str(serializer.dumps({'user_id': user['id']})) # Store api_key db.set('user:{}:api_key'.format(user['id']), api_key) # Store token db.set('user:{}:discord_token'.format(user['id']), json.dumps(discord_token)) # Store api_token in client session api_token = { 'api_key': api_key, 'user_id': user['id'] } session.permanent = True session['api_token'] = api_token return redirect(url_for('select_server'))
def generate_token(user): secret_key = get_secret_key() s = JSONWebSignatureSerializer(secret_key) expired_at = utcnow().replace(hours=+2).timestamp d = {'user': user, 'expired_at': expired_at} token = s.dumps(d).decode('utf-8') return token, d
def hmac(payload, private_key): '''Generate a decryptable signature on the server side that the client can analzye. Server generated HMAC signatures aren't timed. ''' payload = organize_payload(payload) t = Token(secret_key=private_key) return t.dumps(payload)
def generate_token(self): JWT = JWS(os.environ.get('SECRET_KEY')) return JWT.dumps({ "id": self.id, "username": self.username, # 2 hrs expiration "expires": (datetime.datetime.now() + datetime.timedelta(seconds=2*60*60)).timestamp() }).decode('utf-8')
def af_reset_send(cls_, email, returnurl): user = cls_.find_first({'email': email}) if not user: raise ApiValidationError(ValidationError('Unknown email')) serializer = JSONWebSignatureSerializer(current_app.config['API_KEY']) resettoken = serializer.dumps({"id": user.id, "signature": user.signature}).decode('utf-8') content = 'Click here to reset your password: %s' % (returnurl + resettoken) mailer.send(user.email, user.name, 'Password reset link', content) return {'success': True}
def test_invalid_token(f_session, f_user): secret_key = get_secret_key() s = JSONWebSignatureSerializer(secret_key) expired_at = arrow.utcnow().replace(hours=-2).timestamp d = { 'user': {'id': f_user.id, 'email': f_user.email, 'name': f_user.name}, 'expired_at': expired_at } token = s.dumps(d).decode('utf-8') with raises(InvalidTokenError): t = validate_token('dadfa.asdf') with raises(ExpiredTokenError): t = validate_token(token)
def post(self): serializer = JSONWebSignatureSerializer( current_app.config['API_KEY']) args = validator.validate(self.get_request()) user = models.User.find_first({'email': args['email']}) if user is None or user.password != args['password']: raise ApiError("Wrong email or password provided for {}".format(args['email'])) if user.status is not const.STATUS_ACTIVE: raise ApiError("User is not active") token = serializer.dumps({"id": user.id, "signature": user.signature}).decode('utf-8') return self.respond({"token": token, "user": user.jsonify(user)})
def generate_csrf_token(): nonce = os.urandom(16) secret = session.setdefault('_csrf_secret', os.urandom(16)) nonce_int = bytes_to_int(nonce) secret_int = bytes_to_int(secret) jsw = JSONWebSignatureSerializer(app.secret_key) token = jsw.dumps({ "n": b64encode(nonce), "k": b64encode(int_to_bytes(nonce_int ^ secret_int)) }) return token
def post(self, request): username = request.body.get("username", "") password = request.body.get("password", "") user = user_actions.get_by_username_and_password(username, password) if not user: return responses.BadRequest({"error": "Invalid username or password"}) else: token_data = {"user_id": user.id} serializer = JSONWebSignatureSerializer(settings.SECRET_KEY) token = serializer.dumps(token_data).decode("ascii") result = to_plain(user, ignore_fields=["id", "password"]) result["token"] = token return responses.Ok(result)
def voucher(): test_dict = { 'email': '*****@*****.**', 'card': '123456789012345678901234567890', 'charity': 20, 'content': 80, 'amount': 25 } s = JSONWebSignatureSerializer('secret', algorithm_name='HS512') res = s.dumps(test_dict) print(res) l = s.loads(res) print(l) # send_mail('*****@*****.**', 'dasa' ,'asdasfadfada') # voucher()
def generate_token(a_username,a_password): if valid_lpirc_session(a_username) is None: create_lpirc_session(a_username) else: # Should be a penalty for multiple login attempts delete_lpirc_session(a_username) create_lpirc_session(a_username) sess = Session.query.filter_by(username=a_username).first() s = JSONWebSignatureSerializer(server_secret_key) dt = sess.timestamp dt_str = dt.strftime(datetime_format) print dt_str # Adding session created time for timeout validation token = s.dumps({ff_username: a_username, ff_password: a_password, ff_timestamp: dt_str}) return token
def create_load_more_url(kwargs): ''' create the url for load more button in event listing page arg: kwargs: kwargs contain the different type of data return: url for the load more button ''' s = PageSerializer(config.SECRET_KEY) # check for the cur is present or not # if cur is present that means load more button request # in that case page number will increment # othewise new request the assign page no 2 try: # get previous page number prev = get_event_page_no(kwargs['cur']) page = prev + 1 except: page = 2 kwargs['cur'] = s.dumps(page) return url_for('event', **kwargs)
def patch(self, request, username): user = load_user(username) check_user_is_self(request.user, user) validator = user_validators.UserForUpdateValidator(request.body) if validator.is_valid(): user = user_actions.update_user( user, user_entities.UserForUpdate(**validator.cleaned_data) ) token_data = {"user_id": user.id} serializer = JSONWebSignatureSerializer(settings.SECRET_KEY) token = serializer.dumps(token_data).decode("ascii") result = to_plain(user, ignore_fields=["id", "password"]) result["token"] = token return responses.Ok(result) else: return responses.BadRequest(validator.errors)
def generate_token(): """ Access Token """ access_token_serializer = TimedJSONWebSignatureSerializer( app.config['SECRET_KEY'], expires_in=app.config['EXPIRES_IN']) """ Secret Token """ secret_token_serializer = JSONWebSignatureSerializer( app.config['SECRET_KEY']) d = {'username': g.user.username, 'password': g.user.password} d_access = d.copy() d_access.update({'secret': False}) d_secret = d.copy() d_secret.update({'secret': True}) access_token = access_token_serializer.dumps(d_access) secret_token = secret_token_serializer.dumps(d_secret) return access_token, secret_token
def get_token(): try: email = request.form['email'] password = request.form['password'] user = User.query.filter_by(email=email).first() if not user: return False else: if user.email != email: return False elif user.password != password: return False else: s = JSONWebSignatureSerializer('secret_key') token = s.dumps({'user_id': user.id}) user.token = token user.token_expiration = time.time() + 600 db.session.commit() session['logged_in'] = token return token except ValueError as e: app.logger.debug(e.message) return False
def confirm_login(): log.info("Checking login....") # Check for state and for 0 errors state = session.get('oauth2_state') if not state or request.values.get('error'): return redirect(url_for('index')) # Fetch token discord = utils.make_session(state=state) discord_token = discord.fetch_token( data_info.TOKEN_URL, client_secret=data_info.OAUTH2_CLIENT_SECRET, authorization_response=request.url) if not discord_token: log.info("Not clear, returning") return redirect(url_for('index')) # Fetch the user user = utils.get_user(discord_token) # Generate api_key from user_id serializer = JSONWebSignatureSerializer(app.config['SECRET_KEY']) api_key = str(serializer.dumps({'user_id': user['id']})) # Store api_key db.set('user:{}:api_key'.format(user['id']), api_key) # Store token db.set('user:{}:discord_token'.format(user['id']), json.dumps(discord_token)) # Store api_token in client session api_token = { 'api_key': api_key, 'user_id': user['id'] } session.permanent = True session['api_token'] = api_token log.info("Clear, redirect...") if data_info.last_path: return redirect(data_info.last_path) return redirect(url_for('after_login'))
def generate_auth_token(self): s = JSONSerializer(current_app.config['SECRET_KEY']) return s.dumps({'id': self.id}).decode('utf-8')
def generate_auth_token(self): s = Serializer(current_app.config['SECRET_KEY']) return s.dumps({'id': self.id}).decode('ascii')
def generate_api_key(self): s = JSONWebSignatureSerializer(current_app.config['SECRET_KEY']) self.apikey = s.dumps({ 'user': str(self.id), 'time': time(), })
def generate_token(self): s = JSONWebSignatureSerializer(app_config.mwdb.secret_key) return s.dumps({"login": self.user.login, "api_key_id": str(self.id)})
def sign(self, value): if isinstance(value, bytes): value = value.decode("utf-8") s = JSONWebSignatureSerializer(self.secret_key) return s.dumps(value)
class SupOAuth(object): AUTH_HEADER_KEY = 'Authorization' AUTH_HEADER_PREFIX = 'Bearer' RANDOM_STRING_EXPIRES_IN = 60 * 10 DEFAULT_EXT_EXPIRES_IN = 3600 * 24 * 1 - 1 DEFAULT_HEADERS = {'content-type': 'application/json'} def __init__(self, ext_key, ext_secret, secret_key, expires_in, api_uri, token_uri, redirect_uri): expires_in = int(expires_in.total_seconds()) or \ self.DEFAULT_EXT_EXPIRES_IN self._s = TimedSerializer( secret_key=secret_key, expires_in=expires_in ) self._r = TimedSerializer( secret_key=secret_key, expires_in=self.RANDOM_STRING_EXPIRES_IN ) self._f = Serializer( secret_key=secret_key ) self.ext_key = ext_key self.ext_secret = ext_secret self.api_uri = api_uri self.token_uri = token_uri self.redirect_uri = redirect_uri def generate_ext_token(self, open_id): try: open_id = unicode(open_id) token = self._s.dumps(open_id).decode('utf-8') except Exception as e: raise self.OAuthInvalidExtToken(str(e)) return token def encrypt(self, code): try: code = unicode(code) code = self._f.dumps(code).decode('utf-8') except Exception as e: raise self.OAuthEncryptionFailed(str(e)) return code def decrypt(self, code): try: code = self._f.loads(code) except Exception as e: raise self.OAuthDecryptionFailed(str(e)) return code def load_ext_token(self, headers): auth = headers.get(self.AUTH_HEADER_KEY) if not auth: return None parts = auth.split() if parts[0].lower() != self.AUTH_HEADER_PREFIX.lower(): return None try: token = parts[1] open_id = self._s.loads(token) except Exception: return None return open_id def make_random_string(self, open_id): try: open_id = unicode(open_id) random_string = self._r.dumps(open_id).decode('utf-8') except Exception as e: raise self.OAuthInvalidRandomString(str(e)) return random_string def match_random_string(self, random_string, target_string): try: payload = self._r.loads(random_string) except Exception: payload = None return payload == target_string def get_access_token(self, code): payloads = { 'ext_key': self.ext_key, 'ext_secret': self.ext_secret, 'code': code, 'grant_type': 'code', 'redirect_uri': self.redirect_uri } headers = self.DEFAULT_HEADERS try: r = requests.post(self.token_uri, data=json.dumps(payloads), headers=headers) result = r.json() assert isinstance(result, dict) except Exception as e: raise self.OAuthInvalidAccessToken(str(e)) result['access_token'] = self.encrypt(result['access_token']) result['refresh_token'] = self.encrypt(result['refresh_token']) return result def refresh_access_token(self, refresh_token): payloads = { 'ext_key': self.ext_key, 'ext_secret': self.ext_secret, 'refresh_token': self.decrypt(refresh_token), 'grant_type': "refresh_token", } headers = self.DEFAULT_HEADERS try: r = requests.post(self.token_uri, data=payloads, headers=headers) result = r.json() assert isinstance(result, dict) except: raise self.OAuthInvalidRefreshToken result['access_token'] = self.encrypt(result['access_token']) result['refresh_token'] = self.encrypt(result['refresh_token']) return result def request(self, method, url, access_token, **kwargs): headers = self.DEFAULT_HEADERS headers.update({ self.AUTH_HEADER_KEY: '{} {}'.format(self.AUTH_HEADER_PREFIX, self.decrypt(access_token)) }) if 'headers' not in kwargs: kwargs['headers'] = {} kwargs['headers'].update(headers) try: r = requests.request(method, url, **kwargs) result = r.json() assert isinstance(result, dict) assert r.status_code < 400 except Exception as e: if r.status_code == 401: raise self.OAuthInvalidAccessToken elif r.status_code >= 400: err_msg = json.dumps(r.json()) else: err_msg = str(e) raise self.OAuthInvalidRequest(err_msg) return result def get_profile(self, access_token): try: url = '{}/oauth/profile'.format(self.api_uri) except Exception as e: raise self.OAuthInvalidParams(str(e)) return self.request('GET', url, access_token) def get_member(self, member_open_id, access_token): try: url = '{}/crm/oauth/member/{}'.format(self.api_uri, member_open_id) except Exception as e: raise self.OAuthInvalidParams(str(e)) return self.request('GET', url, access_token) def logout(self, access_token): url = '{}/oauth/access_logout'.format(self.api_uri) return self.request('DELETE', url, access_token) # exceptions class OAuthException(Exception): status_message = 'oauth_error' def __init__(self, message=None): self.affix_message = message def __str__(self): return '{}:{}'.format(self.status_message, self.affix_message) class OAuthInvalidAccessToken(OAuthException): status_message = 'OAUTH_INVALID_ACCESS_TOKEN' class OAuthInvalidRefreshToken(OAuthException): status_message = 'OAUTH_INVALID_REFRESH_TOKEN' class OAuthInvalidExtToken(OAuthException): status_message = 'OAUTH_INVALID_EXT_TOKEN' class OAuthInvalidRandomString(OAuthException): status_message = 'OAUTH_INVALID_RANDOM_STRING' class OAuthInvalidRequest(OAuthException): status_message = 'OAUTH_INVALID_REQUEST' class OAuthInvalidParams(OAuthException): status_message = 'OAUTH_INVALID_PARAMS' class OAuthDecryptionFailed(OAuthException): status_message = 'OAUTH_DECRYPTION_FAILED' class OAuthEncryptionFailed(OAuthException): status_message = 'OAUTH_ENCRYPTION_FAILED'
def test(): s = Serializer('ttttt') return jsonify(s.dumps({"id": '123'}))
import time from itsdangerous import JSONWebSignatureSerializer apiKey = 'my_api_key' secret = 'my_api_secret' s = JSONWebSignatureSerializer(secret, algorithm_name='HS512') payload = {"apiKey": apiKey, "timestamp": int(time.time())} token = s.dumps(payload).decode()
def create_auth_token(self): s = Serializer(SECRET_KEY) return s.dumps({'id': self.id})
def generate_auth_token(self): """generate a token""" s = Serializer(current_app.config['SECRET_KEY']) return s.dumps({'id': self.id})
def gen_tn_proxy_token(app, client_id): s = JSONWebSignatureSerializer(app.config['SECRET_KEY']) token = s.dumps({ 'client_id': client_id }) return token
def maketoken(self): s = Serializer(app.config['SECRET_KEY']) token = s.dumps({'user_id': self.id}) return token
def generate_auth_token(self): s = Serializer(app.config['SECRET_KEY']) return s.dumps(self.email)
def generate_api_token(self): s = Serializer(current_app.config['SECRET_KEY'], salt=datetime.now().strftime('%Y-%m-%d %H:%M:%S')) self.api_token = s.dumps({'id': self.id})
def get_verification_token(self): s = Serializer(current_app.config['SECRET_KEY']) return s.dumps({'user_id': self.id}).decode('utf-8')
def encrypt_api_key(user_id, secret_key): '''Encrypts a user id into an api key (token). The generated api key has no expiration time.''' s = Serializer(secret_key) return s.dumps({'id': user_id, 'salt': generate_salt()})
def get_shared_token(self): s = InfinitSerializer(current_app.config['SECRET_KEY']) return s.dumps({'post_id': self.id}).decode('utf-8')#Esto nos devuelve el token a partir de la clave secreta, para ello le
def generate_auth_token(self): s = JSONWebSignatureSerializer(app.config['SECRET_KEY']) return s.dumps({"id": self.id})
class planItDb(): def __init__(self, secret): self.sig = JSONWebSignatureSerializer(secret) self.log = logging.getLogger("planItDb") def init_db(self, app): self.mongo = PyMongo() self.mongo.init_app(app) def log_access(self, idinfo, request, response): #ensure we have the user in the user db curs = self.mongo.db.users.find({'sub':idinfo['sub']}).limit(1) if curs.count() == 0: self.log.info("Add user to db: {}".format(idinfo)) # need to make a copy because mongo adds its objectid, whcih # cannot be seralized into the session. d = idinfo.copy() d['created'] = datetime.datetime.utcnow() d['remote_addr'] = [request.remote_addr] self.mongo.db.users.insert(d) else: dbuser = curs.next() if request.remote_addr not in dbuser['remote_addr']: self.mongo.db.users.update( {'_id': dbuser['_id']}, {'$push': {'remote_addr': request.remote_addr}} ) self.mongo.db.accesses.insert( { 'sub': idinfo['sub'], 'headers': dict(request.headers), 'args': {x:request.args[x] for x in request.args.keys()}, 'time': datetime.datetime.utcnow(), 'remote': request.remote_addr, 'url': request.url, 'status': response.status_code, 'length': response.content_length }) def validate_key(self, key, remote_addr): doc = self.mongo.db.keys.find({'signature': key}).next() if doc == None: return False if 'active' not in doc: return False op = { '$inc': {'use_info.use_count': 1}, '$set': {'use_info.last_used': datetime.datetime.utcnow()} } if remote_addr not in doc['use_info']['remotes']: op['$push'] = {'use_info.remotes': remote_addr} self.mongo.db.keys.update({'_id': doc['_id']}, op) return doc['active'] def enable_key(self, idinfo, signature, remote_addr): return self.mongo.db.keys.update( {'sub': idinfo['sub'], 'signature': signature}, {'$set': {'active': True}} ) def disable_key(self, idinfo, signature, remote_addr): return self.mongo.db.keys.update( {'sub': idinfo['sub'], 'signature': signature}, {'$set': {'active': False}} ) def delete_key(self, idinfo, signature, remote_addr): self.mongo.db.users.update( {"sub": idinfo['sub']}, {'$push': {'deleted_keys': {'when': datetime.datetime.utcnow(), 'remote_addr': remote_addr, 'signature': signature}} } ) return self.mongo.db.keys.remove( {'sub': idinfo['sub'], 'signature': signature}) def get_or_create_webkey(self, idinfo, remote_addr): "look for a webkey, reutrn if found, else create one and return it" curs = self.mongo.db.keys.find( {'sub': idinfo['sub'], 'web': True, 'active':True}, {'_id': False}) if curs.count() > 0: return curs.next() return self.create_key(idinfo, remote_addr, web=True) def create_key(self, idinfo, remote_addr, web=False): "create a new key for the user with idinfo (jwt)" keydoc = { 'sub': idinfo['sub'], 'created': datetime.datetime.utcnow(), 'create_address': remote_addr, 'use_info': {'use_count': 0, 'remotes': [remote_addr]}, 'active': True, 'web': web, 'signature': [0*64] # dummy data } self.mongo.db.keys.insert(keydoc) # do the insert to get the _id, then compute the signature on the id # store the doc id, docid = keydoc['_id'] del keydoc['_id'] keydoc['signature'] = self.sig.dumps(str(docid)).decode('utf-8') self.log.info("signature is: {}".format(keydoc['signature'])) self.mongo.db.keys.update( {"_id": docid}, {'$set': {'signature': keydoc['signature']}} ) self.mongo.db.users.update( {"sub": idinfo['sub']}, {'$push': {'created_keys': {'when': datetime.datetime.utcnow(), 'remote_addr': remote_addr, 'signature': keydoc['signature']}}} ) return keydoc def list_keys(self, idinfo): keys = list(self.mongo.db.keys.find( {'sub': idinfo['sub']}, {'_id': False})) self.log.info("got keys: {}".format(keys)) return keys
def generate_auth_token(self, expiration = 3600): s = Serializer(app.config['SECRET_KEY']) str = s.dumps({'id': self.id}) return b64encode(str).decode('utf-8')
class TokenAuthenticator(Component): implements(IAuthenticator, IPreferencePanelProvider, ITemplateProvider) secret_key = Option('sage_trac', 'secret_key', doc='Secret key to use for signing tokens; ensure ' 'that this is well protected.') def __init__(self): super(TokenAuthenticator, self).__init__() if self.secret_key: self._serializer = JSONWebSignatureSerializer(self.secret_key) else: self.log.warning('No secret key configured for token-based ' 'authentication in {}.'.format( self.__class__.__name__)) self._serializer = None # ITemplateProvider methods def get_htdocs_dirs(self): return [] def get_templates_dirs(self): return [resource_filename(__name__, 'templates')] # IPreferencePanelProvider methods def get_preference_panels(self, req): if req.authname and req.authname != 'anonymous': yield 'token', 'Token' def render_preference_panel(self, req, panel): token = self.create_token(req.authname) return 'prefs_token.html', {'token': token} # IAuthenticator methods def authenticate(self, req): username = self._check_token(req) if username: req.environ['REMOTE_USER'] = username return username def create_token(self, authname): return self._serializer.dumps(authname) def verify_token(self, token): try: username = self._serializer.loads(token) except BadSignature: username = None return username def _check_token(self, req): if not self._serializer: return None header = req.get_header('Authorization') if not header: self.log.debug("No authorization header; skipping token auth") return None try: scheme, token = header.split(None, 1) except ValueError: # Malformed Authorization header; bail self.log.debug("Malformed authorization header; " "skipping token auth") return None if scheme.lower() != 'bearer': self.log.debug("Unrecognized authorization scheme; " "skipping token auth") return None username = self.verify_token(token) if username is not None: self.log.debug("Successful token auth for user {}".format( username)) return username
def sign(self, value): """ json web 签名 """ if isinstance(value, bytes): value = value.decode('utf-8') s = JSONWebSignatureSerializer(self.secret_key) return str(s.dumps(value), encoding='utf-8')
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) # By default, we do not have a custom callback self._custom_callback = None # Keycloak is not enabled by default self.keycloak_enabled = False # 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", {}) # 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') app.config.setdefault("OIDC_KEYCLOAK_ENABLED", False) 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) # 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') if app.config["OIDC_KEYCLOAK_ENABLED"]: keycloak_secrets = self.load_keycloak_secrets(app) self.keycloakApi = KeycloakAPI() self.keycloakApi.init_app(keycloak_secrets) self.keycloak_enabled = True self._keycloak_realm_roles = None self._keycloak_client_roles = None self._rpt_token = None self.current_uri = None 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: with open(content, 'r') as c: return _json_loads(c.read()) def load_keycloak_secrets(self, app): # Load client_secrets.json to pre-initialize some configuration content = app.config['OIDC_KEYCLOAK_CLIENT_SECRETS'] if isinstance(content, dict): return content else: with open(content, 'r') as c: return _json_loads(c.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: # user is not logged in if not self.user_loggedin: return None 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 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 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): # 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=401): return (message, code, { '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, roles_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 :param roles_required: List of roles that are required to be present in the token before returning True :type roles_required: list :returns: True if the token was valid and contained the required scopes and roles. 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, roles_required) if valid is True: return True else: return ErrStr(valid) def _validate_token(self, token, scopes_required=None, roles_required=None): """The actual implementation of validate_token.""" if scopes_required is None: scopes_required = [] if roles_required is None: roles_required = [] scopes_required = set(scopes_required) roles_required = set(roles_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(' ') realm_roles = token_info.get('realm_access', {}).get('roles', []) else: token_scopes = [] realm_roles = [] logger.debug(f"Token is not marked as active. Token info: {token_info}") has_required_scopes = scopes_required.issubset(set(token_scopes)) has_required_roles = roles_required.issubset(set(realm_roles)) if not has_required_scopes: logger.debug(f'Token missed required scopes. Scopes present: {token_scopes}, ' f'scopes required: {scopes_required}') if not has_required_roles: logger.debug(f'Token missed required roles. Roles present: {realm_roles}, ' f'roles required: {roles_required}') if valid_token and has_required_scopes and has_required_roles: 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, auth_header_key=None): """ 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 auth_header_key: Key in headers which contains the Auth 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): self._set_current_uri(request.script_root + request.path) token = self._extract_access_token(request, auth_header_key) validity = self.validate_token(token, scopes_required) if (validity is True) or (not require_token): return view_func(*args, **kwargs) else: return self._deny_access(validity, render_errors, 401) return decorated return wrapper def _extract_access_token(self, request, auth_header_key=None): if auth_header_key is None: auth_header_key = 'Authorization' # Prefer the auth_header_key header, but fall back to Authorization when that header is not present if request.headers.get(auth_header_key, request.headers.get('Authorization', '')).startswith('Bearer '): return request.headers[auth_header_key].split(None, 1)[1].strip() if 'access_token' in request.form: return request.form['access_token'] if 'access_token' in request.args: return request.args['access_token'] return None def check_authorization(self, require_token=False, scopes_required=None, render_errors=True, validation_func=None, roles_required=None, auth_header_key=None): """ 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). This endpoint will also check if the current request has the permission to be executed. If the caller has no authorization it returns a 403. 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. It is tested with keycloak, not with other IdP's! :param auth_header_key: Key in headers which contains the Auth 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 :param validation_func: Function that is called instead of the default implementation to check if the request should granted or not. :type validation_func: function or None :param roles_required: List of roles that are required to be present in the token :type roles_required: list .. versionadded:: 1.4 """ def wrapper(view_func): @wraps(view_func) def decorated(*args, **kwargs): if not self.keycloak_enabled: return view_func(*args, **kwargs) func = validation_func or self._is_authorized self._set_current_uri(request.script_root + request.path) token = self._extract_access_token(request, auth_header_key) valid = self.validate_token(token, scopes_required, roles_required=roles_required) if (not require_token) or (valid and func(token)): return view_func(*args, **kwargs) else: return self._deny_access(valid, render_errors, 403) return decorated return wrapper def _deny_access(self, error_description, render_errors, error_code): """ :param error_description: Error message to print :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. :param error_code: HTTP-Code that is returned :return: Json-Object with error message and error code .. versionadded:: 1.4 """ response_body = {'error': 'invalid_token', 'error_description': error_description} if render_errors: response_body = json.dumps(response_body) return response_body, error_code, {'WWW-Authenticate': 'Bearer'} def _is_authorized(self, token, validation_func=None): """ Proves if the call to the endpoint is authorized :param token: access token :type token: str :param: validation_func: function that implements the verification if the request is authorized If parameter is None, the default behaviour is used :type: validation_func: function :return: true if the user is authorized, otherwise false .. versionadded:: 1.4 """ if not self.keycloak_enabled: return True if token is None: logger.debug("The access token is not available.") return False if validation_func is None: validation_func = self._is_uri_allowed try: self._rpt_token = self.keycloakApi.authorize(token) if self._rpt_token is None: logger.debug("No rpt token received - authorization failed!") return False resources = self._get_permissions_from_token(self._rpt_token["access_token"]) if resources is None: logger.debug("Empty resource set - authorization failed!") return False self._keycloak_realm_roles = self._get_realm_roles_from_token(self._rpt_token["access_token"]) self._keycloak_client_roles = self._get_keycloak_client_roles_from_token(self._rpt_token["access_token"]) for resource_id in resources: resource = self.keycloakApi.get_resource_info(resource_id["rsid"]) if resource is not None and "uris" in resource and \ validation_func(self._rpt_token["access_token"], resource): return True except Exception as e: logger.debug(str(e)) logger.debug("Authorization failed!") return False def _set_current_uri(self, uri): if uri.endswith("/"): self.current_uri = uri[0: 0 + len(uri) - 1] else: self.current_uri = uri @property def rpt_token(self): return self._rpt_token @property def keycloak_realm_roles(self): return self._keycloak_realm_roles @property def keycloak_client_roles(self): return self._keycloak_client_roles def _get_realm_roles_from_token(self, token): """ :param token: access token that contains realm roles :return: Returns a dict with all realm roles from the token .. versionadded:: 1.4 """ if token is None: return None token = self.keycloakApi.jwt_decode(token) return token.get('realm_access', {}).get('roles', None) def _get_keycloak_client_roles_from_token(self, token): """ :param token: access token that contains client roles :return: Returns a dict with all client roles from the token .. versionadded:: 1.4 """ if token is None: return None token = self.keycloakApi.jwt_decode(token) return token.get('resource_access', {}) def _get_permissions_from_token(self, rpt_token): """ :param rpt_token: requesting party token that contains the permissions :return: Returns a dict with all permissions from the token .. versionadded:: 1.4 """ if not self.keycloak_enabled or rpt_token is None: return None rpt = self.keycloakApi.jwt_decode(rpt_token) if rpt is None: return None return rpt.get('authorization', {}).get('permissions', None) def _is_uri_allowed(self, rpt_token, resource): """ Verifies that the access to a resource at a specific endpoint is granted or not :param rpt_token: requesting party token :type rpt_token: str :param resource: the resource at a specific endpoint :type resource: dict :return: true if it is an allowed URI, otherwise false .. versionadded:: 1.4 """ if not self.keycloak_enabled: return True logger.debug("Check URIs against the RPT token.") is_uri_allowed = False permissions = self._get_permissions_from_token(rpt_token) if permissions is None: logger.error("The minimum for the RPT is not satisfied.") return False if resource["_id"] in [p["rsid"] for p in permissions]: return self._is_access_granted(is_uri_allowed, resource) logger.error("Permission was not found in the RPT token.") return False def _is_access_granted(self, is_uri_allowed, resource): """ :type is_uri_allowed: bool :param is_uri_allowed: if is_uri_allowed is True skip verification :type resource: dict :param resource: the resource at a specific endpoint .. versionadded:: 1.4 """ if not self.keycloak_enabled: return True for uri in resource["uris"]: if self._verify_uri(uri): return True return False def _verify_uri(self, uri): """ Verify if the request URI follows the uri pattern :param uri: the URI pattern :return: true if the request path is conform pattern .. versionadded:: 1.4 """ if not self.keycloak_enabled: return True logger.debug("Verify if the request URI follows the uri pattern") return fnmatch.fnmatch(self.current_uri, uri) 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 = {'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 = httplib2.Http().request( self.client_secrets['token_introspection_uri'], 'POST', urlencode(request), headers=headers) # TODO: Cache this reply return _json_loads(content)
def generate_auth_token(self, username, password, expiration=0): s = Serializer(SECRET_KEY) return s.dumps({'username': username, 'password': password})
def generate_confirmation_token(self,expiration=3600): s = Serializer(current_app.config['SECRET_KEY']) return s.dumps({'confirm':self.id})
def sign(self, value): s = JSONWebSignatureSerializer(self.secret_key) return s.dumps(value).decode()
def generate_access_key(self): """Generate access key used for Restful API user authentication.""" from app import app s = JSONWebSignatureSerializer(app.config['SECRET_KEY']) access_key = s.dumps({'username': self.username}) self.access_key = access_key
def generate_auth_token(self, expiration=None): s = Serializer("dfjkahfjkldahfajklhdlash") return s.dumps({'id': self.id})
def generate_auth_token(self) : s = Serializer('secretkey') return s.dumps({'uid':self.uid})
def get_reset_token(self): s = Serializer(current_app.config['SECRET_KEY'], 'confirmation') return s.dumps({'user_id': self.id}).decode('utf-8')
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 generate_auth_token(self, username, password, expiration = 0): s = Serializer(SECRET_KEY) return s.dumps({ 'username': username, 'password': password })
def generate_confirmation_token(self): s = Serializer(current_app.config['SECRET_KEY']) return s.dumps({'activate': self.id}).decode('utf-8')
def get_unsubscribe_token(email): s = UntimedSerializer(current_app.config['SECRET_KEY']) return s.dumps({'email': email}).decode('utf-8')
def PrivateKey(id): '''Generate a reusable private key for the user.''' t = Token(secret_key=current_app.config['SECRET_KEY']) return t.dumps({'id':id})
def __init__(self, jsonObject): s = JSONWebSignatureSerializer(settings.SECRET_KEY) self.jsonObject = s.dumps(jsonObject)
def generate_auth_token(self, expiration=None): s = Serializer(current_app.config['SECRET_KEY']) return s.dumps({'id': self.id}).decode('ascii')
def generate_subscription_token(company_name, from_date, to_date): serializer = JSONSerializer(current_app.config['SECRET_KEY']) obj = {'company': company_name, 'from': from_date.isoformat(), 'to': to_date.isoformat()} return base64_encode(serializer.dumps(obj)).decode('utf-8')