Beispiel #1
0
    def refresh_access_token(self, username, token_name):
        """
        Create a SciToken at the specified path.
        """

        token = scitokens.SciToken(algorithm="ES256",
                                   key=self._private_key,
                                   key_id=self._private_key_id)
        token.update_claims({'sub': username})
        user_authz = self.authz_template.format(username=username)
        token.update_claims({'scope': user_authz})
        token.update_claims({'ver': 'scitokens:2.0'})

        # Serialize the token and write it to a file
        try:
            serialized_token = token.serialize(issuer=self.token_issuer,
                                               lifetime=int(
                                                   self.token_lifetime))
        except TypeError:
            self.log.exception(
                "Failure when attempting to serialize a SciToken, likely due to algorithm mismatch"
            )
            return False

        # copied from the Vault credmon
        (tmp_fd, tmp_access_token_path) = tempfile.mkstemp(dir=self.cred_dir)
        with os.fdopen(tmp_fd, 'w') as f:
            if self.token_use_json:
                # use JSON if configured to do so, i.e. when
                # LOCAL_CREDMON_TOKEN_USE_JSON = True (default)
                f.write(
                    json.dumps({
                        "access_token": serialized_token.decode(),
                        "expires_in": int(self.token_lifetime),
                    }))
            else:
                # otherwise write a bare token string when
                # LOCAL_CREDMON_TOKEN_USE_JSON = False
                f.write(serialized_token.decode() + '\n')

        access_token_path = os.path.join(self.cred_dir, username,
                                         token_name + '.use')

        # atomically move new file into place
        try:
            atomic_rename(tmp_access_token_path, access_token_path)
        except OSError as e:
            self.log.exception(
                "Failure when writing out new access token to {}: {}.".format(
                    access_token_path, str(e)))
            return False
        else:
            return True
Beispiel #2
0
def oauth_return(provider):
    """
    Returning from OAuth provider
    """

    # get the provider name from the outgoing_provider set in oauth_login()
    provider = session.pop('outgoing_provider', get_provider_str(provider, ''))
    if not (provider in session['providers']):
        raise Exception(
            "Provider {0} not in list of providers".format(provider))

    provider_ad = get_provider_ad(provider, session['key_path'])

    # gather information from the key file classad
    client_id = provider_ad['ClientId']
    redirect_uri = provider_ad['ReturnUrl']
    state = session['providers'][provider]['state']
    oauth = OAuth2Session(client_id, state=state, redirect_uri=redirect_uri)

    # convert http url to https if needed
    if request.url.startswith("http://"):
        updated_url = request.url.replace('http://', 'https://', 1)
    else:
        updated_url = request.url

    # fetch token
    client_secret = provider_ad['ClientSecret']
    token_url = provider_ad['TokenUrl']
    token = oauth.fetch_token(token_url,
                              authorization_response=updated_url,
                              client_secret=client_secret,
                              method='POST')
    print('Got {0} token for user {1}'.format(provider,
                                              session['local_username']))

    # get user info if available
    # todo: make this more generic
    try:
        get_user_info = oauth.get(provider_ad['UserUrl'])
        user_info = get_user_info.json()
        if 'login' in user_info:  # box
            session['providers'][provider]['username'] = user_info['login']
        elif 'sub' in user_info:  # scitokens/jwt
            session['providers'][provider]['username'] = user_info['sub']
        else:
            session['providers'][provider]['username'] = '******'
    except ValueError:
        session['providers'][provider]['username'] = '******'

    # split off the refresh token from the access token if it exists
    try:
        refresh_token_string = token.pop('refresh_token')
    except KeyError:  # no refresh token
        use_refresh_token = False
        refresh_token_string = ''
    else:
        use_refresh_token = True

    refresh_token = {'refresh_token': refresh_token_string}

    # create a metadata file for refreshing the token
    metadata = {
        'client_id': client_id,
        'client_secret': client_secret,
        'token_url': token_url,
        'use_refresh_token': use_refresh_token
    }

    # atomically write the tokens to the cred dir
    cred_dir = get_cred_dir()
    user_cred_dir = os.path.join(cred_dir, session['local_username'])
    if not os.path.isdir(user_cred_dir):
        os.makedirs(user_cred_dir)
    refresh_token_path = os.path.join(user_cred_dir,
                                      provider.replace(' ', '_') + '.top')
    access_token_path = os.path.join(user_cred_dir,
                                     provider.replace(' ', '_') + '.use')
    metadata_path = os.path.join(user_cred_dir,
                                 provider.replace(' ', '_') + '.meta')

    # write tokens to tmp files
    (tmp_fd, tmp_access_token_path) = tempfile.mkstemp(dir=user_cred_dir)
    with os.fdopen(tmp_fd, 'w') as f:
        json.dump(token, f)

    (tmp_fd, tmp_refresh_token_path) = tempfile.mkstemp(dir=user_cred_dir)
    with os.fdopen(tmp_fd, 'w') as f:
        json.dump(refresh_token, f)

    (tmp_fd, tmp_metadata_path) = tempfile.mkstemp(dir=user_cred_dir)
    with os.fdopen(tmp_fd, 'w') as f:
        json.dump(metadata, f)

    # (over)write token files
    try:
        atomic_rename(tmp_access_token_path, access_token_path)
        atomic_rename(tmp_refresh_token_path, refresh_token_path)
        atomic_rename(tmp_metadata_path, metadata_path)
    except OSError as e:
        sys.stderr.write(e)

    # mark provider as logged in
    session['providers'][provider]['logged_in'] = True

    # check if other providers are logged in
    session['logged_in'] = True
    for provider in session['providers']:
        if session['providers'][provider]['logged_in'] == False:
            session['logged_in'] = False

    return redirect("/")
Beispiel #3
0
    def refresh_access_token(self, username, token_name):
        if OAuth2Session is None:
            raise ImportError("No module named OAuth2Session")

        # load the refresh token
        refresh_token_path = os.path.join(self.cred_dir, username,
                                          token_name + '.top')
        try:
            with open(refresh_token_path, 'r') as f:
                refresh_token = json.load(f)
        except IOError as ie:
            self.log.error("Could not open refresh token %s: %s",
                           refresh_token_path, str(ie))
            return False
        except ValueError as ve:
            self.log.error(
                "The format of the refresh token file %s is invalid; could not parse as JSON: %s",
                refresh_token_path, str(ve))
            return False

        # load metadata
        metadata_path = os.path.join(self.cred_dir, username,
                                     token_name + '.meta')
        try:
            with open(metadata_path, 'r') as f:
                token_metadata = json.load(f)
        except IOError as ie:
            self.log.error("Could not open metadata file %s: %s",
                           metadata_path, str(ie))
            return False
        except ValueError as ve:
            self.log.error(
                "The metadata file at %s is invalid; could not parse as JSON: %s",
                metadata_path, str(ve))
            return False

        # refresh the token (provides both new refresh and access tokens)
        oauth_client = OAuth2Session(token_metadata['client_id'],
                                     token=refresh_token)
        new_token = oauth_client.refresh_token(
            token_metadata['token_url'],
            client_id=token_metadata['client_id'],
            client_secret=token_metadata['client_secret'])
        try:
            refresh_token = {u'refresh_token': new_token.pop('refresh_token')}
        except KeyError:
            self.log.error("No %s refresh token returned for %s", token_name,
                           username)
            return False

        # write tokens to tmp files
        (tmp_fd, tmp_refresh_token_path) = tempfile.mkstemp(dir=self.cred_dir)
        with os.fdopen(tmp_fd, 'w') as f:
            json.dump(refresh_token, f)
        (tmp_fd, tmp_access_token_path) = tempfile.mkstemp(dir=self.cred_dir)
        with os.fdopen(tmp_fd, 'w') as f:
            json.dump(new_token, f)

        # atomically move new tokens in place
        access_token_path = os.path.join(self.cred_dir, username,
                                         token_name + '.use')
        try:
            atomic_rename(tmp_access_token_path, access_token_path)
            atomic_rename(tmp_refresh_token_path, refresh_token_path)
        except OSError as e:
            self.log.error(e)
            return False
        else:
            return True
Beispiel #4
0
    def refresh_access_token(self, username, token_name):
        # load the vault token plus vault bearer token URL from .top file
        top_path = os.path.join(self.cred_dir, username, token_name + '.top')
        try:
            with open(top_path, 'r') as f:
                top_data = json.load(f)
        except IOError as ie:
            self.log.warning("Could not open %s: %s", top_path, str(ie))
            return False
        except ValueError as ve:
            self.log.warning(
                "The file at %s is invalid; could not parse as JSON: %s",
                top_path, str(ve))
            return False

        if 'vault_token' not in top_data:
            self.log.error("vault_token missing from %s", top_path)
            return False

        if 'vault_url' not in top_data:
            self.log.error("vault_url missing from %s", top_path)
            return False

        params = {'minimum_seconds': self.get_minimum_seconds()}
        url = top_data['vault_url'] + '?' + urllib_parse.urlencode(params)
        headers = {'X-Vault-Token': top_data['vault_token']}
        request = urllib_request.Request(url=url, headers=headers)
        capath = '/etc/grid-security/certificates'
        if htcondor is not None and 'AUTH_SSL_CLIENT_CADIR' in htcondor.param:
            capath = htcondor.param['AUTH_SSL_CLIENT_CADIR']
        try:
            handle = urllib_request.urlopen(request, capath=capath)
        except Exception as e:
            self.log.error("read of access token from %s failed: %s", url,
                           str(e))
            return False
        try:
            response = json.load(handle)
        except Exception as e:
            self.log.error("could not parse json response from %s: %s", url,
                           str(e))
            return False

        finally:
            handle.close()

        if 'data' not in response or 'access_token' not in response['data']:
            self.log.error("access_token missing in read from %s", url)
            return False

        if 'scopes' in top_data or 'audience' in top_data:
            # Re-request access token with restricted scopes and/or audience.
            # These were not included in the initial request because currently
            #  this uses a separate token exchange flow in Vault which does
            #  not renew the refresh token, but we want that to happen too.
            # Just ignore the original access token in this case.

            if 'scopes' in top_data:
                params['scopes'] = top_data['scopes']
            if 'audience' in top_data:
                params['audience'] = top_data['audience']

            url = top_data['vault_url'] + '?' + urllib_parse.urlencode(params)

            try:
                handle = urllib_request.urlopen(request, capath=capath)
            except Exception as e:
                self.log.error(
                    "read of exchanged access token from %s failed: %s", url,
                    str(e))
                return False
            try:
                response = json.load(handle)
            except Exception as e:
                self.log.error("could not parse json response from %s: %s",
                               url, str(e))
                return False

            finally:
                handle.close()

            if 'data' not in response or 'access_token' not in response['data']:
                self.log.error(
                    "exchanged access_token missing in read from %s", url)
                return False

        access_token = response['data']['access_token']

        # write data to tmp file
        (tmp_fd, tmp_access_token_path) = tempfile.mkstemp(dir=self.cred_dir)
        with os.fdopen(tmp_fd, 'w') as f:
            f.write(access_token + '\n')

        # atomically move new file into place
        access_token_path = os.path.join(self.cred_dir, username,
                                         token_name + '.use')
        try:
            atomic_rename(tmp_access_token_path, access_token_path)
        except OSError as e:
            self.log.error(e)
            return False
        else:
            return True
Beispiel #5
0
    def refresh_access_token(self, username, token_name):
        """
        Create a SciToken at the specified path.
        """

        token = scitokens.SciToken(algorithm="ES256",
                                   key=self._private_key,
                                   key_id=self._private_key_id)
        token.update_claims({'sub': username})
        user_authz = self.authz_template.format(username=username)
        token.update_claims({'scope': user_authz})

        # Only set the version if we have one.  No version is valid, and implies scitokens:1.0
        if self.token_ver:
            token.update_claims({'ver': self.token_ver})

        # Convert the space separated list of audiences to a proper list
        # No aud is valid for scitokens:1.0 tokens.  Also, no resonable default.
        aud_list = self.token_aud.strip().split()
        if aud_list:
            token.update_claims({'aud': aud_list})
        elif self.token_ver.lower() == "scitokens:2.0":
            self.log.error(
                'No "aud" claim, LOCAL_CREDMON_TOKEN_AUDIENCE must be set when requesting a scitokens:2.0 token'
            )
            return False

        # Serialize the token and write it to a file
        try:
            serialized_token = token.serialize(issuer=self.token_issuer,
                                               lifetime=int(
                                                   self.token_lifetime))
        except TypeError:
            self.log.exception(
                "Failure when attempting to serialize a SciToken, likely due to algorithm mismatch"
            )
            return False

        # copied from the Vault credmon
        (tmp_fd, tmp_access_token_path) = tempfile.mkstemp(dir=self.cred_dir)
        with os.fdopen(tmp_fd, 'w') as f:
            if self.token_use_json:
                # use JSON if configured to do so, i.e. when
                # LOCAL_CREDMON_TOKEN_USE_JSON = True (default)
                f.write(
                    json.dumps({
                        "access_token": serialized_token.decode(),
                        "expires_in": int(self.token_lifetime),
                    }))
            else:
                # otherwise write a bare token string when
                # LOCAL_CREDMON_TOKEN_USE_JSON = False
                f.write(serialized_token.decode() + '\n')

        access_token_path = os.path.join(self.cred_dir, username,
                                         token_name + '.use')

        # atomically move new file into place
        try:
            atomic_rename(tmp_access_token_path, access_token_path)
        except OSError as e:
            self.log.exception(
                "Failure when writing out new access token to {}: {}.".format(
                    access_token_path, str(e)))
            return False
        else:
            return True
Beispiel #6
0
    def refresh_access_token(self, username, token_name):
        # load the vault token plus vault bearer token URL from .top file
        top_path = os.path.join(self.cred_dir, username, token_name + '.top')
        try:
            with open(top_path, 'r') as f:
                vault_token = f.readline().strip()
                vault_url = f.readline().strip()
        except Exception as e:
            self.log.error("Could not open vault token file %s: %s", top_path,
                           str(e))
            return False
        try:
            with open(top_path, 'r') as f:
                top_data = json.load(f)
        except IOError as ie:
            self.log.warning("Could not open %s: %s", top_path, str(ie))
            return True
        except ValueError as ve:
            self.log.warning(
                "The file at %s is invalid; could not parse as JSON: %s",
                top_path, str(ve))
            return True

        if 'vault_token' not in top_data:
            self.log.error("vault_token missing from %s", top_path)
            return False

        if 'vault_url' not in top_data:
            self.log.error("vault_url missing from %s", top_path)
            return False

        params = {'minimum_seconds': self.get_minimum_seconds()}
        url = top_data['vault_url'] + '?' + urllib_parse.urlencode(params)
        headers = {'X-Vault-Token': top_data['vault_token']}
        request = urllib_request.Request(url=url, headers=headers)
        capath = '/etc/grid-security/certificates'
        if htcondor is not None and 'AUTH_SSL_CLIENT_CADIR' in htcondor.param:
            capath = htcondor.param['AUTH_SSL_CLIENT_CADIR']
        try:
            handle = urllib_request.urlopen(request, capath=capath)
        except Exception as e:
            self.log.error("read of access token from %s failed: %s", url,
                           str(e))
            return False
        try:
            response = json.load(handle)
        except Exception as e:
            self.log.error("could not parse json response from %s: %s", url,
                           str(e))
            return False

        finally:
            handle.close()

        if 'data' not in response or 'access_token' not in response['data']:
            self.log.error("access_token missing in read from %s", url)
            return False

        access_token = response['data']['access_token']

        # write data to tmp file
        (tmp_fd, tmp_access_token_path) = tempfile.mkstemp(dir=self.cred_dir)
        with os.fdopen(tmp_fd, 'w') as f:
            f.write(access_token + '\n')

        # atomically move new file into place
        access_token_path = os.path.join(self.cred_dir, username,
                                         token_name + '.use')
        try:
            atomic_rename(tmp_access_token_path, access_token_path)
        except OSError as e:
            self.log.error(e)
            return False
        else:
            return True
def oauth_return(provider):
    """
    Returning from OAuth provider
    """

    # get the provider name from the outgoing_provider set in oauth_login()
    provider = session.pop('outgoing_provider', get_provider_str(provider, ''))
    if not ('providers' in session):
        sys.stderr.write(
            '"providers" key was not found in session object: {0}\n'.format(
                session))
        raise KeyError(
            'Key "providers" was not found in session object. This session is invalid.'
        )
    if not (provider in session['providers']):
        sys.stderr.write(
            'key {0} was not found in session["providers"] dict: {1}\n'.format(
                provider, session))
        raise KeyError(
            "Provider {0} was not found in list of providers. This session is invalid."
            .format(provider))
    provider_ad = get_provider_ad(provider, session['key_path'])

    # gather information from the key file classad
    client_id = provider_ad['ClientId']
    redirect_uri = provider_ad['ReturnUrl']
    state = session['providers'][provider]['state']
    oauth = OAuth2Session(client_id, state=state, redirect_uri=redirect_uri)

    # convert http url to https if needed
    if request.url.startswith("http://"):
        updated_url = request.url.replace('http://', 'https://', 1)
    else:
        updated_url = request.url

    # fetch token
    client_secret = provider_ad['ClientSecret']
    token_url = provider_ad['TokenUrl']
    token = oauth.fetch_token(token_url,
                              authorization_response=updated_url,
                              client_secret=client_secret,
                              method='POST',
                              **fetch_token_kwargs)
    print('Got {0} token for user {1}'.format(provider,
                                              session['local_username']))

    # get user info if available
    try:
        (user_url,
         user_field_keys) = api_endpoints.user(provider_ad['TokenUrl'])
        if user_url is not None:
            get_user_info = oauth.get(user_url)
            user_info = get_user_info.json()
            for user_field in user_field_keys:
                username = user_info[user_field]
            username = str(username)
            session['providers'][provider]['username'] = username
        elif 'sub' in token:  # scitokens/jwt
            session['providers'][provider]['username'] = token['sub']
        elif 'name' in token:  # scitokens/jwt
            session['providers'][provider]['username'] = token['name']
        else:
            session['providers'][provider]['username'] = '******'
    except ValueError:
        session['providers'][provider]['username'] = '******'

    # split off the refresh token from the access token if it exists
    try:
        refresh_token_string = token.pop('refresh_token')
    except KeyError:  # no refresh token
        use_refresh_token = False
        refresh_token_string = ''
    else:
        use_refresh_token = True

    refresh_token = {'refresh_token': refresh_token_string}

    # create a metadata file for refreshing the token
    metadata = {
        'client_id': client_id,
        'client_secret': client_secret,
        'token_url': token_url,
        'use_refresh_token': use_refresh_token
    }

    # atomically write the tokens to the cred dir
    cred_dir = get_cred_dir()
    user_cred_dir = os.path.join(cred_dir, session['local_username'])
    if not os.path.isdir(user_cred_dir):
        os.makedirs(user_cred_dir)
    refresh_token_path = os.path.join(user_cred_dir,
                                      provider.replace(' ', '_') + '.top')
    access_token_path = os.path.join(user_cred_dir,
                                     provider.replace(' ', '_') + '.use')
    metadata_path = os.path.join(user_cred_dir,
                                 provider.replace(' ', '_') + '.meta')

    # write tokens to tmp files
    try:
        (tmp_fd, tmp_access_token_path) = tempfile.mkstemp(dir=user_cred_dir)
    except OSError as oe:
        print(
            "Failed to create temporary file in the user credential directory: {0}"
            .format(str(oe)))
        raise
    with os.fdopen(tmp_fd, 'w') as f:
        json.dump(token, f)

    (tmp_fd, tmp_refresh_token_path) = tempfile.mkstemp(dir=user_cred_dir)
    with os.fdopen(tmp_fd, 'w') as f:
        json.dump(refresh_token, f)

    (tmp_fd, tmp_metadata_path) = tempfile.mkstemp(dir=user_cred_dir)
    with os.fdopen(tmp_fd, 'w') as f:
        json.dump(metadata, f)

    # (over)write token files
    try:
        atomic_rename(tmp_access_token_path, access_token_path)
        atomic_rename(tmp_refresh_token_path, refresh_token_path)
        atomic_rename(tmp_metadata_path, metadata_path)
    except OSError as e:
        sys.stderr.write('{0}\n'.format(str(e)))

    # mark provider as logged in
    session['providers'][provider]['logged_in'] = True

    # check if other providers are logged in
    session['logged_in'] = True
    for provider in session['providers']:
        if session['providers'][provider]['logged_in'] == False:
            session['logged_in'] = False

    # cleanup key file if logged in
    if session['logged_in']:
        print('Attempting to remove session file {0}'.format(
            session['key_path']))
        try:
            os.unlink(session['key_path'])
        except OSError as e:
            sys.stderr.write('Could not remove session file {0}: {1}\n'.format(
                session['key_path'], str(e)))

    return redirect("/")