Exemplo n.º 1
0
    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)
Exemplo n.º 2
0
 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
Exemplo n.º 3
0
def get_credential(a_token):
    s = JSONWebSignatureSerializer(server_secret_key)
    try:
        credential = s.loads(a_token)
        return credential
    except:
        return None
Exemplo n.º 4
0
def decode_auth_token(token):
    try:
        s = JSONWebSignatureSerializer(app.secret_key)
        print token 
        return s.loads(token)
    except:
        return None
Exemplo n.º 5
0
def VerifyToken(token, secret):
    s = Serializer(secret)
    try:
        data = s.loads(token)
    except BadSignature as e:
        raise InvalidAuth('bad_token', e.message)
    return data
Exemplo n.º 6
0
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')
Exemplo n.º 7
0
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
Exemplo n.º 8
0
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)
Exemplo n.º 9
0
	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'])
Exemplo n.º 10
0
    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'
                })
Exemplo n.º 11
0
def verify_token(token):
    s = SigSerializer(SECRET_KEY)
    try:
        data = s.loads(token)
    except (BadSignature):
        return None
    return data
Exemplo n.º 12
0
def verify_auth_token(token):
    s = Serializer(app.config['SECRET_KEY'])
    
    try:
        return s.loads(token)
    except BadSignature:
        return None
Exemplo n.º 13
0
 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
Exemplo n.º 14
0
 def unsign(self, value):
     if value is None:
         return value
     s = JSONWebSignatureSerializer(self.secret_key)
     try:
         return s.loads(value)
     except BadSignature:
         return {}
Exemplo n.º 15
0
 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 {}
Exemplo n.º 16
0
 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
Exemplo n.º 17
0
 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'])
Exemplo n.º 18
0
 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
Exemplo n.º 19
0
	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']]
Exemplo n.º 20
0
 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'])
Exemplo n.º 21
0
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)
Exemplo n.º 22
0
 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
Exemplo n.º 23
0
 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'))
Exemplo n.º 24
0
	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
Exemplo n.º 25
0
 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
Exemplo n.º 26
0
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'))
Exemplo n.º 27
0
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
Exemplo n.º 28
0
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
Exemplo n.º 29
0
    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
Exemplo n.º 30
0
    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
Exemplo n.º 31
0
    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
Exemplo n.º 32
0
    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
Exemplo n.º 33
0
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
Exemplo n.º 34
0
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']
Exemplo n.º 35
0
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
Exemplo n.º 36
0
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
Exemplo n.º 37
0
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
Exemplo n.º 38
0
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)
Exemplo n.º 39
0
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
Exemplo n.º 40
0
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': '服务已过期, 请及时续费'})
Exemplo n.º 41
0
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)
Exemplo n.º 42
0
    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
Exemplo n.º 43
0
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))
Exemplo n.º 44
0
    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
Exemplo n.º 45
0
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}
Exemplo n.º 46
0
def parse_token(token: str) -> str:
    s = JSONWebSignatureSerializer(current_app.secret_key, salt='auth')
    try:
        return s.loads(token)
    except BadSignature:
        return None
Exemplo n.º 47
0
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
Exemplo n.º 48
0
def deserialize_data(data):
    s = JSONWebSignatureSerializer(current_app.config['SECRET_KEY'])
    return s.loads(data)
Exemplo n.º 49
0
def decrypt(text):
    s = JSONWebSignatureSerializer(SECRET_KEY)
    try:
        return s.loads(text)
    except BadSignature:
        return {}
Exemplo n.º 50
0
def decrypt(key, sig):
    s = JSONWebSignatureSerializer(key)
    decrypted_data = s.loads(sig)
    return decrypted_data
Exemplo n.º 51
0
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))
Exemplo n.º 52
0
 def unsign(self, value):
     s = JSONWebSignatureSerializer(self.secret_key)
     return s.loads(value)
Exemplo n.º 53
0
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
Exemplo n.º 55
0
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\''
            )
Exemplo n.º 56
0
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)
Exemplo n.º 57
0
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'
        })
Exemplo n.º 58
0
# 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
Exemplo n.º 59
0
 def unsign(self, value):
     s = JSONWebSignatureSerializer(self.secret_key)
     try:
         return s.loads(value)
     except BadSignature:
         return {}
Exemplo n.º 60
0
'''
@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))