I develop class that manages connections to external resources. Its responsibility is reading credentials from given source and returning authenticated connections, like:
def get_svn_connection(self):
username, pwd=self.get_credentials("SVN")
client=self.get_svn_client(username, pwd)
return client
def get_db_connection(self):
username, pwd, url, schema=self.get_credentials("DB")
client=self.get_db_client(username, pwd, url, schema)
return client
Today I met new use case: sometimes user needs to get only credentials without connection. So, following current solution, I have to create methods get_svn_credentials, get_db_credentials and so on. It looks very redundant so I want to refactor this structure.
My ideas are:
Create subclasses for every connection type and for each of them define methods get_connection and get_credentials. The problems are: 1) I want one class to rule all the connections; 2) I would have to use multi-inheritance (possible in Python, but would not work in, for example, Java - if I'll meet this problem in future)
Create methods get_connection(type) and get_credentials(type) and for every known connection type specified by string return corresponding instance. Problem is I have to create a switch which considered bad in OOP.
Use metaprogramming to generate methods for every connection types. Not obvious and Python-specific.
How would you design this?
You could have a Credentials class, which can be used to authenticate another function call:
class SvnCredentials:
def __init__(username, password):
self._username = username
self._password = password
def authenticate(function):
return function(self._username, self._password)
Usage:
>>> credentials = SvnCredentials('AzureDiamond ', 'hunter2')
>>> client = credentials.authenticate(get_svn_client)
Just to add on Jean-Francois Fabre answer, you can do one function the following way
def get_svn_connection(self, decision_str):
if decision_str = "DB":
return self.get_connection("DB",self.get_db_client)
if decision_str = "SVN":
return self.get_connection(decision_str, self.get_svn_client)
I don't think you can do it any more concisely than that, and I am not convinced it is better than Jean's way...
def get_svn_connection(self, credentials=False):
username, pwd=self.get_credentials("SVN")
if credentials:
return (username, pwd)
client=self.get_svn_client(username, pwd)
return client
def get_db_connection(self, credentials=False):
username, pwd, url, schema=self.get_credentials("DB")
if credentials:
return (username, pwd, url, schema)
client=self.get_db_client(username, pwd, url, schema)
return client
Related
I would like to use the firebase database in my python program and restrict certain information to certain users. I have figured out how to set up the rules but would like to implement the authentication part into my program. I have imported:
from firebase import firebase
And I have a user: test.user#gmail.com pass: password123
how can would I make a post request that verifies that this user can indeed post?
You are able to create custom Security Rules and verify users permissions with Custom Claims.
Check this tutorial for more information
My solution was to first create a firestore "collection" keyed by user_id containing the claims. Then you can decorate your restricted methods to require a valid idToken plus arbitrary conditions on the claims:
import firebase_admin
import firebase_fave
# wrapper function to require credentials and claims!
def require_creds(creds_reqs={}):
def real_require_creds(protected_function):
#wraps(protected_function)
def protector(*args, **kwargs):
token = request.args.get('idToken', '')
try:
auth_resp = firebase_admin.auth.verify_id_token(token, check_revoked=True)
claims = firebase_admin.firestore.client().collection('user_claims')\
.document(auth_resp['user_id']).get().to_dict()
except:
abort(401)
if 'exp' in auth_resp\
and auth_resp['exp'] > time.time()\
and min(*[bool(creds_reqs[k](v)) for k, v in claims.items()]):
return protected_function(*args, **kwargs)
else:
abort(401)
return protector
return real_require_creds
Usage:
#require_creds(
{'access_flag': lambda x: x & 8, 'release_lag', lambda x: time.time() > RELEASE_TIME + x}
)
def your_post_method(self, ...
See also my monkey patch on pypi that adds a verify_user method to firebase_admin:
https://pypi.org/project/firebase_fave/
I'm trying out Channels in Django 1.10 and set up a few consumers.
I tried creating a login_required decorator for it that closes the connection before executing it to prevent guests from entering this private socket. Also integrated unit tests afterwards to test it and they keep failing because it keeps letting guests in (AnonymousUser errors everywhere).
Also, sometimes when logging in and logging out the session doesn't clear and it lets the old user in.
The decorator:
def login_required_websocket(func):
"""
If user is not logged in, close connection immediately.
"""
#functools.wraps(func)
def inner(message, *args, **kwargs):
if not message.user.is_authenticated():
message.reply_channel.send({'close': True})
return func(message, *args, **kwargs)
return inner
Here's the consumer code:
def ws_connect(message, slug):
message.reply_channel.send({ 'accept': True })
client = message.reply_channel
client.send(signal.message("Welcome"))
try:
# import pdb; pdb.set_trace()
Room.objects.get(name=slug)
except Room.DoesNotExist:
room = Room.objects.create(name=slug)
room.users.add(message.user)
room.turn = message.user.id
room.save()
story = Story(room=room)
story.save()
# We made sure it exists.
room = Room.objects.get(name=slug)
message.channel_session['room'] = room.name
# Check if user is allowed here.
if not room.user_allowed(message.user):
# Close the connection. User is not allowed.
client.send(Signal.error("User isn't allowed in this room."))
client.send({'close': True})
The strange thing is, when commenting out all the logic between client.send(signal.message)) forwards, it works just fine and unit tests pass (meaning guests are blocked and auth code does not run [hence AnonymousUser errors]). Any ideas?
Here's the tests too:
class RoomsTests(ChannelTestCase):
def test_reject_guest(self):
"""
This tests whether the login_required_websocket decorator is rejecting guests.
"""
client = HttpClient()
user = User.objects.create_user(
username='test', password='password')
client.send_and_consume('websocket.connect',
path='/rooms/test_room', check_accept=False)
self.assertEqual(client.receive(), {'close': True})
def test_accept_logged_in(self):
"""
This tests whether the connection is accepted when a user is logged in.
"""
client = HttpClient()
user = User.objects.create_user(
username='test', password='password')
client.login(username='test', password='password')
client.send_and_consume('websocket.connect', path='/rooms/test_room')
Am I approaching this wrong, and if I am, how do I do this (require auth) properly?
EDIT: Integrated an actions system to try something out, looks like Django channels is simply not picking up any sessions from HTTP at all.
#enforce_ordering
#channel_session_user_from_http
def ws_connect(message, slug):
message.reply_channel.send({'accept': True})
message.reply_channel.send(Action.info(message.user.is_authenticated()).to_send())
Just returns false.
EDIT2: I see it works now, I tried changing localhost to 127.0.0.1 and turns out it works now. Is there a way to make it detect localhost as a valid domain so it ports over the sessions?
EDIT3: Turns out I found the localhost vs 127.0.0.1 cookie issue haha. To not waste the bounty, how would you personally implement auth login_required in messages/channels?
edit4: While I still don't know why the thing didn't work, here's how I eventually changed my app around the issue:
I created an actions system. When entering in, the socket does nothing until you send it an AUTHENTICATE action through JSON. I separated logged in actions in guest_actions and user_actions. Once authenticated, it sets the session and you are able to use user_actions.
Django Channels already supports session authentication:
# In consumers.py
from channels import Channel, Group
from channels.sessions import channel_session
from channels.auth import channel_session_user, channel_session_user_from_http
# Connected to websocket.connect
#channel_session_user_from_http
def ws_add(message):
# Accept connection
message.reply_channel.send({"accept": True})
# Add them to the right group
Group("chat-%s" % message.user.username[0]).add(message.reply_channel)
# Connected to websocket.receive
#channel_session_user
def ws_message(message):
Group("chat-%s" % message.user.username[0]).send({
"text": message['text'],
})
# Connected to websocket.disconnect
#channel_session_user
def ws_disconnect(message):
Group("chat-%s" % message.user.username[0]).discard(message.reply_channel)
http://channels.readthedocs.io/en/stable/getting-started.html#authentication
Your function worked "as-is" for me. Before I walk through the details, there was a bug (now resolved) that was preventing sessions from being closed which may explain your other issue.
I use scarce quotes around "as-is" because I was using a class-based consumer so I had to add self to the whole stack of decorators to test it explicitly:
class MyRouter(WebsocketDemultiplexer):
# WebsocketDemultiplexer calls raw_connect for websocket.connect
#channel_session_user_from_http
#login_required_websocket
def raw_connect(self, message, **kwargs):
...
After adding some debug messages to verify the sequence of execution:
>>> ws = create_connection("ws://localhost:8085")
# server logging
channel_session_user_from_http.run
login_required_websocket.run
user: AnonymousUser
# client logging
websocket._exceptions.WebSocketBadStatusException: Handshake status 403
>>> ws = create_connection("ws://localhost:8085", cookie='sessionid=43jxki76cdjl97b8krco0ze2lsqp6pcg')
# server logging
channel_session_user_from_http.run
login_required_websocket.run
user: admin
As you can see from my snippet, you need to call #channel_session_user_from_http first. For function-based consumers, you can simplify this by including it in your decorator:
def login_required_websocket(func):
#channel_session_user_from_http
#functools.wraps(func)
def inner(message, *args, **kwargs):
...
On class-based consumers, this is handled internally (and in the right order) by setting http_user_and_session:
class MyRouter(WebsocketDemultiplexer):
http_user_and_session = True
Here's the full code for a self-respecting decorator that would be used with it:
def login_required_websocket(func):
"""
If user is not logged in, close connection immediately.
"""
#functools.wraps(func)
def inner(self, message, *args, **kwargs):
if not message.user.is_authenticated():
message.reply_channel.send({'close': True})
return func(self, message, *args, **kwargs)
return inner
My suggestion is that you can require a session key or even better take the username/password input within your consumer method. Then call the authenticate method to check if the user exists. On valid user object return, you can broadcast the message or return and invalid login details message.
from django.contrib.auth import authenticate
#channel_session_user
def ws_message(message):
user = authenticate(username=message.username, password=message.password')
if user is not None:
Group("chat-%s" % message.user.username[0]).send({
"text": message['text'],
})
else:
# User is not authenticated so return an error message.
This is my first program in Python and I'm working with some API which is used to log in or register user.
This is how my class looks like:
class MyClass:
...
def __init__(self, apikey, gameid, securitykey, responseformat,
username=None, password=None, email=None)
self.apikey = apikey
self.gameid = gameid
self.securitykey = securitykey
self.responseformat = responseformat
if username and password:
self.userlogin(username,password)
if username and password and email:
self.userregistration(username, password, email)
''' And there are function userlogin and userregistration
i'll show just one of them '''
def userregistration(self, username, password, email):
securitypayload = self.buildurl(username, password, email)
user = requests.get('url', params = securitypayload)
if 'error' in user.text:
return user.text
else:
return 'Success!'
I tested this class with code below:
api_test = MyClass('apikeyadfasdf', 'gameiddfdfd', 'seckey34324324', 'JSON',
'admin', 'pass1')
print(api_test)
and I got output like this:
<package1.package2.file_name.MyClass object at 0x7f278388b48>
But when I change my test to this:
api_test = MyClass('apikeyadfasdf', 'gameiddfdfd', 'seckey34324324', 'JSON',
'admin', 'pass1')
data = api_test.userregistration(username, password, email)
print(data)
it'll do the thing and I'll get my output, but it'll execute that function two times.
I was thinking where the problem could be and I remembered that in JAVA you could get similar output when you forget about override toString(). Now, I've read about python equivalents: repr and str but firstly I don't know how to return data from methods (not self.apikey etc.) and secondly I'm not 100% sure that is the right way to get my output, not address.
If the problem is that you get the userregistration executed two times -- you execute it two times. Once in the __init__ and once explicitly.
Regarding returning from __init__ check SO "How to return a value from __init__ in Python?" :
__init__ returns the newly created object. You cannot (or at least shouldn't) return something else.
And besides, don't use the return value to indicate error - use exceptions:
Errors and Exceptions
Idioms and Anti-Idioms - Exceptions
In this post, Nick suggested a decoartor:
Python/WebApp Google App Engine - testing for user/pass in the headers
I'm writing an API to expose potentially dozens of methods as web-services, so the decorator sounds like a great idea.
I tried to start coding one based on this sample:
http://groups.google.com/group/google-appengine/browse_thread/thread/ac51cc32196d62f8/aa6ccd47f217cb9a?lnk=gst&q=timeout#aa6ccd47f217cb9a
I need it compatible with Python 2.5 to run under Google App Engine (GAE).
Here's my attempt. Please just point the way to if I'm on the right track or not.
Currently getting an error "Invalid Syntax" on this line:
class WSTest(webapp.RequestHandler):
My idea is to pass an array of roles to the decorator. These are the only roles (from my db that should have access to each various web service).
def BasicAuthentication(roles=[]):
def _decorator(func):
def _wrapper(*args, **kwds):
logging.info("\n\n BasicAuthentication:START:__call__ \n\n")
auth = None
if 'Authorization' in self.request.headers:
auth = self.request.headers['Authorization']
if not auth:
self.response.headers['WWW-Authenticate'] = 'Basic realm="MYREALM"'
self.response.set_status(401)
self.response.out.write("Authorization required")
logging.info ("\n\n Authorization required \n\n")
return
(username, password) = base64.b64decode(auth.split(' ')[1]).split(':')
logging.info ("\n\n username = " + username + " password=" + password + "\n\n")
isValidUserPass = False
usersSimulatedRole = "Admin"
#check against database here...
if user == "test12" and password == "test34":
isValidUserPass = True
isValidRole = False
if usersSimulatedRole in roles:
isValidRole = True
#next check that user has one of the roles
# TODO
if not isValidUserPass:
self.response.set_status(403)
self.response.out.write("Forbidden: Userid/password combination failed")
logging.info("\n\n BasicAuthentication:END:__call__ \n\n")
return func(*args, **kwds)
return _wrapper
return _decorator
#BasicAuthentication(["Admin","Worker"]) #list of roles that can run this function
class WSTest(webapp.RequestHandler):
def get(self):
logging.info("\n\n\n WSTest \n\n")
...etc...
Thanks,
Neal Walters
You need to write a method decorator, not a class decorator: As lost-theory points out, class decorators don't exist in Python 2.5, and they wouldn't work very well in any case, because the RequestHandler class isn't initialized with request data until after it's constructed. A method decorator also gives you more control - eg, you could allow GET requests unauthenticated, but still require authentication for POST requests.
Other than that, your decorator looks fine - just apply it to the relevant methods. The only change I would really suggest is replacing the .set_status() calls with .error() calls and remove the response.write calls; this allows you to override .error() on the RequestHandler class to output a nice error page for each possible status code.
Class decorators were added in Python 2.6.
You'll have to manually wrap the class or think of another solution to work under 2.5. How about writing a decorator for the get method instead?
The answer to a previous question showed that Nexus implement a custom authentication helper called "NxBASIC".
How do I begin to implement a handler in python?
Update:
Implementing the handler per Alex's suggestion looks to be the right approach, but fails trying to extract the scheme and realm from the authreq.
The returned value for authreq is:
str: NxBASIC realm="Sonatype Nexus Repository Manager API""
AbstractBasicAuthHandler.rx.search(authreq) is only returning a single tuple:
tuple: ('NxBASIC', '"', 'Sonatype Nexus Repository Manager API')
so scheme,realm = mo.groups() fails. From my limited regex knowledge it looks like the standard regex from AbstractBasicAuthHandler should match scheme and realm, but it seems not to.
The regex is:
rx = re.compile('(?:.*,)*[ \t]*([^ \t]+)[ \t]+'
'realm=(["\'])(.*?)\\2', re.I)
Update 2:
From inspection of AbstractBasicAuthHandler, the default processing is to do:
scheme, quote, realm = mo.groups()
Changing to this works. I now just need to set the password against the correct realm. Thanks Alex!
If, as described, name and description are the only differences between this "NxBasic" and good old "Basic", then you could essentially copy-paste-edit some code from urllib2.py (which unfortunately doesn't expose the scheme name as easily overridable in itself), as follows (see urllib2.py's online sources):
import urllib2
class HTTPNxBasicAuthHandler(urllib2.HTTPBasicAuthHandler):
def http_error_auth_reqed(self, authreq, host, req, headers):
# host may be an authority (without userinfo) or a URL with an
# authority
# XXX could be multiple headers
authreq = headers.get(authreq, None)
if authreq:
mo = AbstractBasicAuthHandler.rx.search(authreq)
if mo:
scheme, realm = mo.groups()
if scheme.lower() == 'nxbasic':
return self.retry_http_basic_auth(host, req, realm)
def retry_http_basic_auth(self, host, req, realm):
user, pw = self.passwd.find_user_password(realm, host)
if pw is not None:
raw = "%s:%s" % (user, pw)
auth = 'NxBasic %s' % base64.b64encode(raw).strip()
if req.headers.get(self.auth_header, None) == auth:
return None
req.add_header(self.auth_header, auth)
return self.parent.open(req)
else:
return None
As you can see by inspection, I've just changed two strings from "Basic" to "NxBasic" (and the lowercase equivalents) from what's in urrlib2.py (in the abstract basic auth handler superclass of the http basic auth handler class).
Try using this version -- and if it's still not working, at least having it be your code can help you add print/logging statements, breakpoints, etc, to better understand what's breaking and how. Best of luck! (Sorry I can't help further but I don't have any Nexus around to experiment with).