-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 3005872
Showing
7 changed files
with
755 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
FROM ubuntu:20.04 | ||
|
||
RUN \ | ||
apt-get -y update && \ | ||
export DEBIAN_FRONTEND=noninteractive && \ | ||
export TZ=Etc/UTC && \ | ||
apt-get -y install gcc make curl git openjdk-8-jre | ||
|
||
# Copy in the SDK | ||
COPY --from=kbase/kb-sdk:20180808 /src /sdk | ||
RUN sed -i 's|/src|/sdk|g' /sdk/bin/* | ||
|
||
RUN \ | ||
V=py38_4.10.3 && \ | ||
curl -o conda.sh -s https://repo.anaconda.com/miniconda/Miniconda3-${V}-Linux-x86_64.sh && \ | ||
sh ./conda.sh -b -p /opt/conda3 && \ | ||
rm conda.sh | ||
|
||
ENV PATH=/opt/conda3/bin:$PATH:/sdk/bin | ||
|
||
# Install packages including mamba | ||
RUN \ | ||
conda install -c conda-forge mamba | ||
|
||
ADD ./requirements.txt /tmp/ | ||
RUN \ | ||
pip install -r /tmp/requirements.txt | ||
|
||
# Add in some legacy modules | ||
ADD biokbase /opt/conda3/lib/python3.8/site-packages/biokbase | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
Copyright (c) 2022 The KBase Project and its Contributors | ||
|
||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: | ||
|
||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
# SDK Base Python Image | ||
|
||
This is a very minimal base python image for building KBase SDK apps. | ||
|
||
|
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,339 @@ | ||
""" | ||
Kbase wrappers around Globus Online Nexus client libraries. We wrap the Nexus | ||
libraries to provide a similar API between the Perl Bio::KBase::Auth* libraries | ||
and the python version | ||
In this module, we follow standard Python idioms of raising exceptions for | ||
various failure states ( Perl modules returned error states in error_msg field) | ||
""" | ||
from biokbase.nexus.client import NexusClient | ||
from ConfigParser import ConfigParser | ||
import os | ||
from urlparse import urlparse | ||
from pprint import pformat | ||
import requests | ||
import re | ||
|
||
""" | ||
Package "globals" | ||
kb_config | ||
trust_token_signers | ||
attrs | ||
authdata | ||
config | ||
tokenend | ||
AuthSvcHost | ||
RoleSvcURL | ||
nexusconfig | ||
""" | ||
__version__ = "0.9" | ||
|
||
kb_config = os.environ.get('KB_DEPLOYMENT_CONFIG',os.environ['HOME']+"/.kbase_config") | ||
|
||
trust_token_signers = [ 'https://nexus.api.globusonline.org/goauth/keys' ] | ||
attrs = [ 'user_id', 'token','client_secret', 'keyfile', | ||
'keyfile_passphrase','password','sshagent_keys', | ||
'sshagent_keyname'] | ||
|
||
# authdata stores the configuration key/values from any configuration file | ||
authdata = dict() | ||
if os.path.exists( kb_config): | ||
try: | ||
conf = ConfigParser() | ||
conf.read(kb_config) | ||
# strip down whatever we read to only what is legit | ||
for x in attrs: | ||
authdata[x] = conf.get('authentication',x) if conf.has_option('authentication',x) else None | ||
except Exception, e: | ||
print "Error while reading INI file %s: %s" % (kb_config, e) | ||
tokenenv = authdata.get( 'tokenvar', 'KB_AUTH_TOKEN') | ||
# Yes, some variables are camel cased and others are all lower. Trying to maintain | ||
# the attributes names from the perl version which was a mishmash too. regret. | ||
AuthSvcHost = authdata.get( 'servicehost', "https://nexus.api.globusonline.org/") | ||
# Copied from perl libs for reference, not used here | ||
#ProfilePath = authdata.get( 'authpath', "/goauth/token") | ||
RoleSvcURL = authdata.get( 'rolesvcurl', "https://kbase.us/services/authorization/Roles") | ||
nexusconfig = { 'cache' : { 'class': 'biokbase.nexus.token_utils.InMemoryCache', | ||
'args': [], | ||
}, | ||
'server' : urlparse(AuthSvcHost).netloc, | ||
'verify_ssl' : False, | ||
'client' : None, | ||
'client_secret' : None} | ||
# Compile a regex for parsing out user_id's from tokens | ||
token_userid = re.compile( '(?<=^un=)\w+') | ||
|
||
def LoadConfig(): | ||
""" | ||
Method to load configuration from INI style files from the file in kb_config | ||
""" | ||
global kb_config,authdata,tokenenv,AuthSvcHost,RolesSvcHost | ||
global RoleSvcURL,nexusconfig,conf | ||
|
||
kb_config = os.environ.get('KB_DEPLOYMENT_CONFIG',os.environ['HOME']+"/.kbase_config") | ||
|
||
if os.path.exists( kb_config): | ||
try: | ||
conf = ConfigParser() | ||
conf.read(kb_config) | ||
# strip down whatever we read to only what is legit | ||
for x in attrs: | ||
authdata[x] = conf.get('authentication',x) if conf.has_option('authentication',x) else None | ||
except Exception, e: | ||
print "Error while reading INI file %s: %s" % (kb_config, e) | ||
tokenenv = authdata.get( 'tokenvar', 'KB_AUTH_TOKEN') | ||
# Yes, some variables are camel cased and others are all lower. Trying to maintain | ||
# the attributes names from the perl version which was a mishmash too. regret. | ||
AuthSvcHost = authdata.get( 'servicehost', "https://nexus.api.globusonline.org/") | ||
# Copied from perl libs for reference, not used here | ||
#ProfilePath = authdata.get( 'authpath', "/goauth/token") | ||
RoleSvcURL = authdata.get( 'rolesvcurl', "https://kbase.us/services/authorization/Roles") | ||
nexusconfig = { 'cache' : { 'class': 'biokbase.nexus.token_utils.InMemoryCache', | ||
'args': [], | ||
}, | ||
'server' : urlparse(AuthSvcHost).netloc, | ||
'verify_ssl' : False, | ||
'client' : None, | ||
'client_secret' : None} | ||
|
||
def SetConfigs( configs): | ||
""" | ||
Method used to set configuration directives in INI file kb_config | ||
Takes as a parameter a dictionary of config directives within the authentication section | ||
that need to be set/unset. If there is a dictionary entry where the value is None | ||
then the config setting will be deleted | ||
""" | ||
global kb_config,authdata,tokenenv,AuthSvcHost,RolesSvcHost | ||
global RoleSvcURL,nexusconfig,conf | ||
|
||
conf = ConfigParser() | ||
if os.path.exists( kb_config): | ||
conf.read(kb_config) | ||
if not conf.has_section('authentication'): | ||
conf.add_section('authentication') | ||
for key in configs.keys(): | ||
if configs[key] is not None: | ||
conf.set('authentication',key, configs[key]) | ||
else: | ||
conf.remove_option('authentication',key) | ||
with open(kb_config, 'wb') as configfile: | ||
conf.write(configfile) | ||
LoadConfig() | ||
|
||
class AuthCredentialsNeeded( Exception ): | ||
""" | ||
Simple wrapper around Exception class that flags the fact that we don't have | ||
enough credentials to authenticate, which is distinct from having bad or bogus | ||
credentials | ||
""" | ||
pass | ||
|
||
class AuthFail( Exception ): | ||
""" | ||
Simple wrapper around Exception class that flags our credentials are bad or bogus | ||
""" | ||
pass | ||
|
||
class Token: | ||
""" | ||
Class that handles token requests and validation. This is basically a wrapper | ||
around the biokbase.nexus.client.NexusClient class from GlobusOnline that provides a | ||
similar API to the perl Bio::KBase::AuthToken module. For KBase purposes | ||
we have modified the base Globus Online classes to support ssh agent based | ||
authentication as well. | ||
In memory caching is provided by the underlying biokbase.nexus.client implementation. | ||
Instance Attributes: | ||
user_id | ||
password | ||
token | ||
keyfile | ||
client_secret | ||
keyfile_passphrase | ||
sshagent_keyname | ||
""" | ||
|
||
def __init__(self, **kwargs): | ||
""" | ||
Constructor for Token class will accept these optional parameters attributes in | ||
order to initialize the object: | ||
user_id, password, token, keyfile, client_secret, keyfile_passphrase, sshagent_keyname | ||
If user_id is provided among the initializers, the get() method will be called at the | ||
end of initialization to attempt to fetch a token from the service defined in | ||
AuthSvcHost. If there are not enough credentials to authenticate, we ignore the | ||
exception. However if there are enough credentials and they fail to authenticate, | ||
the exception will be reraised. | ||
If there is a ~/kbase_config INI file, it will be used to fill in values when there | ||
are no initialization values given - this can be short circuited by setting | ||
ignore_kbase_config to true among the initialization params | ||
""" | ||
global nexusconfig | ||
attrs = [ 'keyfile','keyfile_passphrase','user_id','password','token','client_secret','sshagent_keyname'] | ||
for attr in attrs: | ||
setattr( self, attr, kwargs.get(attr,None)) | ||
self.nclient = NexusClient(nexusconfig) | ||
self.nclient.user_key_file = self.keyfile | ||
|
||
if self.nclient.__dict__.has_key("agent_keys"): | ||
self.sshagent_keys = self.nclient.agent_keys | ||
else: | ||
self.sshagent_keys = dict() | ||
|
||
# Flag to mark if we got default values from .kbase_config file | ||
defattr = reduce( lambda x,y: x or (authconf.get(y, None) is not None), attrs) | ||
# if we have a user_id defined, try to get a token with whatever else was given | ||
# if it fails due to not enough creds, try any values from ~/.kbase_config | ||
if (self.user_id): | ||
try: | ||
self.get() | ||
except AuthCredentialsNeeded: | ||
pass | ||
except Exception, e: | ||
raise e | ||
elif os.environ.get(tokenenv): | ||
self.token = os.environ[tokenenv] | ||
elif defattr and not kwargs.get('ignore_kbase_config'): | ||
for attr in attrs: | ||
if authdata.get(attr) is not None: | ||
setattr(self,attr,authdata[attr]) | ||
try: | ||
self.get() | ||
except AuthCredentialsNeeded: | ||
pass | ||
except Exception, e: | ||
raise e | ||
if self.user_id is None and self.token: | ||
# parse out the user_id and set it | ||
self.user_id = token_userid.search( self.token).group(0) | ||
|
||
def validate( self, token = None): | ||
""" | ||
Method that validates the contents of self.token against the authentication service backend | ||
This method caches results, so an initial validation will be high latency due to the | ||
network round trips, but subsequent validations will return very quickly | ||
A successfully validated token will return user_id | ||
Invalid tokens will generate a ValueError exception | ||
""" | ||
if token is not None: | ||
res = self.nclient.validate_token( token) | ||
else: | ||
res = self.nclient.validate_token( self.token) | ||
self.user_id = res[0] | ||
return self.user_id | ||
|
||
def get(self, **kwargs): | ||
""" | ||
Use either explicit parameters or the current instance vars to authenticate and retrieve a | ||
token from GlobusOnline (or whoever else is defined in the AuthSvcHost class attribute). | ||
The following parameters are optional, and will be assigned to the instance vars before | ||
attempting to fetch a token: | ||
keyfile, keyfile_passphrase, user_id, password, client_secret, sshagent_keyname | ||
A user_id and any of the following will be enough to attempt authentication: | ||
keyfile, keyfile_passphrase, password, sshagent_keyname | ||
If there are not enough credentials, then an AuthCredentialsNeeded exception will be raised | ||
If the underlying Globus libraries fail to authenticate, the exception will be passed up | ||
Success returns self, but with the token attribute containing a good token, an AuthFail | ||
exception will be thrown if the credentials are rejected by Globus Online | ||
Note: authentication with an explicit RSA client_secret is not currently supported | ||
""" | ||
# attributes that we would allow to be passed in via kwargs | ||
attrs = [ 'keyfile','keyfile_passphrase','user_id','password','token','client_secret','sshagent_keyname'] | ||
for attr in attrs: | ||
if attr in kwargs: | ||
setattr( self, attr, kwargs[attr]) | ||
# override the user_key_file default in the nclient object | ||
self.nclient.user_key_file = self.keyfile | ||
# in the perl libraries, if we have a user_id, no other credentials, and a single | ||
# available sshagent_keyname from ssh_agent, default to using that for auth | ||
if (self.user_id and not ( self.password or self.sshagent_keyname or self.keyfile) | ||
and (len(self.sshagent_keys.keys()) == 1)): | ||
self.sshagent_keyname = self.sshagent_keys.keys()[0] | ||
if not (self.user_id and ( self.password or self.sshagent_keyname or self.keyfile)): | ||
raise AuthCredentialsNeeded( "Need either (user_id, client_secret || password || sshagent_keyname) to be defined.") | ||
if self.keyfile: | ||
self.nclient.user_key_file = self.keyfile | ||
if (self.user_id and self.keyfile): | ||
passphrase = kwargs.get("keyfile_passphrase",self.keyfile_passphrase) | ||
res = self.nclient.request_client_credential( self.user_id, lambda : passphrase ) | ||
elif (self.user_id and self.password): | ||
res = self.nclient.request_client_credential( self.user_id, self.password) | ||
elif (self.user_id and self.sshagent_keyname): | ||
res = self.nclient.request_client_credential_sshagent( self.user_id, self.sshagent_keyname) | ||
else: | ||
raise AuthCredentialsNeeded("Authentication with explicit client_secret not supported - please put key in file or sshagent") | ||
if 'access_token' in res: | ||
self.token = res['access_token'] | ||
else: | ||
raise AuthFail('Could not authenticate with values: ' + pformat(self.__dict__)) | ||
return self | ||
|
||
def get_sessDB_token(): | ||
pass | ||
|
||
class User: | ||
top_attrs = { "user_id" : "username", | ||
"verified" : "email_validated", | ||
"opt_in" : "opt_in", | ||
"name" : "fullname", | ||
"email" : "email", | ||
"system_admin" : "system_admin" } | ||
|
||
def __init__(self, **kwargs): | ||
""" | ||
Constructor for User class will accept these optional parameters attributes in | ||
order to initialize the object: | ||
user_id, password, token, enabled, groups, name, email, verified | ||
If a token is provided among the initializers, the get() method will be called at the | ||
end of initialization to attempt to fetch the user profile from Globus Online | ||
The ~/.kbase_config file is only indirectly supported - use it to get a token, and then | ||
use that token as an initializer to this function to fetch a profile | ||
""" | ||
global nexusconfig | ||
attrs = [ 'user_id', 'enabled', 'groups', 'name', 'email', 'verified' ] | ||
for attr in attrs: | ||
setattr( self, attr, kwargs.get(attr,None)) | ||
if kwargs['token']: | ||
self.authToken = Token( token = kwargs['token']) | ||
self.token = self.authToken.token | ||
self.get() | ||
return | ||
|
||
def get(self, **kwargs): | ||
if 'token' in kwargs: | ||
self.authToken = Token( token = kwargs['token']) | ||
self.token = self.authToken.token | ||
if not self.token: | ||
raise AuthCredentialsNeeded( "Authentication token required") | ||
p = { 'custom_fields' : '*', | ||
'fields' : 'groups,username,email_validated,fullname,email' | ||
} | ||
headers = { 'Authorization' : 'Globus-Goauthtoken ' + self.token } | ||
resp = requests.get( AuthSvcHost+"users/" + self.authToken.user_id, params = p, | ||
headers = headers) | ||
profile = resp.json() | ||
for attr,go_attr in self.top_attrs.items(): | ||
setattr( self, attr, profile.get( go_attr)) | ||
# pull out the name field from the groups dict entries and put into groups | ||
setattr( self, 'groups', [ x['name'] for x in resp.json['groups']]) | ||
if 'custom_fields' in profile: | ||
for attr in profile['custom_fields'].keys(): | ||
setattr( self, attr, profile['custom_fields'][attr]) | ||
return self | ||
|
||
def update(self, **kwargs): | ||
pass |
Oops, something went wrong.