Commit 8536534e authored by Tanner Prestegard's avatar Tanner Prestegard

Consolidate all auth types under a single client class

One client class (GraceDb) will now be usable for all authentication
types (X509, basic, and None).  Users can provide credentials to the
constructor directly (path to cert and key or proxy file, or
username and password). If those are not provided, it will look for
X509 credentials in environment variables and then in an expected
default location.  Basic auth credentials will be looked up in the
default location for a .netrc file.

The client instantiation will no longer fail if credentials are not
found (unless the user explicitly sets the 'fail_if_noauth' property
to True).
parent da247e04
......@@ -20,7 +20,7 @@ from __future__ import print_function
import os, sys, shutil
import json
import six
from ligo.gracedb.rest import GraceDb, GraceDbBasic
from ligo.gracedb.rest import GraceDb
from ligo.gracedb.rest import DEFAULT_SERVICE_URL
DEFAULT_COLUMNS = "graceid,labels,group,pipeline,search,far,gpstime,created,dataurl"
......@@ -73,58 +73,46 @@ typeCodeMap = {
validTypes = list(typeCodeMap.keys())
#-----------------------------------------------------------------
# This is a factory for client classes.
# Given a base client class with the correct properties, derive
# one suitable for use with this command-line tool. In practice,
# the base class here will either be the X509 auth GraceDb class
# or the basic auth GraceDbBasic class.
def derive_client(ClientBase=GraceDb):
class client(ClientBase):
def __init__(self, url=DEFAULT_SERVICE_URL, *args, **kwargs):
if (url[-1] != '/'):
url += '/'
self.url = url
super(client, self).__init__(url, *args, **kwargs)
def download(self, graceid, filename, destfile):
# Check that we *could* write the file before we
# go to the trouble of getting it. Also, try not
# to open a file until we know we have data.
if not hasattr(destfile, 'read') and destfile != "-":
if not os.access(os.path.dirname(os.path.abspath(destfile)), os.W_OK):
raise IOError("%s: Permission denied" % destfile)
response = self.files(graceid, filename)
if response.status == 200:
if not hasattr(destfile, 'read'):
if destfile == '-':
destfile = sys.stdout
# Python 2/3 compatibility
if hasattr(destfile, 'buffer'):
destfile = destfile.buffer
else:
destfile = open(destfile, "wb")
shutil.copyfileobj(response, destfile)
return 0
else:
return "Error. (%d) %s" % (response.status, response.reason)
# Hamstring 'adjustResponse' from the example REST client.
# We don't want it messing with the response from the server.
def adjustResponse(self, response):
response.json = lambda: self.load_json_or_die(response)
return response
@classmethod
def output_and_die(cls, msg):
sys.stderr.write(msg)
sys.exit(1)
# Override and add a few methods to the base GraceDb client class
class GraceDbClient(GraceDb):
def __init__(self, url=DEFAULT_SERVICE_URL, *args, **kwargs):
if (url[-1] != '/'):
url += '/'
self.url = url
super(GraceDbClient, self).__init__(url, *args, **kwargs)
def download(self, graceid, filename, destfile):
# Check that we *could* write the file before we
# go to the trouble of getting it. Also, try not
# to open a file until we know we have data.
if not hasattr(destfile, 'read') and destfile != "-":
if not os.access(os.path.dirname(os.path.abspath(destfile)), os.W_OK):
raise IOError("%s: Permission denied" % destfile)
response = self.files(graceid, filename)
if response.status == 200:
if not hasattr(destfile, 'read'):
if destfile == '-':
destfile = sys.stdout
# Python 2/3 compatibility
if hasattr(destfile, 'buffer'):
destfile = destfile.buffer
else:
destfile = open(destfile, "wb")
shutil.copyfileobj(response, destfile)
return 0
else:
return "Error. (%d) %s" % (response.status, response.reason)
return client
# Hamstring 'adjustResponse' from the example REST client.
# We don't want it messing with the response from the server.
def adjustResponse(self, response):
response.json = lambda: self.load_json_or_die(response)
return response
# Get X509 client in global scope
# This is required for passing the unit tests.
Client = derive_client()
@classmethod
def output_and_die(cls, msg):
sys.stderr.write(msg)
sys.exit(1)
#-----------------------------------------------------------------
# Main
......@@ -210,6 +198,7 @@ Credentials are looked for in this order:
(2) $(X509_USER_PROXY)
(3) Default location of grid proxy ( /tmp/x509up_u$(UID) )
(4) $(HOME)/.globus/usercert.pem / $(HOME)/.globus/userkey.pem
(5) Basic auth credentials in $(HOME)/.netrc
Note that comments can only be 200 characters long.
Longer strings will be truncated.""" % {
......@@ -226,12 +215,6 @@ Longer strings will be truncated.""" % {
help="GraCEDb Service URL", metavar="URL")
op.add_option("-f", "--filename", dest="filename",
help="If data is read from stdin, use this as the filename.", metavar="NAME")
op.add_option("-a", "--alert", dest="alert",
help="Send an LV alert (deprecated; alerts sent by default)",
action="store_true", default=None
)
op.add_option("-c", "--columns", dest="columns",
help="Comma separated list of event attributes to include in results (only meaningful in search)",
default=DEFAULT_COLUMNS
......@@ -249,18 +232,28 @@ Longer strings will be truncated.""" % {
help="tag display name (ignored for existing tags)",
default=None
)
op.add_option("-b", "--use-basic-auth", dest="use_basic_auth",
help="Use basic auth with a .netrc file. Available to non-LVC members only.",
op.add_option("-n", "--force-noauth", dest="force_noauth",
help="Do not use any authentication credentials.",
action="store_true", default=False
)
op.add_option("-o", "--offline", dest="offline", action="store_true",
help=("Signifies that an event was found by an offline "
"pipeline. Used when creating a new event."), default=False
)
op.add_option("-a", "--username", dest="username", type=str,
help=("Username for basic auth"), default=None
)
op.add_option("-b", "--password", dest="password", type=str,
help=("Password for basic auth"), default=None
)
op.add_option("--labels", dest="labels",help=("Defines labels for creating"
" a new event. Should be a comma-separated list of labels."),
default=[], type=str
)
op.add_option("--show-creds", dest="show_creds", help=("Prints your "
"authentication credentials and exits."), default=False,
action="store_true"
)
options, args = op.parse_args(args)
if isinstance(options.labels, str):
......@@ -285,14 +278,10 @@ Longer strings will be truncated.""" % {
os.environ.get('GRACEDB_SERVICE_URL', None) or \
DEFAULT_SERVICE_URL
# Client subclass according to preferred auth method.
global Client
if options.use_basic_auth:
Client = derive_client(GraceDbBasic)
if options.alert is not None:
warning("alert option is deprecated. Alerts are now sent by default.")
# Compile dict of kwargs for instantiating the client class
client_args = {}
# Process proxy args
proxyport = None
if proxy and proxy.find(':') > 0:
try:
......@@ -300,12 +289,21 @@ Longer strings will be truncated.""" % {
proxyport = int(proxyport)
except:
op.error("Malformed proxy: '%s'" % proxy)
if proxyport:
client = Client(service,
proxy_host=proxy,
proxy_port=proxyport)
else:
client = Client(service, proxy_host=proxy)
client_args['proxy_host'] = proxy
client_args['proxy_port'] = proxyport
# Auth args
client_args['force_noauth'] = options.force_noauth
client_args['username'] = options.username
client_args['password'] = options.password
# Instantiate the client
client = GraceDbClient(service, **client_args)
if options.show_creds:
client.show_credentials()
print()
exit(0)
if len(args) < 1:
op.error("not enough arguments")
......
This diff is collapsed.
......@@ -61,7 +61,7 @@ import os
from datetime import datetime
import time
from ligo.gracedb.rest import GraceDb, GraceDbBasic
from ligo.gracedb.rest import GraceDb
from six.moves import range
......@@ -170,7 +170,6 @@ class TestMain(TestGraceDb):
Upload a large file.
Issue https://bugs.ligo.org/redmine/issues/951
"""
uploadFile = os.path.join(self.TEST_DATA_DIR, "big.data")
r = self._gracedb.writeLog(self._eventId, "FILE UPLOAD", uploadFile)
self.assertEqual(r.status, 201) # CREATED
......
......@@ -29,7 +29,7 @@ try:
except ImportError:
import mock
from ligo.gracedb.rest import GraceDb, GraceDbBasic
from ligo.gracedb.rest import GraceDb
from ligo.gracedb.exceptions import HTTPError
from ligo.gracedb.test.test import TestGraceDb
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment