(Session) authentication in a pyramids web application - python

I am working on a python web application based on the pyramid framework. I am trying to add session authentication to it. By that I understand that:
users can log in/out (security is desirable); user data are kept in a database
authentication is handled via the session (request.session)
First off: Is session authentication a good option or are there better ones?
Secondly: I can't really make heads or tails of the documentation and examples.
So far, I've followed http://docs.pylonsproject.org/projects/pyramid/en/1.3-branch/tutorials/wiki2/authorization.html#adding-login-and-logout-views so far that I have a login/logout form. However, my authn_policy is a http://docs.pylonsproject.org/projects/pyramid/en/latest/api/authentication.html#pyramid.authentication.SessionAuthenticationPolicy
As the session factory in pyramid is insecure (see http://docs.pylonsproject.org/projects/pyramid/en/1.3-branch/narr/sessions.html), I use *pyramid_beaker* instead.
The configuration is:
in __init__.py: session_factory = session_factory_from_settings(settings)
in the .ini file:
beaker.session.lock_dir = %(here)s/data/sessions/lock
beaker.session.type = ext:database
beaker.session.sa.url = mysql://user:pass#localhost:3306/db
beaker.session.table_name = user_session
I hope I was able to make my problem clear.

I'd say it depends on what you want to do. Session authentication works fine if you use Beaker, but I like using AuthTktAuthenticationPolicy for the additional timeout and reissue options, and the fact that your authentication doesn't disappear even if you clear the session.

Related

Salesforce API - This session is not valid for use with the REST API - Invalid Session ID

For over a year, I have connected to Salesforce using the simple_salesforce package in order to pull some data from various objects and load it to a data lake.
I have used the authentication method using username / password / security token.
client = Salesforce(
username="****************",
password="*************",
security_token="****************"
)
On the 1st of February came the enforcement of multi factor auth. Starting on that day, I consistently hit the same error over and over.
[{'message': 'This session is not valid for use with the REST API', 'errorCode': 'INVALID_SESSION_ID'}]
After some research, I tried to add a permission set with API Enabled and then API Only user. Result: still the same error, but now I am locked out of the UI.
Has anyone else encountered similar issues and could point me towards the right resources, please? Thanks!
MFA shouldn't matter for API access according to https://help.salesforce.com/s/articleView?id=000352937&type=1 (Ctrl+F "API"), it's probably something else your admin did.
Username, password+token sounds like you're use SOAP login method.
See if you can create a "connected app" in SF to use the OAuth2 login method, more natural for REST API. I wrote a bit about it in https://stackoverflow.com/a/62694002/313628. In the connected app you should be able to allow API access, even full if needed. No idea if Simple has natural place for the keys though, it's bit rubbish if you'll have to craft raw http requests yourself.
Simple's documentation also mentions using JWT to log in (and that requires connected app anyway), basically instead of username + pass you go username + certificate + the fact admin preauthorised this user... You'll be fine until certificate expires.
The text part of https://gist.github.com/booleangate/30d345ecf0617db0ea19c54c7a44d06f can help you with the connected app creation; sample code's probably not needed if you're going with Simple

How to set password reset url to a mobile deeplink using Flask Security?

Gist of the problem is, I'm not developing an SPA, I'm developing a mobile app, with a backend in Flask. FlaskSecurityToo has provided me with some great features, and I'm now trying to use their password reset functionality. Here's my gripe.
I want to have the email send a deeplink, which users on the mobile app will click and get sent to the password reset form on the app. There's no UI view for this. But FlaskSecurityToo has logic that requires the server is first hit to validate the token, then redirects them to whatever has REDIRECT_HOST set. Which works great when I set the REDIRECT_BEHAVIOR as spa
Is there a way to tell Flask "Hey, don't worry about the need to validate the token from the initially provided password reset email, let the UI/Mobile app make the call to determine that whenever they want" from the provided configuration? Thus, relaxing the constraint on the host name / details of the url for a password reset, as long as a token exists? Or is this abusing some of the principles of FlaskSecurity that I don't grasp yet?
My current plan is to let it open a mobile browser, and hopefully the redirect forces the app open? I have little experience with deeplinks, so I'm testing and probing things as I learn.
You are correct about current FS behavior - here is a suggestion (not clean but it would be interesting if it was all you need) - the POST /reset/ endpoint is stand-alone - you don't have to call GET first - the POST will ALSO verify the token is valid. So the issue becomes how to generate the link for the email that has what you want. FS currently doesn't allow to configure this (that could be an enhancement) - but in 4.0.0 you can easily replace the MailUtil class and have your implementation of send_mail look for template='reset_instructions'. Now - at this point the email has already been rendered - so you would have to parse the body and rewrite the url (keeping the token intact). Ugly but doable - is this sufficient? If so I can see a few simple improvements in FS to allow more flexibility around emails.

How to retrieve a Facebook O-Auth session from Python Flask

So I'm going through the auth-o-matic flask tutorial. I was able to get it to work but now I want to of course apply the session to my other pages. If a user has logged in they will have a session specific to the browser. The tutorial recommended If I wanted to use flask-session to save the session in this way.
result = authomatic.login(
WerkzeugAdapter(request, response),
provider_name,
session=session,
session_saver=lambda: app.save_session(session, response)
)
When looking at examples like (runnable.com/Uhf58hcCo9RSAACs/using-sessions-in-flask-for-python) this using flask sessions I notice that the session is bound more explicitly to app.session. Which makes sense that I can then check the session in an if statement every time a user requests a route.
I imagine something like,
#app.route('/content')
def content():
if app.open_session(app):
return render_template('content.html', session=app.open_session() )
return render_template('content.html')
This makes sense to me so I'm wondering if it's coming from a misunderstanding of sessions. If anyone can explain this small example for me of explain why it doesn't work I would really appreciate it! I've posted the full code on a GitHub gist (link below).
TLDR: I have a facebook authentication from auth-o-matic and I can't access the session variable to apply it to my other routes to make sure a user is logged in when accessing critical sections. Code examples and full code in links.
Thank You!
https://gist.github.com/DavidAwad/f881a6ec64e8e2be2b86

Multiple Authhentication Policies in Pyramid

I have received the task of adding HTTP authentication(BasicAuthAuthenticationPolicy) to a Pyramid application that already have a AuthTktAuthenticationPolicy implemented...
Basically I need to create a RESTful API to authenticate users(Could I use BasicAuthAuthenticationPolicy for that?).
Is there a way to check whether a user is using the web interface, or using the api - to check which Authentication Policy to use?
I have not come across documentation that covers two different Authentication Policies in a single Pyramid application(if its even possible).
PS:
I have come across a blog series that started showing how to create a RESTful API with the pyramid framework... The Blogger reported that there was going to be 6 articles in the sersies, however I only managed to find two of those articles: Building a RESTful API with Pyramid - Setup and Building a RESTful API with Pyramid - Resource and Traversal. I am/was looking forward to his last article: Building a RESTful API with Pyramid - Authentication and ACL, but it doesn't seem like he is going to finish the series.
To recap my questions:
Could I use BasicAuthAuthenticationPolicy for building a RESTful api to authenticate users?
Is there a way to check whether a user is using the web interface, or using the API - to check which Authentication Policy to use?
Any help would be Appreciated.
So what I did was I merged Pyramids BasicAuthAuthenticationPolicy and CallbackAuthenticationPolicy and I ended up with this.
I have modified the callback method to use a redis session
To use this class(HTTPAthentication) you can do something like(this is an example how I implemented it for my usecase):
def _get_userid(details, request):
userre = re.compile("""^([a-zA-Z1-9\.]+):([a-zA-Z1-9\.:-]+)$""", re.I)
userpass = base64.b64decode(details[1])
res = userre.search(userpass)
if res:
return res.group()[0]
def authcheck(username, password, request):
user = Users.by_username(username, enabled=True)
host = request.registry.settings.get("authentication.host", "127.0.0.1")
maxattempts = request.registry.settings.get("authentication.maxattempts",5)
base = _get_userid(request.authorization, request)
if request.redis.exists("pimssess:%s" % base64.b64encode(request.remote_addr+":"+base)):
store = pickle.loads(request.redis.get("pimssess:%s" % base64.b64encode(request.remote_addr+":"+base)))
if store.get("auth.attempts").get(request.remote_addr):
if store["auth.attempts"][request.remote_addr] >= maxattempts:
raise HTTPMethodNotAllowed(body="You have been locked out for 5 minutes")
if user and user.agent and not user.is_http_auth:
raise HTTPMethodNotAllowed(body="You are not permitted http access")
if user and user.agent and user.host != host:
raise HTTPMethodNotAllowed(body="Your host is not permitted http access")
if user and user.agent and not user.validate_password(password):
time.sleep(1.5)
raise HTTPForbidden(body="Failed login, Incorrect password")
return getGroups(username)
The getGroups function retruns a list of groups that is attached to the user , i.e. ['admin', 'reporting']
I followed this example: BasicAuthAuthenticationPolicy(scroll to bottom)
And for the web interface login(CallbackAuthentication), you create a login interface, and create the view to accommodate the template(checking for password and username match, etc.)
Oh I almost forgot... In your project __init__.py, when you call the AuthPolicy, in the def main(...). I did:
authentication = AuthPolicy(secret='##^&*$!DSYUIDSA8321789DS',
hashalg='sha512', check=authcheck, debug=True)
authorization = ACLAuthorizationPolicy()
config = Configurator(settings=settings, root_factory=RootFactory,
authentication_policy=authentication,
authorization_policy=authorization)
I do hope this can help someone.
Pyramid does not make it easy to use different policies for different parts of the app (maybe something to work around with custom decorators) but for multiple policies check out pyramid_authstack. I'm using it with Session and BasicAuth policies for the same purpose as you are.
If it's not straightforward having one pyramid app with two auth policies you can have TWO separate Pyramid apps with a different policy each assembled into a single WSGI stack. Each app can import the same Python code, so, essentially, it'll be two startup files using the same views and everything.
If your apps have different URLs you can use paste.urlmap for this, and if your requirements are more complex you can even write your own router (say, requests with a certain HTTP header are routed to one app and without it to another)

Flask: share sessions between domain.com and username.domain.com

I have a flask running at domain.com
I also have another flask instance on another server running at username.domain.com
Normally user logs in through domain.com
However, for paying users they are suppose to login at username.domain.com
Using flask, how can I make sure that sessions are shared between domain.com and username.domain.com while ensuring that they will only have access to the specifically matching username.domain.com ?
I am concerned about security here.
EDIT:
Later, after reading your full question I noticed the original answer is not what you're looking for.
I've left the original at the bottom of this answer for Googlers, but the revised version is below.
Cookies are automatically sent to subdomains on a domain (in most modern browsers the domain name must contain a period (indicating a TLD) for this behavior to occur). The authentication will need to happen as a pre-processor, and your session will need to be managed from a centralised source. Let's walk through it.
To confirm, I'll proceed assuming (from what you've told me) your setup is as follows:
SERVER 1:
Flask app for domain.com
SERVER 2:
Flask app for user profiles at username.domain.com
A problem that first must be overcome is storing the sessions in a location that is accessible to both servers. Since by default sessions are stored on disk (and both servers obviously don't share the same hard drive), we'll need to do some modifications to both the existing setup and the new Flask app for user profiles.
Step one is to choose where to store your sessions, a database powered by a DBMS such as MySQL, Postgres, etc. is a common choice, but people also often choose to put them somewhere more ephemeral such as Memcachd or Redis for example.
The short version for choosing between these two starkly different systems breaks down to the following:
Database
Databases are readily available
It's likely you already have a database implemented
Developers usually have a pre-existing knowledge of their chosen database
Memory (Redis/Memchachd/etc.)
Considerably faster
Systems often offer basic self-management of data
Doesn't incur extra load on existing database
You can find some examples database sessions in flask here and here.
While Redis would be more difficult to setup depending on each users level of experience, it would be the option I recommend. You can see an example of doing this here.
The rest I think is covered in the original answer, part of which demonstrates the matching of username to database record (the larger code block).
Old solution for a single Flask app
Firstly, you'll have to setup Flask to handle subdomains, this is as easy as specifying a new variable name in your config file. For example, if your domain was example.com you would append the following to your Flask configuration.
SERVER_NAME = "example.com"
You can read more about this option here.
Something quick here to note is that this will be extremely difficult (if not impossible) to test if you're just working off of localhost. As mentioned above, browsers often won't bother to send cookies to subdomains of a domain without dots in the name (a TLD). Localhost also isn't set up to allow subdomains by default in many operating systems. There are ways to do this like defining your own DNS entries that you can look into (/etc/hosts on *UNIX, %system32%/etc/hosts on Windows).
Once you've got your config ready, you'll need to define a Blueprint for a subdomain wildard.
This is done pretty easily:
from flask import Blueprint
from flask.ext.login import current_user
# Create our Blueprint
deep_blue = Blueprint("subdomain_routes", __name__, subdomain="<username>")
# Define our route
#deep_blue.route('/')
def user_index(username):
if not current_user.is_authenticated():
# The user needs to log in
return "Please log in"
elif username != current_user.username:
# This is not the correct user.
return "Unauthorized"
# It's the right user!
return "Welcome back!"
The trick here is to make sure the __repr__ for your user object includes a username key. For eg...
class User(db.Model):
username = db.Column(db.String)
def __repr__(self):
return "<User {self.id}, username={self.username}>".format(self=self)
Something to note though is the problem that arises when a username contains special characters (a space, #, ?, etc.) that don't work in a URL. For this you'll need to either enforce restrictions on the username, or properly escape the name first and unescape it when validating it.
If you've got any questions or requests, please ask. Did this during my coffee break so it was a bit rushed.
You can do this with the builtin Flask sessions, which are cookie-based client-side sessions. To allow users to login to multiple subdomains in '.domain.com', you need only to specify
app.config['SESSION_COOKIE_DOMAIN'] = '.domain.com'
and the client's browser will have a session cookie that allows him to login to every Flask instance that is at 'domain.com'.
This only works if every instance of Flask has the same app.secret_key
For more information, also see
Same Flask login session across two applications

Categories

Resources