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
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("/")
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
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
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
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("/")