Exemplo n.º 1
0
def generate_token(a_username,a_password):
    if valid_lpirc_session(a_username) is None:
        create_lpirc_session(a_username)
    else:
        # Session already created, nothing to do send previously generated token
        if recycle_db != 1:
            sess = Session.query.filter_by(username=a_username).first()
            token = sess.token
            return token
        else: # Recycle
            # Should be a penalty for multiple login attempts
            delete_lpirc_session(a_username)
            create_lpirc_session(a_username)

    sess = Session.query.filter_by(username=a_username).first()
    s = JSONWebSignatureSerializer(server_secret_key)
    dt = sess.timestamp
    dt_str = dt.strftime(datetime_format)
    print dt_str
    # Adding session created time for timeout validation
    token = s.dumps({ff_username: a_username, ff_password: a_password, ff_timestamp: dt_str})
    # Update session database
    sess.token = token
    if (enable_powermeter == 1) and (a_username != powermeter_user):
        sess.powermeter_status = powermeter_status_stop # So that, it can be restarted later

    if a_username == powermeter_user:
        sess.mytimeout = sys.maxint

    db.session.commit()
    return token
Exemplo n.º 2
0
Arquivo: app.py Projeto: Hatsuney/mee6
def confirm_login():
    # Check for state and for 0 errors
    state = session.get('oauth2_state')
    if not state or request.values.get('error'):
        return redirect(url_for('index'))

    # Fetch token
    discord = make_session(state=state)
    discord_token = discord.fetch_token(
        TOKEN_URL,
        client_secret=OAUTH2_CLIENT_SECRET,
        authorization_response=request.url)
    if not discord_token:
        return redirect(url_for('index'))

    # Fetch the user
    user = get_user(discord_token)
    # Generate api_key from user_id
    serializer = JSONWebSignatureSerializer(app.config['SECRET_KEY'])
    api_key = str(serializer.dumps({'user_id': user['id']}))
    # Store api_key
    db.set('user:{}:api_key'.format(user['id']), api_key)
    # Store token
    db.set('user:{}:discord_token'.format(user['id']), json.dumps(discord_token))
    # Store api_token in client session
    api_token = {
        'api_key': api_key,
        'user_id': user['id']
    }
    session.permanent = True
    session['api_token'] = api_token
    return redirect(url_for('select_server'))
Exemplo n.º 3
0
def generate_token(user):
    secret_key = get_secret_key()
    s = JSONWebSignatureSerializer(secret_key)
    expired_at = utcnow().replace(hours=+2).timestamp
    d = {'user': user, 'expired_at': expired_at}
    token = s.dumps(d).decode('utf-8')
    return token, d
Exemplo n.º 4
0
def hmac(payload, private_key):
    '''Generate a decryptable signature on the server side that the client
    can analzye. Server generated HMAC signatures aren't timed.
    '''
    payload = organize_payload(payload)
    t = Token(secret_key=private_key)
    return t.dumps(payload)
Exemplo n.º 5
0
 def generate_token(self):
     JWT = JWS(os.environ.get('SECRET_KEY'))
     return JWT.dumps({
         "id": self.id,
         "username": self.username,
         # 2 hrs expiration
         "expires": (datetime.datetime.now() +
                     datetime.timedelta(seconds=2*60*60)).timestamp()
         }).decode('utf-8')
Exemplo n.º 6
0
    def af_reset_send(cls_, email, returnurl):
        user = cls_.find_first({'email': email})
        if not user:
            raise ApiValidationError(ValidationError('Unknown email'))

        serializer = JSONWebSignatureSerializer(current_app.config['API_KEY'])
        resettoken = serializer.dumps({"id": user.id, "signature": user.signature}).decode('utf-8')
        content = 'Click here to reset your password: %s' % (returnurl + resettoken)
        mailer.send(user.email, user.name, 'Password reset link', content)
        return {'success': True}
Exemplo n.º 7
0
def test_invalid_token(f_session, f_user):
    secret_key = get_secret_key()
    s = JSONWebSignatureSerializer(secret_key)
    expired_at = arrow.utcnow().replace(hours=-2).timestamp
    d = {
        'user': {'id': f_user.id, 'email': f_user.email, 'name': f_user.name},
        'expired_at': expired_at
    }
    token = s.dumps(d).decode('utf-8')
    with raises(InvalidTokenError):
        t = validate_token('dadfa.asdf')
    with raises(ExpiredTokenError):
        t = validate_token(token)
Exemplo n.º 8
0
    def post(self):
        serializer = JSONWebSignatureSerializer(
            current_app.config['API_KEY'])
        args = validator.validate(self.get_request())

        user = models.User.find_first({'email': args['email']})
        if user is None or user.password != args['password']:
            raise ApiError("Wrong email or password provided for {}".format(args['email']))

        if user.status is not const.STATUS_ACTIVE:
            raise ApiError("User is not active")

        token = serializer.dumps({"id": user.id, "signature": user.signature}).decode('utf-8')
        return self.respond({"token": token, "user": user.jsonify(user)})
Exemplo n.º 9
0
    def generate_csrf_token():
        nonce = os.urandom(16)
        secret = session.setdefault('_csrf_secret', os.urandom(16))

        nonce_int = bytes_to_int(nonce)
        secret_int = bytes_to_int(secret)

        jsw = JSONWebSignatureSerializer(app.secret_key)
        token = jsw.dumps({
            "n": b64encode(nonce),
            "k": b64encode(int_to_bytes(nonce_int ^ secret_int))
        })

        return token
Exemplo n.º 10
0
    def post(self, request):
        username = request.body.get("username", "")
        password = request.body.get("password", "")
        user = user_actions.get_by_username_and_password(username, password)
        if not user:
            return responses.BadRequest({"error": "Invalid username or password"})
        else:
            token_data = {"user_id": user.id}
            serializer = JSONWebSignatureSerializer(settings.SECRET_KEY)
            token = serializer.dumps(token_data).decode("ascii")

            result = to_plain(user, ignore_fields=["id", "password"])
            result["token"] = token
            return responses.Ok(result)
Exemplo n.º 11
0
def voucher():
    test_dict = {
        'email': '*****@*****.**',
        'card': '123456789012345678901234567890',
        'charity': 20,
        'content': 80,
        'amount': 25
                }
    s = JSONWebSignatureSerializer('secret', algorithm_name='HS512')
    res = s.dumps(test_dict)
    print(res)
    l = s.loads(res)
    print(l)

# send_mail('*****@*****.**', 'dasa' ,'asdasfadfada')
# voucher()
Exemplo n.º 12
0
def generate_token(a_username,a_password):
    if valid_lpirc_session(a_username) is None:
        create_lpirc_session(a_username)
    else:
        # Should be a penalty for multiple login attempts
        delete_lpirc_session(a_username)
        create_lpirc_session(a_username)

    sess = Session.query.filter_by(username=a_username).first()
    s = JSONWebSignatureSerializer(server_secret_key)
    dt = sess.timestamp
    dt_str = dt.strftime(datetime_format)
    print dt_str
    # Adding session created time for timeout validation
    token = s.dumps({ff_username: a_username, ff_password: a_password, ff_timestamp: dt_str})
    return token
Exemplo n.º 13
0
def create_load_more_url(kwargs):
    ''' create the url for load more button in event listing page
    arg:
        kwargs: kwargs contain the different type of data
    return:
        url for the load more button
    '''
    s = PageSerializer(config.SECRET_KEY)
    # check for the cur is present or not
    # if cur is present that means load more button request
    # in that case page number will increment
    # othewise new request the assign page no 2
    try:
        # get previous page number
        prev = get_event_page_no(kwargs['cur'])
        page = prev + 1
    except:
        page = 2
    kwargs['cur'] = s.dumps(page)
    return url_for('event', **kwargs)
Exemplo n.º 14
0
    def patch(self, request, username):
        user = load_user(username)
        check_user_is_self(request.user, user)

        validator = user_validators.UserForUpdateValidator(request.body)
        if validator.is_valid():
            user = user_actions.update_user(
                user,
                user_entities.UserForUpdate(**validator.cleaned_data)
            )

            token_data = {"user_id": user.id}
            serializer = JSONWebSignatureSerializer(settings.SECRET_KEY)
            token = serializer.dumps(token_data).decode("ascii")

            result = to_plain(user, ignore_fields=["id", "password"])
            result["token"] = token
            return responses.Ok(result)
        else:
            return responses.BadRequest(validator.errors)
Exemplo n.º 15
0
def generate_token():

    """ Access Token """
    access_token_serializer = TimedJSONWebSignatureSerializer(
                            app.config['SECRET_KEY'],
                            expires_in=app.config['EXPIRES_IN'])

    """ Secret Token """
    secret_token_serializer = JSONWebSignatureSerializer(
                            app.config['SECRET_KEY'])

    d = {'username': g.user.username, 'password': g.user.password}

    d_access = d.copy()
    d_access.update({'secret': False})

    d_secret = d.copy()
    d_secret.update({'secret': True})

    access_token = access_token_serializer.dumps(d_access)
    secret_token = secret_token_serializer.dumps(d_secret)

    return access_token, secret_token
Exemplo n.º 16
0
def get_token():
    try:
        email = request.form['email']
        password = request.form['password']
        user = User.query.filter_by(email=email).first()
        if not user:
            return False
        else:
            if user.email != email:
                return False
            elif user.password != password:
                return False
            else:
                s = JSONWebSignatureSerializer('secret_key')
                token = s.dumps({'user_id': user.id})
                user.token = token
                user.token_expiration = time.time() + 600
                db.session.commit()
                session['logged_in'] = token
                return token
    except ValueError as e:
        app.logger.debug(e.message)
        return False
Exemplo n.º 17
0
def confirm_login():
    log.info("Checking login....")
    # Check for state and for 0 errors
    state = session.get('oauth2_state')
    if not state or request.values.get('error'):
        return redirect(url_for('index'))

    # Fetch token
    discord = utils.make_session(state=state)
    discord_token = discord.fetch_token(
        data_info.TOKEN_URL,
        client_secret=data_info.OAUTH2_CLIENT_SECRET,
        authorization_response=request.url)
    if not discord_token:
        log.info("Not clear, returning")
        return redirect(url_for('index'))

    # Fetch the user
    user = utils.get_user(discord_token)
    # Generate api_key from user_id
    serializer = JSONWebSignatureSerializer(app.config['SECRET_KEY'])
    api_key = str(serializer.dumps({'user_id': user['id']}))
    # Store api_key
    db.set('user:{}:api_key'.format(user['id']), api_key)
    # Store token
    db.set('user:{}:discord_token'.format(user['id']), json.dumps(discord_token))
    # Store api_token in client session
    api_token = {
        'api_key': api_key,
        'user_id': user['id']
    }
    session.permanent = True
    session['api_token'] = api_token
    log.info("Clear, redirect...")
    if data_info.last_path:
        return redirect(data_info.last_path)
    return redirect(url_for('after_login'))
Exemplo n.º 18
0
 def generate_auth_token(self):
     s = JSONSerializer(current_app.config['SECRET_KEY'])
     return s.dumps({'id': self.id}).decode('utf-8')
Exemplo n.º 19
0
	def generate_auth_token(self):
		s = Serializer(current_app.config['SECRET_KEY'])
		return s.dumps({'id': self.id}).decode('ascii')
Exemplo n.º 20
0
 def generate_api_key(self):
     s = JSONWebSignatureSerializer(current_app.config['SECRET_KEY'])
     self.apikey = s.dumps({
         'user': str(self.id),
         'time': time(),
     })
Exemplo n.º 21
0
 def generate_token(self):
     s = JSONWebSignatureSerializer(app_config.mwdb.secret_key)
     return s.dumps({"login": self.user.login, "api_key_id": str(self.id)})
Exemplo n.º 22
0
 def sign(self, value):
     if isinstance(value, bytes):
         value = value.decode("utf-8")
     s = JSONWebSignatureSerializer(self.secret_key)
     return s.dumps(value)
Exemplo n.º 23
0
class SupOAuth(object):
    AUTH_HEADER_KEY = 'Authorization'
    AUTH_HEADER_PREFIX = 'Bearer'

    RANDOM_STRING_EXPIRES_IN = 60 * 10

    DEFAULT_EXT_EXPIRES_IN = 3600 * 24 * 1 - 1
    DEFAULT_HEADERS = {'content-type': 'application/json'}

    def __init__(self, ext_key, ext_secret, secret_key, expires_in,
                 api_uri, token_uri, redirect_uri):

        expires_in = int(expires_in.total_seconds()) or \
                     self.DEFAULT_EXT_EXPIRES_IN
        self._s = TimedSerializer(
            secret_key=secret_key,
            expires_in=expires_in
        )
        self._r = TimedSerializer(
            secret_key=secret_key,
            expires_in=self.RANDOM_STRING_EXPIRES_IN
        )
        self._f = Serializer(
            secret_key=secret_key
        )
        self.ext_key = ext_key
        self.ext_secret = ext_secret
        self.api_uri = api_uri
        self.token_uri = token_uri
        self.redirect_uri = redirect_uri

    def generate_ext_token(self, open_id):
        try:
            open_id = unicode(open_id)
            token = self._s.dumps(open_id).decode('utf-8')
        except Exception as e:
            raise self.OAuthInvalidExtToken(str(e))
        return token

    def encrypt(self, code):
        try:
            code = unicode(code)
            code = self._f.dumps(code).decode('utf-8')
        except Exception as e:
            raise self.OAuthEncryptionFailed(str(e))
        return code

    def decrypt(self, code):
        try:
            code = self._f.loads(code)
        except Exception as e:
            raise self.OAuthDecryptionFailed(str(e))
        return code

    def load_ext_token(self, headers):
        auth = headers.get(self.AUTH_HEADER_KEY)
        if not auth:
            return None
        parts = auth.split()

        if parts[0].lower() != self.AUTH_HEADER_PREFIX.lower():
            return None

        try:
            token = parts[1]
            open_id = self._s.loads(token)
        except Exception:
            return None

        return open_id

    def make_random_string(self, open_id):
        try:
            open_id = unicode(open_id)
            random_string = self._r.dumps(open_id).decode('utf-8')
        except Exception as e:
            raise self.OAuthInvalidRandomString(str(e))
        return random_string

    def match_random_string(self, random_string, target_string):
        try:
            payload = self._r.loads(random_string)
        except Exception:
            payload = None
        return payload == target_string

    def get_access_token(self, code):
        payloads = {
            'ext_key': self.ext_key,
            'ext_secret': self.ext_secret,
            'code': code,
            'grant_type': 'code',
            'redirect_uri': self.redirect_uri
        }
        headers = self.DEFAULT_HEADERS
        try:
            r = requests.post(self.token_uri,
                              data=json.dumps(payloads),
                              headers=headers)
            result = r.json()
            assert isinstance(result, dict)
        except Exception as e:
            raise self.OAuthInvalidAccessToken(str(e))

        result['access_token'] = self.encrypt(result['access_token'])
        result['refresh_token'] = self.encrypt(result['refresh_token'])

        return result

    def refresh_access_token(self, refresh_token):
        payloads = {
            'ext_key': self.ext_key,
            'ext_secret': self.ext_secret,
            'refresh_token': self.decrypt(refresh_token),
            'grant_type': "refresh_token",
        }

        headers = self.DEFAULT_HEADERS
        try:
            r = requests.post(self.token_uri,
                              data=payloads,
                              headers=headers)
            result = r.json()
            assert isinstance(result, dict)
        except:
            raise self.OAuthInvalidRefreshToken

        result['access_token'] = self.encrypt(result['access_token'])
        result['refresh_token'] = self.encrypt(result['refresh_token'])

        return result

    def request(self, method, url, access_token, **kwargs):
        headers = self.DEFAULT_HEADERS
        headers.update({
            self.AUTH_HEADER_KEY: '{} {}'.format(self.AUTH_HEADER_PREFIX,
                                                 self.decrypt(access_token))
        })

        if 'headers' not in kwargs:
            kwargs['headers'] = {}

        kwargs['headers'].update(headers)

        try:
            r = requests.request(method, url, **kwargs)
            result = r.json()
            assert isinstance(result, dict)
            assert r.status_code < 400
        except Exception as e:
            if r.status_code == 401:
                raise self.OAuthInvalidAccessToken
            elif r.status_code >= 400:
                err_msg = json.dumps(r.json())
            else:
                err_msg = str(e)
            raise self.OAuthInvalidRequest(err_msg)

        return result

    def get_profile(self, access_token):
        try:
            url = '{}/oauth/profile'.format(self.api_uri)
        except Exception as e:
            raise self.OAuthInvalidParams(str(e))
        return self.request('GET', url, access_token)

    def get_member(self, member_open_id, access_token):
        try:
            url = '{}/crm/oauth/member/{}'.format(self.api_uri, member_open_id)
        except Exception as e:
            raise self.OAuthInvalidParams(str(e))
        return self.request('GET', url, access_token)

    def logout(self, access_token):
        url = '{}/oauth/access_logout'.format(self.api_uri)
        return self.request('DELETE', url, access_token)

    # exceptions

    class OAuthException(Exception):
        status_message = 'oauth_error'

        def __init__(self, message=None):
            self.affix_message = message

        def __str__(self):
            return '{}:{}'.format(self.status_message, self.affix_message)

    class OAuthInvalidAccessToken(OAuthException):
        status_message = 'OAUTH_INVALID_ACCESS_TOKEN'

    class OAuthInvalidRefreshToken(OAuthException):
        status_message = 'OAUTH_INVALID_REFRESH_TOKEN'

    class OAuthInvalidExtToken(OAuthException):
        status_message = 'OAUTH_INVALID_EXT_TOKEN'

    class OAuthInvalidRandomString(OAuthException):
        status_message = 'OAUTH_INVALID_RANDOM_STRING'

    class OAuthInvalidRequest(OAuthException):
        status_message = 'OAUTH_INVALID_REQUEST'

    class OAuthInvalidParams(OAuthException):
        status_message = 'OAUTH_INVALID_PARAMS'

    class OAuthDecryptionFailed(OAuthException):
        status_message = 'OAUTH_DECRYPTION_FAILED'

    class OAuthEncryptionFailed(OAuthException):
        status_message = 'OAUTH_ENCRYPTION_FAILED'
Exemplo n.º 24
0
def test():
    s = Serializer('ttttt')
    return jsonify(s.dumps({"id": '123'}))
Exemplo n.º 25
0
import time
from itsdangerous import JSONWebSignatureSerializer

apiKey = 'my_api_key'
secret = 'my_api_secret'

s = JSONWebSignatureSerializer(secret, algorithm_name='HS512')
payload = {"apiKey": apiKey, "timestamp": int(time.time())}
token = s.dumps(payload).decode()
Exemplo n.º 26
0
 def create_auth_token(self):
     s = Serializer(SECRET_KEY)
     return s.dumps({'id': self.id})
Exemplo n.º 27
0
 def generate_auth_token(self):
     """generate a token"""
     s = Serializer(current_app.config['SECRET_KEY'])
     return s.dumps({'id': self.id})
Exemplo n.º 28
0
 def generate_api_key(self):
     s = JSONWebSignatureSerializer(current_app.config['SECRET_KEY'])
     self.apikey = s.dumps({
         'user': str(self.id),
         'time': time(),
     })
Exemplo n.º 29
0
def gen_tn_proxy_token(app, client_id):
    s = JSONWebSignatureSerializer(app.config['SECRET_KEY'])
    token = s.dumps({ 'client_id': client_id })
    return token
Exemplo n.º 30
0
 def maketoken(self):
     s = Serializer(app.config['SECRET_KEY'])
     token = s.dumps({'user_id': self.id})
     return token
Exemplo n.º 31
0
 def generate_auth_token(self):
     s = Serializer(app.config['SECRET_KEY'])
     return s.dumps(self.email)
Exemplo n.º 32
0
 def generate_api_token(self):
     s = Serializer(current_app.config['SECRET_KEY'],
                    salt=datetime.now().strftime('%Y-%m-%d %H:%M:%S'))
     self.api_token = s.dumps({'id': self.id})
Exemplo n.º 33
0
 def get_verification_token(self):
     s = Serializer(current_app.config['SECRET_KEY'])
     return s.dumps({'user_id': self.id}).decode('utf-8')
Exemplo n.º 34
0
def encrypt_api_key(user_id, secret_key):
    '''Encrypts a user id into an api key (token). The generated api key has no expiration time.'''
    s = Serializer(secret_key)
    return s.dumps({'id': user_id, 'salt': generate_salt()})
Exemplo n.º 35
0
 def get_shared_token(self):
     s = InfinitSerializer(current_app.config['SECRET_KEY'])
     return s.dumps({'post_id': self.id}).decode('utf-8')#Esto nos devuelve el token a partir de la clave secreta, para ello le 
Exemplo n.º 36
0
 def generate_auth_token(self):
     s = JSONWebSignatureSerializer(app.config['SECRET_KEY'])
     return s.dumps({"id": self.id})
Exemplo n.º 37
0
class planItDb():
    def __init__(self, secret):
        self.sig = JSONWebSignatureSerializer(secret)
        self.log = logging.getLogger("planItDb")

    def init_db(self, app):
        self.mongo = PyMongo()
        self.mongo.init_app(app)

    def log_access(self, idinfo, request, response):
        #ensure we have the user in the user db
        curs = self.mongo.db.users.find({'sub':idinfo['sub']}).limit(1)
        if curs.count() == 0:
            self.log.info("Add user to db: {}".format(idinfo))
            # need to make a copy because mongo adds its objectid, whcih
            # cannot be seralized into the session.
            d = idinfo.copy()
            d['created'] = datetime.datetime.utcnow()
            d['remote_addr'] = [request.remote_addr]
            self.mongo.db.users.insert(d)
        else:
            dbuser = curs.next()
            if request.remote_addr not in dbuser['remote_addr']:
                self.mongo.db.users.update(
                    {'_id': dbuser['_id']},
                    {'$push': {'remote_addr': request.remote_addr}}
                )

        self.mongo.db.accesses.insert(
        {
            'sub': idinfo['sub'],
            'headers': dict(request.headers),
            'args': {x:request.args[x] for x in request.args.keys()},
            'time': datetime.datetime.utcnow(),
            'remote': request.remote_addr,
            'url': request.url,
            'status': response.status_code,
            'length': response.content_length
        })
    def validate_key(self, key, remote_addr):
        doc = self.mongo.db.keys.find({'signature': key}).next()
        if doc == None:
            return False

        if 'active' not in doc:
            return False

        op = {
            '$inc': {'use_info.use_count': 1},
            '$set': {'use_info.last_used': datetime.datetime.utcnow()}
            }
        if remote_addr not in doc['use_info']['remotes']:
            op['$push'] = {'use_info.remotes': remote_addr}
        self.mongo.db.keys.update({'_id': doc['_id']}, op)

        return doc['active']

    def enable_key(self, idinfo, signature, remote_addr):
        return self.mongo.db.keys.update(
            {'sub': idinfo['sub'], 'signature': signature},
            {'$set': {'active': True}}
        )
    def disable_key(self, idinfo, signature, remote_addr):
        return self.mongo.db.keys.update(
            {'sub': idinfo['sub'], 'signature': signature},
            {'$set': {'active': False}}
        )
    def delete_key(self, idinfo, signature, remote_addr):
        self.mongo.db.users.update(
                {"sub": idinfo['sub']},
                {'$push': {'deleted_keys':
                    {'when': datetime.datetime.utcnow(),
                    'remote_addr': remote_addr,
                     'signature': signature}}
                    }
            )
        return self.mongo.db.keys.remove(
            {'sub': idinfo['sub'], 'signature': signature})

    def get_or_create_webkey(self, idinfo, remote_addr):
        "look for a webkey, reutrn if found, else create one and return it"
        curs = self.mongo.db.keys.find(
            {'sub': idinfo['sub'], 'web': True, 'active':True},
            {'_id': False})
        if curs.count() > 0:
            return curs.next()
        return self.create_key(idinfo, remote_addr, web=True)

    def create_key(self, idinfo, remote_addr, web=False):
        "create a new key for the user with idinfo (jwt)"
        keydoc = {
            'sub': idinfo['sub'],
            'created': datetime.datetime.utcnow(),
            'create_address': remote_addr,
            'use_info': {'use_count': 0,
                        'remotes': [remote_addr]},
            'active': True,
            'web': web,
            'signature': [0*64] # dummy data
        }
        self.mongo.db.keys.insert(keydoc)
        # do the insert to get the _id, then compute the signature on the id

        # store the doc id,
        docid = keydoc['_id']
        del keydoc['_id']

        keydoc['signature'] = self.sig.dumps(str(docid)).decode('utf-8')
        self.log.info("signature is: {}".format(keydoc['signature']))

        self.mongo.db.keys.update(
            {"_id": docid},
            {'$set': {'signature': keydoc['signature']}}
        )

        self.mongo.db.users.update(
            {"sub": idinfo['sub']},
            {'$push': {'created_keys':
                {'when': datetime.datetime.utcnow(),
                 'remote_addr': remote_addr,
                 'signature': keydoc['signature']}}}

        )

        return keydoc

    def list_keys(self, idinfo):
        keys = list(self.mongo.db.keys.find(
            {'sub': idinfo['sub']},
            {'_id': False}))

        self.log.info("got keys: {}".format(keys))

        return keys
Exemplo n.º 38
0
 def generate_auth_token(self, expiration = 3600):
     s = Serializer(app.config['SECRET_KEY'])
     str = s.dumps({'id': self.id})
     return b64encode(str).decode('utf-8')
Exemplo n.º 39
0
class TokenAuthenticator(Component):
    implements(IAuthenticator, IPreferencePanelProvider, ITemplateProvider)

    secret_key = Option('sage_trac', 'secret_key',
                        doc='Secret key to use for signing tokens; ensure '
                            'that this is well protected.')

    def __init__(self):
        super(TokenAuthenticator, self).__init__()
        if self.secret_key:
            self._serializer = JSONWebSignatureSerializer(self.secret_key)
        else:
            self.log.warning('No secret key configured for token-based '
                             'authentication in {}.'.format(
                                 self.__class__.__name__))
            self._serializer = None

    # ITemplateProvider methods
    def get_htdocs_dirs(self):
        return []

    def get_templates_dirs(self):
        return [resource_filename(__name__, 'templates')]

    # IPreferencePanelProvider methods
    def get_preference_panels(self, req):
        if req.authname and req.authname != 'anonymous':
            yield 'token', 'Token'

    def render_preference_panel(self, req, panel):
        token = self.create_token(req.authname)
        return 'prefs_token.html', {'token': token}

    # IAuthenticator methods
    def authenticate(self, req):
        username = self._check_token(req)
        if username:
            req.environ['REMOTE_USER'] = username
            return username

    def create_token(self, authname):
        return self._serializer.dumps(authname)

    def verify_token(self, token):
        try:
            username = self._serializer.loads(token)
        except BadSignature:
            username = None

        return username

    def _check_token(self, req):
        if not self._serializer:
            return None

        header = req.get_header('Authorization')
        if not header:
            self.log.debug("No authorization header; skipping token auth")
            return None

        try:
            scheme, token = header.split(None, 1)
        except ValueError:
            # Malformed Authorization header; bail
            self.log.debug("Malformed authorization header; "
                           "skipping token auth")
            return None

        if scheme.lower() != 'bearer':
            self.log.debug("Unrecognized authorization scheme; "
                           "skipping token auth")
            return None

        username = self.verify_token(token)

        if username is not None:
            self.log.debug("Successful token auth for user {}".format(
                username))

        return username
Exemplo n.º 40
0
 def sign(self, value):
     """ json web 签名 """
     if isinstance(value, bytes):
         value = value.decode('utf-8')
     s = JSONWebSignatureSerializer(self.secret_key)
     return str(s.dumps(value), encoding='utf-8')
Exemplo n.º 41
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)

        # By default, we do not have a custom callback
        self._custom_callback = None

        # Keycloak is not enabled by default
        self.keycloak_enabled = False

        # get stuff from the app's config, which may override stuff set above
        if app is not None:
            self.init_app(app)

    def init_app(self, app):
        """
        Do setup that requires a Flask app.

        :param app: The application to initialize.
        :type app: Flask
        """
        secrets = self.load_secrets(app)

        self.client_secrets = list(secrets.values())[0]
        secrets_cache = DummySecretsCache(secrets)

        # Set some default configuration options
        app.config.setdefault('OIDC_SCOPES', ['openid', 'email'])
        app.config.setdefault('OIDC_GOOGLE_APPS_DOMAIN', None)
        app.config.setdefault('OIDC_ID_TOKEN_COOKIE_NAME', 'oidc_id_token')
        app.config.setdefault('OIDC_ID_TOKEN_COOKIE_PATH', '/')
        app.config.setdefault('OIDC_ID_TOKEN_COOKIE_TTL', 7 * 86400)  # 7 days
        # should ONLY be turned off for local debugging
        app.config.setdefault('OIDC_COOKIE_SECURE', True)
        app.config.setdefault('OIDC_VALID_ISSUERS',
                              (self.client_secrets.get('issuer') or
                               GOOGLE_ISSUERS))
        app.config.setdefault('OIDC_CLOCK_SKEW', 60)  # 1 minute
        app.config.setdefault('OIDC_REQUIRE_VERIFIED_EMAIL', False)
        app.config.setdefault('OIDC_OPENID_REALM', None)
        app.config.setdefault('OIDC_USER_INFO_ENABLED', True)
        app.config.setdefault('OIDC_CALLBACK_ROUTE', '/oidc_callback')
        app.config.setdefault('OVERWRITE_REDIRECT_URI', False)
        app.config.setdefault("OIDC_EXTRA_REQUEST_AUTH_PARAMS", {})
        # Configuration for resource servers
        app.config.setdefault('OIDC_RESOURCE_SERVER_ONLY', False)
        app.config.setdefault('OIDC_RESOURCE_CHECK_AUD', False)

        # We use client_secret_post, because that's what the Google
        # oauth2client library defaults to
        app.config.setdefault('OIDC_INTROSPECTION_AUTH_METHOD', 'client_secret_post')
        app.config.setdefault('OIDC_TOKEN_TYPE_HINT', 'access_token')
        app.config.setdefault("OIDC_KEYCLOAK_ENABLED", False)

        if not 'openid' in app.config['OIDC_SCOPES']:
            raise ValueError('The value "openid" must be in the OIDC_SCOPES')

        # register callback route and cookie-setting decorator
        if not app.config['OIDC_RESOURCE_SERVER_ONLY']:
            app.route(app.config['OIDC_CALLBACK_ROUTE'])(self._oidc_callback)
            app.before_request(self._before_request)
            app.after_request(self._after_request)

        # Initialize oauth2client
        self.flow = flow_from_clientsecrets(
            app.config['OIDC_CLIENT_SECRETS'],
            scope=app.config['OIDC_SCOPES'],
            cache=secrets_cache)
        assert isinstance(self.flow, OAuth2WebServerFlow)

        # create signers using the Flask secret key
        self.extra_data_serializer = JSONWebSignatureSerializer(
            app.config['SECRET_KEY'], salt='flask-oidc-extra-data')
        self.cookie_serializer = JSONWebSignatureSerializer(
            app.config['SECRET_KEY'], salt='flask-oidc-cookie')

        if app.config["OIDC_KEYCLOAK_ENABLED"]:
            keycloak_secrets = self.load_keycloak_secrets(app)
            self.keycloakApi = KeycloakAPI()
            self.keycloakApi.init_app(keycloak_secrets)
            self.keycloak_enabled = True
            self._keycloak_realm_roles = None
            self._keycloak_client_roles = None
            self._rpt_token = None
            self.current_uri = None

        try:
            self.credentials_store = app.config['OIDC_CREDENTIALS_STORE']
        except KeyError:
            pass

    def load_secrets(self, app):
        # Load client_secrets.json to pre-initialize some configuration
        content = app.config['OIDC_CLIENT_SECRETS']
        if isinstance(content, dict):
            return content
        else:
            with open(content, 'r') as c:
                return _json_loads(c.read())

    def load_keycloak_secrets(self, app):
        # Load client_secrets.json to pre-initialize some configuration
        content = app.config['OIDC_KEYCLOAK_CLIENT_SECRETS']
        if isinstance(content, dict):
            return content
        else:
            with open(content, 'r') as c:
                return _json_loads(c.read())

    @property
    def user_loggedin(self):
        """
        Represents whether the user is currently logged in.

        Returns:
            bool: Whether the user is logged in with Flask-OIDC.

        .. versionadded:: 1.0
        """
        return g.oidc_id_token is not None

    def user_getfield(self, field, access_token=None):
        """
        Request a single field of information about the user.

        :param field: The name of the field requested.
        :type field: str
        :returns: The value of the field. Depending on the type, this may be
            a string, list, dict, or something else.
        :rtype: object

        .. versionadded:: 1.0
        """
        info = self.user_getinfo([field], access_token)
        return info.get(field)

    def user_getinfo(self, fields, access_token=None):
        """
        Request multiple fields of information about the user.

        :param fields: The names of the fields requested.
        :type fields: list
        :returns: The values of the current user for the fields requested.
            The keys are the field names, values are the values of the
            fields as indicated by the OpenID Provider. Note that fields
            that were not provided by the Provider are absent.
        :rtype: dict
        :raises Exception: If the user was not authenticated. Check this with
            user_loggedin.

        .. versionadded:: 1.0
        """
        if g.oidc_id_token is None and access_token is None:
            raise Exception('User was not authenticated')
        info = {}
        all_info = None
        for field in fields:
            if access_token is None and field in g.oidc_id_token:
                info[field] = g.oidc_id_token[field]
            elif current_app.config['OIDC_USER_INFO_ENABLED']:
                # This was not in the id_token. Let's get user information
                if all_info is None:
                    all_info = self._retrieve_userinfo(access_token)
                    if all_info is None:
                        # To make sure we don't retry for every field
                        all_info = {}
                if field in all_info:
                    info[field] = all_info[field]
                else:
                    # We didn't get this information
                    pass
        return info

    def get_access_token(self):
        """Method to return the current requests' access_token.

        :returns: Access token or None
        :rtype: str

        .. versionadded:: 1.2
        """
        try:
            # user is not logged in
            if not self.user_loggedin:
                return None
            credentials = OAuth2Credentials.from_json(
                self.credentials_store[g.oidc_id_token['sub']])
            return credentials.access_token
        except KeyError:
            logger.debug("Expired ID token, credentials missing",
                         exc_info=True)
            return None

    def get_refresh_token(self):
        """Method to return the current requests' refresh_token.

        :returns: Access token or None
        :rtype: str

        .. versionadded:: 1.2
        """
        try:
            credentials = OAuth2Credentials.from_json(
                self.credentials_store[g.oidc_id_token['sub']])
            return credentials.refresh_token
        except KeyError:
            logger.debug("Expired ID token, credentials missing",
                         exc_info=True)
            return None

    def _retrieve_userinfo(self, access_token=None):
        """
        Requests extra user information from the Provider's UserInfo and
        returns the result.

        :returns: The contents of the UserInfo endpoint.
        :rtype: dict
        """
        if 'userinfo_uri' not in self.client_secrets:
            logger.debug('Userinfo uri not specified')
            raise AssertionError('UserInfo URI not specified')

        # Cache the info from this request
        if '_oidc_userinfo' in g:
            return g._oidc_userinfo

        http = httplib2.Http()
        if access_token is None:
            try:
                credentials = OAuth2Credentials.from_json(
                    self.credentials_store[g.oidc_id_token['sub']])
            except KeyError:
                logger.debug("Expired ID token, credentials missing",
                             exc_info=True)
                return None
            credentials.authorize(http)
            resp, content = http.request(self.client_secrets['userinfo_uri'])
        else:
            # We have been manually overriden with an access token
            resp, content = http.request(
                self.client_secrets['userinfo_uri'],
                "POST",
                body=urlencode({"access_token": access_token}),
                headers={'Content-Type': 'application/x-www-form-urlencoded'})

        logger.debug('Retrieved user info: %s' % content)
        info = _json_loads(content)

        g._oidc_userinfo = info

        return info

    def get_cookie_id_token(self):
        """
        .. deprecated:: 1.0
           Use :func:`user_getinfo` instead.
        """
        warn('You are using a deprecated function (get_cookie_id_token). '
             'Please reconsider using this', DeprecationWarning)
        return self._get_cookie_id_token()

    def _get_cookie_id_token(self):
        try:
            id_token_cookie = request.cookies.get(current_app.config[
                                                      'OIDC_ID_TOKEN_COOKIE_NAME'])
            if not id_token_cookie:
                # Do not error if we were unable to get the cookie.
                # The user can debug this themselves.
                return None
            return self.cookie_serializer.loads(id_token_cookie)
        except SignatureExpired:
            logger.debug("Invalid ID token cookie", exc_info=True)
            return None
        except BadSignature:
            logger.info("Signature invalid for ID token cookie", exc_info=True)
            return None

    def set_cookie_id_token(self, id_token):
        """
        .. deprecated:: 1.0
        """
        warn('You are using a deprecated function (set_cookie_id_token). '
             'Please reconsider using this', DeprecationWarning)
        return self._set_cookie_id_token(id_token)

    def _set_cookie_id_token(self, id_token):
        """
        Cooperates with @after_request to set a new ID token cookie.
        """
        g.oidc_id_token = id_token
        g.oidc_id_token_dirty = True

    def _after_request(self, response):
        """
        Set a new ID token cookie if the ID token has changed.
        """
        # This means that if either the new or the old are False, we set
        # insecure cookies.
        # We don't define OIDC_ID_TOKEN_COOKIE_SECURE in init_app, because we
        # don't want people to find it easily.
        cookie_secure = (current_app.config['OIDC_COOKIE_SECURE'] and
                         current_app.config.get('OIDC_ID_TOKEN_COOKIE_SECURE',
                                                True))

        if getattr(g, 'oidc_id_token_dirty', False):
            if g.oidc_id_token:
                signed_id_token = self.cookie_serializer.dumps(g.oidc_id_token)
                response.set_cookie(
                    current_app.config['OIDC_ID_TOKEN_COOKIE_NAME'],
                    signed_id_token,
                    secure=cookie_secure,
                    httponly=True,
                    max_age=current_app.config['OIDC_ID_TOKEN_COOKIE_TTL'])
            else:
                # This was a log out
                response.set_cookie(
                    current_app.config['OIDC_ID_TOKEN_COOKIE_NAME'],
                    '',
                    path=current_app.config['OIDC_ID_TOKEN_COOKIE_PATH'],
                    secure=cookie_secure,
                    httponly=True,
                    expires=0)
        return response

    def _before_request(self):
        g.oidc_id_token = None
        self.authenticate_or_redirect()

    def authenticate_or_redirect(self):
        """
        Helper function suitable for @app.before_request and @check.
        Sets g.oidc_id_token to the ID token if the user has successfully
        authenticated, else returns a redirect object so they can go try
        to authenticate.

        :returns: A redirect object, or None if the user is logged in.
        :rtype: Redirect

        .. deprecated:: 1.0
           Use :func:`require_login` instead.
        """
        # the auth callback and error pages don't need user to be authenticated
        if request.endpoint in frozenset(['_oidc_callback', '_oidc_error']):
            return None

        # retrieve signed ID token cookie
        id_token = self._get_cookie_id_token()
        if id_token is None:
            return self.redirect_to_auth_server(request.url)

        # ID token expired
        # when Google is the IdP, this happens after one hour
        if time.time() >= id_token['exp']:
            # get credentials from store
            try:
                credentials = OAuth2Credentials.from_json(
                    self.credentials_store[id_token['sub']])
            except KeyError:
                logger.debug("Expired ID token, credentials missing",
                             exc_info=True)
                return self.redirect_to_auth_server(request.url)

            # refresh and store credentials
            try:
                credentials.refresh(httplib2.Http())
                if credentials.id_token:
                    id_token = credentials.id_token
                else:
                    # It is not guaranteed that we will get a new ID Token on
                    # refresh, so if we do not, let's just update the id token
                    # expiry field and reuse the existing ID Token.
                    if credentials.token_expiry is None:
                        logger.debug('Expired ID token, no new expiry. Falling'
                                     ' back to assuming 1 hour')
                        id_token['exp'] = time.time() + 3600
                    else:
                        id_token['exp'] = calendar.timegm(
                            credentials.token_expiry.timetuple())
                self.credentials_store[id_token['sub']] = credentials.to_json()
                self._set_cookie_id_token(id_token)
            except AccessTokenRefreshError:
                # Can't refresh. Wipe credentials and redirect user to IdP
                # for re-authentication.
                logger.debug("Expired ID token, can't refresh credentials",
                             exc_info=True)
                del self.credentials_store[id_token['sub']]
                return self.redirect_to_auth_server(request.url)

        # make ID token available to views
        g.oidc_id_token = id_token

        return None

    def require_login(self, view_func):
        """
        Use this to decorate view functions that require a user to be logged
        in. If the user is not already logged in, they will be sent to the
        Provider to log in, after which they will be returned.

        .. versionadded:: 1.0
           This was :func:`check` before.
        """

        @wraps(view_func)
        def decorated(*args, **kwargs):
            if g.oidc_id_token is None:
                return self.redirect_to_auth_server(request.url)
            return view_func(*args, **kwargs)

        return decorated

    # Backwards compatibility
    check = require_login
    """
    .. deprecated:: 1.0
       Use :func:`require_login` instead.
    """

    def require_keycloak_role(self, client, role):
        """
        Function to check for a KeyCloak client role in JWT access token.

        This is intended to be replaced with a more generic 'require this value
        in token or claims' system, at which point backwards compatibility will
        be added.

        .. versionadded:: 1.5.0
        """

        def wrapper(view_func):
            @wraps(view_func)
            def decorated(*args, **kwargs):
                pre, tkn, post = self.get_access_token().split('.')
                access_token = json.loads(b64decode(tkn))
                if role in access_token['resource_access'][client]['roles']:
                    return view_func(*args, **kwargs)
                else:
                    return abort(403)

            return decorated

        return wrapper

    def flow_for_request(self):
        """
        .. deprecated:: 1.0
           Use :func:`require_login` instead.
        """
        warn('You are using a deprecated function (flow_for_request). '
             'Please reconsider using this', DeprecationWarning)
        return self._flow_for_request()

    def _flow_for_request(self):
        """
        Build a flow with the correct absolute callback URL for this request.
        :return:
        """
        flow = copy(self.flow)
        redirect_uri = current_app.config['OVERWRITE_REDIRECT_URI']
        if not redirect_uri:
            flow.redirect_uri = url_for('_oidc_callback', _external=True)
        else:
            flow.redirect_uri = redirect_uri
        return flow

    def redirect_to_auth_server(self, destination=None, customstate=None):
        """
        Set a CSRF token in the session, and redirect to the IdP.

        :param destination: The page that the user was going to,
            before we noticed they weren't logged in.
        :type destination: Url to return the client to if a custom handler is
            not used. Not available with custom callback.
        :param customstate: The custom data passed via the ODIC state.
            Note that this only works with a custom_callback, and this will
            ignore destination.
        :type customstate: Anything that can be serialized
        :returns: A redirect response to start the login process.
        :rtype: Flask Response

        .. deprecated:: 1.0
           Use :func:`require_login` instead.
        """
        if not self._custom_callback and customstate:
            raise ValueError('Custom State is only avilable with a custom '
                             'handler')
        if 'oidc_csrf_token' not in session:
            csrf_token = urlsafe_b64encode(os.urandom(24)).decode('utf-8')
            session['oidc_csrf_token'] = csrf_token
        state = {
            'csrf_token': session['oidc_csrf_token'],
        }
        statefield = 'destination'
        statevalue = destination
        if customstate is not None:
            statefield = 'custom'
            statevalue = customstate
        state[statefield] = self.extra_data_serializer.dumps(
            statevalue).decode('utf-8')

        extra_params = {
            'state': urlsafe_b64encode(json.dumps(state).encode('utf-8')),
        }
        extra_params.update(current_app.config['OIDC_EXTRA_REQUEST_AUTH_PARAMS'])
        if current_app.config['OIDC_GOOGLE_APPS_DOMAIN']:
            extra_params['hd'] = current_app.config['OIDC_GOOGLE_APPS_DOMAIN']
        if current_app.config['OIDC_OPENID_REALM']:
            extra_params['openid.realm'] = current_app.config[
                'OIDC_OPENID_REALM']

        flow = self._flow_for_request()
        auth_url = '{url}&{extra_params}'.format(
            url=flow.step1_get_authorize_url(),
            extra_params=urlencode(extra_params))
        # if the user has an ID token, it's invalid, or we wouldn't be here
        self._set_cookie_id_token(None)
        return redirect(auth_url)

    def _is_id_token_valid(self, id_token):
        """
        Check if `id_token` is a current ID token for this application,
        was issued by the Apps domain we expected,
        and that the email address has been verified.

        @see: http://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation
        """
        if not id_token:
            return False

        # step 2: check issuer
        if id_token['iss'] not in current_app.config['OIDC_VALID_ISSUERS']:
            logger.error('id_token issued by non-trusted issuer: %s'
                         % id_token['iss'])
            return False

        if isinstance(id_token['aud'], list):
            # step 3 for audience list
            if self.flow.client_id not in id_token['aud']:
                logger.error('We are not a valid audience')
                return False
            # step 4
            if 'azp' not in id_token and len(id_token['aud']) > 1:
                logger.error('Multiple audiences and not authorized party')
                return False
        else:
            # step 3 for single audience
            if id_token['aud'] != self.flow.client_id:
                logger.error('We are not the audience')
                return False

        # step 5
        if 'azp' in id_token and id_token['azp'] != self.flow.client_id:
            logger.error('Authorized Party is not us')
            return False

        # step 6-8: TLS checked

        # step 9: check exp
        if int(time.time()) >= int(id_token['exp']):
            logger.error('Token has expired')
            return False

        # step 10: check iat
        if id_token['iat'] < (time.time() -
                              current_app.config['OIDC_CLOCK_SKEW']):
            logger.error('Token issued in the past')
            return False

        # (not required if using HTTPS?) step 11: check nonce

        # step 12-13: not requested acr or auth_time, so not needed to test

        # additional steps specific to our usage
        if current_app.config['OIDC_GOOGLE_APPS_DOMAIN'] and \
                id_token.get('hd') != current_app.config[
            'OIDC_GOOGLE_APPS_DOMAIN']:
            logger.error('Invalid google apps domain')
            return False

        if not id_token.get('email_verified', False) and \
                current_app.config['OIDC_REQUIRE_VERIFIED_EMAIL']:
            logger.error('Email not verified')
            return False

        return True

    WRONG_GOOGLE_APPS_DOMAIN = 'WRONG_GOOGLE_APPS_DOMAIN'

    def custom_callback(self, view_func):
        """
        Wrapper function to use a custom callback.
        The custom OIDC callback will get the custom state field passed in with
        redirect_to_auth_server.
        """

        @wraps(view_func)
        def decorated(*args, **kwargs):
            plainreturn, data = self._process_callback('custom')
            if plainreturn:
                return data
            else:
                return view_func(data, *args, **kwargs)

        self._custom_callback = decorated
        return decorated

    def _oidc_callback(self):
        plainreturn, data = self._process_callback('destination')
        if plainreturn:
            return data
        else:
            return redirect(data)

    def _process_callback(self, statefield):
        """
        Exchange the auth code for actual credentials,
        then redirect to the originally requested page.
        """
        # retrieve session and callback variables
        try:
            session_csrf_token = session.get('oidc_csrf_token')

            state = _json_loads(urlsafe_b64decode(request.args['state'].encode('utf-8')))
            csrf_token = state['csrf_token']

            code = request.args['code']
        except (KeyError, ValueError):
            logger.debug("Can't retrieve CSRF token, state, or code",
                         exc_info=True)
            return True, self._oidc_error()

        # check callback CSRF token passed to IdP
        # against session CSRF token held by user
        if csrf_token != session_csrf_token:
            logger.debug("CSRF token mismatch")
            return True, self._oidc_error()

        # make a request to IdP to exchange the auth code for OAuth credentials
        flow = self._flow_for_request()
        credentials = flow.step2_exchange(code)
        id_token = credentials.id_token
        if not self._is_id_token_valid(id_token):
            logger.debug("Invalid ID token")
            if id_token.get('hd') != current_app.config[
                'OIDC_GOOGLE_APPS_DOMAIN']:
                return True, self._oidc_error(
                    "You must log in with an account from the {0} domain."
                        .format(current_app.config['OIDC_GOOGLE_APPS_DOMAIN']),
                    self.WRONG_GOOGLE_APPS_DOMAIN)
            return True, self._oidc_error()

        # store credentials by subject
        # when Google is the IdP, the subject is their G+ account number
        self.credentials_store[id_token['sub']] = credentials.to_json()

        # Retrieve the extra statefield data
        try:
            response = self.extra_data_serializer.loads(state[statefield])
        except BadSignature:
            logger.error('State field was invalid')
            return True, self._oidc_error()

        # set a persistent signed cookie containing the ID token
        # and redirect to the final destination
        self._set_cookie_id_token(id_token)
        return False, response

    def _oidc_error(self, message='Not Authorized', code=401):
        return (message, code, {
            'Content-Type': 'text/plain',
        })

    def logout(self):
        """
        Request the browser to please forget the cookie we set, to clear the
        current session.

        Note that as described in [1], this will not log out in the case of a
        browser that doesn't clear cookies when requested to, and the user
        could be automatically logged in when they hit any authenticated
        endpoint.

        [1]: https://github.com/puiterwijk/flask-oidc/issues/5#issuecomment-86187023

        .. versionadded:: 1.0
        """
        # TODO: Add single logout
        self._set_cookie_id_token(None)

    # Below here is for resource servers to validate tokens
    def validate_token(self, token, scopes_required=None, roles_required=None):
        """
        This function can be used to validate tokens.

        Note that this only works if a token introspection url is configured,
        as that URL will be queried for the validity and scopes of a token.

        :param scopes_required: List of scopes that are required to be
            granted by the token before returning True.
        :type scopes_required: list
        :param roles_required: List of roles that are required to be present
            in the token before returning True
        :type roles_required: list

        :returns: True if the token was valid and contained the required
            scopes and roles. An ErrStr (subclass of string for which bool() is
            False) if an error occured.
        :rtype: Boolean or String

        .. versionadded:: 1.1
        """
        valid = self._validate_token(token, scopes_required, roles_required)
        if valid is True:
            return True
        else:
            return ErrStr(valid)

    def _validate_token(self, token, scopes_required=None, roles_required=None):
        """The actual implementation of validate_token."""
        if scopes_required is None:
            scopes_required = []
        if roles_required is None:
            roles_required = []
        scopes_required = set(scopes_required)
        roles_required = set(roles_required)

        token_info = None
        valid_token = False
        has_required_scopes = False
        if token:
            try:
                token_info = self._get_token_info(token)
            except Exception as ex:
                token_info = {'active': False}
                logger.error('ERROR: Unable to get token info')
                logger.error(str(ex))

            valid_token = token_info.get('active', False)

            if 'aud' in token_info and \
                    current_app.config['OIDC_RESOURCE_CHECK_AUD']:
                valid_audience = False
                aud = token_info['aud']
                clid = self.client_secrets['client_id']
                if isinstance(aud, list):
                    valid_audience = clid in aud
                else:
                    valid_audience = clid == aud

                if not valid_audience:
                    logger.error('Refused token because of invalid '
                                 'audience')
                    valid_token = False

            if valid_token:
                token_scopes = token_info.get('scope', '').split(' ')
                realm_roles = token_info.get('realm_access', {}).get('roles', [])
            else:
                token_scopes = []
                realm_roles = []
                logger.debug(f"Token is not marked as active. Token info: {token_info}")
            has_required_scopes = scopes_required.issubset(set(token_scopes))
            has_required_roles = roles_required.issubset(set(realm_roles))

            if not has_required_scopes:
                logger.debug(f'Token missed required scopes. Scopes present: {token_scopes}, '
                             f'scopes required: {scopes_required}')
            if not has_required_roles:
                logger.debug(f'Token missed required roles. Roles present: {realm_roles}, '
                             f'roles required: {roles_required}')

        if valid_token and has_required_scopes and has_required_roles:
            g.oidc_token_info = token_info
            return True

        if not valid_token:
            return 'Token required but invalid'
        elif not has_required_scopes:
            return 'Token does not have required scopes'
        else:
            return 'Something went wrong checking your token'

    def accept_token(self, require_token=False, scopes_required=None, render_errors=True, auth_header_key=None):
        """
        Use this to decorate view functions that should accept OAuth2 tokens,
        this will most likely apply to API functions.

        Tokens are accepted as part of the query URL (access_token value) or
        a POST form value (access_token).

        Note that this only works if a token introspection url is configured,
        as that URL will be queried for the validity and scopes of a token.

        :param auth_header_key: Key in headers which contains the Auth Token
        :param require_token: Whether a token is required for the current
            function. If this is True, we will abort the request if there
            was no token provided.
        :type require_token: bool
        :param scopes_required: List of scopes that are required to be
            granted by the token before being allowed to call the protected
            function.
        :type scopes_required: list
        :param render_errors: Whether or not to eagerly render error objects
            as JSON API responses. Set to False to pass the error object back
            unmodified for later rendering.
        :type render_errors: callback(obj) or None

        .. versionadded:: 1.0
        """

        def wrapper(view_func):
            @wraps(view_func)
            def decorated(*args, **kwargs):
                self._set_current_uri(request.script_root + request.path)
                token = self._extract_access_token(request, auth_header_key)

                validity = self.validate_token(token, scopes_required)
                if (validity is True) or (not require_token):
                    return view_func(*args, **kwargs)
                else:
                    return self._deny_access(validity, render_errors, 401)

            return decorated

        return wrapper

    def _extract_access_token(self, request, auth_header_key=None):
        if auth_header_key is None:
            auth_header_key = 'Authorization'
        # Prefer the auth_header_key header, but fall back to Authorization when that header is not present
        if request.headers.get(auth_header_key, request.headers.get('Authorization', '')).startswith('Bearer '):
            return request.headers[auth_header_key].split(None, 1)[1].strip()
        if 'access_token' in request.form:
            return request.form['access_token']
        if 'access_token' in request.args:
            return request.args['access_token']
        return None

    def check_authorization(self, require_token=False,
                            scopes_required=None,
                            render_errors=True,
                            validation_func=None,
                            roles_required=None,
                            auth_header_key=None):
        """
        Use this to decorate view functions that should accept OAuth2 tokens,
        this will most likely apply to API functions.

        Tokens are accepted as part of the query URL (access_token value) or
        a POST form value (access_token).

        This endpoint will also check if the current request has the permission
        to be executed. If the caller has no authorization it returns a 403.

        Note that this only works if a token introspection url is configured,
        as that URL will be queried for the validity and scopes of a token.
        It is tested with keycloak, not with other IdP's!

        :param auth_header_key: Key in headers which contains the Auth Token
        :param require_token: Whether a token is required for the current
            function. If this is True, we will abort the request if there
            was no token provided.
        :type require_token: bool
        :param scopes_required: List of scopes that are required to be
            granted by the token before being allowed to call the protected
            function.
        :type scopes_required: list
        :param render_errors: Whether or not to eagerly render error objects
            as JSON API responses. Set to False to pass the error object back
            unmodified for later rendering.
        :type render_errors: callback(obj) or None
        :param validation_func: Function that is called instead of the default
            implementation to check if the request should granted or not.
        :type validation_func: function or None
        :param roles_required: List of roles that are required to be present in
            the token
        :type roles_required: list

        .. versionadded:: 1.4
        """

        def wrapper(view_func):
            @wraps(view_func)
            def decorated(*args, **kwargs):
                if not self.keycloak_enabled:
                    return view_func(*args, **kwargs)
                func = validation_func or self._is_authorized
                self._set_current_uri(request.script_root + request.path)
                token = self._extract_access_token(request, auth_header_key)

                valid = self.validate_token(token, scopes_required, roles_required=roles_required)
                if (not require_token) or (valid and func(token)):
                    return view_func(*args, **kwargs)
                else:
                    return self._deny_access(valid, render_errors, 403)

            return decorated

        return wrapper

    def _deny_access(self, error_description, render_errors, error_code):
        """
        :param error_description: Error message to print
        :param render_errors: Whether or not to eagerly render error objects
            as JSON API responses. Set to False to pass the error object back
            unmodified for later rendering.
        :param error_code: HTTP-Code that is returned
        :return: Json-Object with error message and error code

        .. versionadded:: 1.4
        """
        response_body = {'error': 'invalid_token',
                         'error_description': error_description}
        if render_errors:
            response_body = json.dumps(response_body)
        return response_body, error_code, {'WWW-Authenticate': 'Bearer'}

    def _is_authorized(self, token, validation_func=None):
        """
        Proves if the call to the endpoint is authorized
        :param token: access token
        :type token: str
        :param: validation_func: function that implements the verification if the request is authorized
            If parameter is None, the default behaviour is used
        :type: validation_func: function
        :return: true if the user is authorized, otherwise false

        .. versionadded:: 1.4
        """
        if not self.keycloak_enabled:
            return True
        if token is None:
            logger.debug("The access token is not available.")
            return False
        if validation_func is None:
            validation_func = self._is_uri_allowed
        try:
            self._rpt_token = self.keycloakApi.authorize(token)
            if self._rpt_token is None:
                logger.debug("No rpt token received - authorization failed!")
                return False
            resources = self._get_permissions_from_token(self._rpt_token["access_token"])
            if resources is None:
                logger.debug("Empty resource set - authorization failed!")
                return False
            self._keycloak_realm_roles = self._get_realm_roles_from_token(self._rpt_token["access_token"])
            self._keycloak_client_roles = self._get_keycloak_client_roles_from_token(self._rpt_token["access_token"])
            for resource_id in resources:
                resource = self.keycloakApi.get_resource_info(resource_id["rsid"])
                if resource is not None and "uris" in resource and \
                        validation_func(self._rpt_token["access_token"], resource):
                    return True
        except Exception as e:
            logger.debug(str(e))
        logger.debug("Authorization failed!")
        return False

    def _set_current_uri(self, uri):
        if uri.endswith("/"):
            self.current_uri = uri[0: 0 + len(uri) - 1]
        else:
            self.current_uri = uri

    @property
    def rpt_token(self):
        return self._rpt_token

    @property
    def keycloak_realm_roles(self):
        return self._keycloak_realm_roles

    @property
    def keycloak_client_roles(self):
        return self._keycloak_client_roles

    def _get_realm_roles_from_token(self, token):
        """
        :param token: access token that contains realm roles
        :return: Returns a dict with all realm roles from the token

        .. versionadded:: 1.4
        """
        if token is None:
            return None
        token = self.keycloakApi.jwt_decode(token)
        return token.get('realm_access', {}).get('roles', None)

    def _get_keycloak_client_roles_from_token(self, token):
        """
        :param token: access token that contains client roles
        :return: Returns a dict with all client roles from the token

        .. versionadded:: 1.4
        """
        if token is None:
            return None
        token = self.keycloakApi.jwt_decode(token)
        return token.get('resource_access', {})

    def _get_permissions_from_token(self, rpt_token):
        """
        :param rpt_token: requesting party token that contains the permissions
        :return: Returns a dict with all permissions from the token

        .. versionadded:: 1.4
        """
        if not self.keycloak_enabled or rpt_token is None:
            return None
        rpt = self.keycloakApi.jwt_decode(rpt_token)
        if rpt is None:
            return None
        return rpt.get('authorization', {}).get('permissions', None)

    def _is_uri_allowed(self, rpt_token, resource):
        """
        Verifies that the access to a resource at a specific endpoint is granted or not
        :param rpt_token: requesting party token
        :type rpt_token: str
        :param resource: the resource at a specific endpoint
        :type resource: dict
        :return: true if it is an allowed URI, otherwise false

        .. versionadded:: 1.4
        """
        if not self.keycloak_enabled:
            return True
        logger.debug("Check URIs against the RPT token.")
        is_uri_allowed = False

        permissions = self._get_permissions_from_token(rpt_token)
        if permissions is None:
            logger.error("The minimum for the RPT is not satisfied.")
            return False

        if resource["_id"] in [p["rsid"] for p in permissions]:
            return self._is_access_granted(is_uri_allowed, resource)
        logger.error("Permission was not found in the RPT token.")
        return False

    def _is_access_granted(self, is_uri_allowed, resource):
        """
        :type is_uri_allowed: bool
        :param is_uri_allowed: if is_uri_allowed is True skip verification
        :type resource: dict
        :param resource: the resource at a specific endpoint

        .. versionadded:: 1.4
        """
        if not self.keycloak_enabled:
            return True
        for uri in resource["uris"]:
            if self._verify_uri(uri):
                return True
        return False

    def _verify_uri(self, uri):
        """
        Verify if the request URI follows the uri pattern
        :param uri: the URI pattern
        :return: true if the request path is conform pattern

        .. versionadded:: 1.4
        """
        if not self.keycloak_enabled:
            return True
        logger.debug("Verify if the request URI follows the uri pattern")
        return fnmatch.fnmatch(self.current_uri, uri)

    def _get_token_info(self, token):
        # We hardcode to use client_secret_post, because that's what the Google
        # oauth2client library defaults to
        request = {'token': token}
        headers = {'Content-type': 'application/x-www-form-urlencoded'}

        hint = current_app.config['OIDC_TOKEN_TYPE_HINT']
        if hint != 'none':
            request['token_type_hint'] = hint

        auth_method = current_app.config['OIDC_INTROSPECTION_AUTH_METHOD']
        if auth_method == 'client_secret_basic':
            basic_auth_string = '%s:%s' % (self.client_secrets['client_id'], self.client_secrets['client_secret'])
            basic_auth_bytes = bytearray(basic_auth_string, 'utf-8')
            headers['Authorization'] = 'Basic %s' % b64encode(basic_auth_bytes).decode('utf-8')
        elif auth_method == 'bearer':
            headers['Authorization'] = 'Bearer %s' % token
        elif auth_method == 'client_secret_post':
            request['client_id'] = self.client_secrets['client_id']
            if self.client_secrets['client_secret'] is not None:
                request['client_secret'] = self.client_secrets['client_secret']

        resp, content = httplib2.Http().request(
            self.client_secrets['token_introspection_uri'], 'POST',
            urlencode(request), headers=headers)
        # TODO: Cache this reply
        return _json_loads(content)
Exemplo n.º 42
0
 def generate_auth_token(self, username, password, expiration=0):
     s = Serializer(SECRET_KEY)
     return s.dumps({'username': username, 'password': password})
Exemplo n.º 43
0
	def generate_confirmation_token(self,expiration=3600):
		s = Serializer(current_app.config['SECRET_KEY'])
		return s.dumps({'confirm':self.id})
Exemplo n.º 44
0
 def sign(self, value):
     s = JSONWebSignatureSerializer(self.secret_key)
     return s.dumps(value).decode()
Exemplo n.º 45
0
 def generate_access_key(self):
     """Generate access key used for Restful API user authentication."""
     from app import app
     s = JSONWebSignatureSerializer(app.config['SECRET_KEY'])
     access_key = s.dumps({'username': self.username})
     self.access_key = access_key
Exemplo n.º 46
0
 def generate_auth_token(self, expiration=None):
     s = Serializer("dfjkahfjkldahfajklhdlash")
     return s.dumps({'id': self.id})
Exemplo n.º 47
0
 def generate_auth_token(self, expiration = 3600):
     s = Serializer(app.config['SECRET_KEY'])
     str = s.dumps({'id': self.id})
     return b64encode(str).decode('utf-8')
Exemplo n.º 48
0
 def generate_auth_token(self) :
     s = Serializer('secretkey')
     return s.dumps({'uid':self.uid})
Exemplo n.º 49
0
 def get_reset_token(self):
     s = Serializer(current_app.config['SECRET_KEY'], 'confirmation')
     return s.dumps({'user_id': self.id}).decode('utf-8')
Exemplo n.º 50
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.º 51
0
	def generate_auth_token(self, username, password, expiration = 0):
		s = Serializer(SECRET_KEY)
		return s.dumps({ 'username': username, 'password': password })
Exemplo n.º 52
0
 def generate_confirmation_token(self):
     s = Serializer(current_app.config['SECRET_KEY'])
     return s.dumps({'activate': self.id}).decode('utf-8')
Exemplo n.º 53
0
def get_unsubscribe_token(email):
    s = UntimedSerializer(current_app.config['SECRET_KEY'])
    return s.dumps({'email': email}).decode('utf-8')
Exemplo n.º 54
0
def PrivateKey(id):
    '''Generate a reusable private key for the user.'''
    t = Token(secret_key=current_app.config['SECRET_KEY'])
    return t.dumps({'id':id})
Exemplo n.º 55
0
 def __init__(self, jsonObject):
     s = JSONWebSignatureSerializer(settings.SECRET_KEY)
     self.jsonObject = s.dumps(jsonObject)
Exemplo n.º 56
0
 def generate_auth_token(self):
     s = Serializer(app.config['SECRET_KEY'])
     return s.dumps(self.email)
Exemplo n.º 57
0
 def generate_auth_token(self, expiration=None):
     s = Serializer(current_app.config['SECRET_KEY'])
     return s.dumps({'id': self.id}).decode('ascii')
Exemplo n.º 58
0
 def sign(self, value):
     if isinstance(value, bytes):
         value = value.decode("utf-8")
     s = JSONWebSignatureSerializer(self.secret_key)
     return s.dumps(value)
Exemplo n.º 59
0
 def generate_subscription_token(company_name, from_date, to_date):
     serializer = JSONSerializer(current_app.config['SECRET_KEY'])
     obj = {'company': company_name, 'from': from_date.isoformat(),
            'to': to_date.isoformat()}
     return base64_encode(serializer.dumps(obj)).decode('utf-8')