Change Plone default URL for authenticated users - plone

I need to change the default URL for authenticated users. For example, a request for root site will response:
[site-root]/wellcome01 (default url, set by plone configuration interface), for an anonymous user
[site-root]/wellcome02 (set by some resource) for an authenticated user
So what's the best solution to implement this resource?

You got several options
1.
You can add a index_html Python Script in your plone root, with the following code.
if context.portal_membership.isAnonymousUser():
return context.REQUEST.RESPONSE.redirect('welcome01')
else:
return context.REQUEST.RESPONSE.redirect('welcome02')
2.
I personally don't like not versioned code on my site, so I advice you to write a BrowserView, which handles de redirect
from Products.Five.browser import BrowserView
from plone import api
class RootRedirector(BrowserView):
def __call__(self):
if api.user.is_anonymous()
return self.request.RESPONSE.redirect('welcome01')
else:
return self.request.RESPONSE.redirect('welcome02')
You may regsiter this view only for the SiteRoot (Products.CMFPlone.interfaces.siteroot.IPloneSiteRoot)

If you're willing to just react to login events, you can adapt the following, which will redirect on the first-ever login only (and you probably want IUserLoggedInEvent instead):
configure.zcml:
<subscriber for="Products.PlonePAS.events.IUserInitialLoginInEvent"
handler=".hellonewbie.showIntroPage" />
hellonewbie.py:
# -*- coding: utf-8 -*-
from zope.app.component.hooks import getSite
USER_ROLE = 'Member'
INTRO_PAGE_ID = 'new-user-info'
def showIntroPage(event):
"""Login event handler: first login.
For proper users (i.e. Member role) that have
never logged in before, redirect them to a page
that tells them about the system.
"""
user = event.object
if user.has_role(USER_ROLE):
# yup, redirect the guy, he/she's new.
portal = getSite()
request = getattr(portal, "REQUEST", None)
if request:
infopage = portal.restrictedTraverse(INTRO_PAGE_ID, None)
if infopage:
request.response.redirect(infopage.absolute_url())

Or, just use one view with two elements, one only visible if not logged-in, the other only, if logged-in, via TAL-conditions.

Related

Django Oauth Toolkit: Change the path when accessing the Applications page

I want to know if I can modify the URL path if I try going the Application page for Oauth2 when I am not logged in. Basically, I want to reuse the Admin login for the user to login and then redirect them to the Application page.
# Current URL
http://localhost:8000/accounts/login/?next=/o/applications/
# Desired URL
# Change 'accounts' to 'admin'
http://localhost:8000/admin/login/?next=/o/applications/
Note:
I got the response I want with the following approach in my root urls.py:
urlpatterns = [
path('admin/', admin.site.urls, name="admin"),
path('accounts/', admin.site.urls, name="account_auth"),
path('o/', include('oauth2_provider.urls', namespace='oath2_provider')),
]
With this, I don't have to change the path but I feel like it's not the right way to do (2 paths/routes for 1 view).

How to skip extra 'sign in with' page in Flask App Builder / Airflow

I have started using Apache Airflow (Built upon FAB) and enabled authentication through OAuth (as shown in FAB Security). I have ONLY one OAUTH_PROVIDER (azure).
FYI, Airflow version 2.1.4
When I launch my airflow home page, it used to open the direct login page of my OAUTH_PROVIDER.
NOW, the real problem started when I upgraded my airflow to 2.2.4 AND configured the same OAUTH (Azure) provider.
When I launch my airflow home page, a page coming like this
After clicking the button "Sign In with azure", the user login page comes.
Compared to the older airflow, the latest version got an extra page.
Why is matter to me?
We are rendering airflow in a web app and this extra "sign in with" page does not look good.
Please provide some info on SKIPPING that extra interaction.
After upgrading Airflow, Flask Appbuilder version was probably also bumped and that caused change in behavior. Basically:
Flask Appbuilder < 3.4.0 made it possible to have automatic sign-in when just one oauth provider was configured
Flask Appbuilder >= 3.4.0 changed the behavior and made it impossible to achieve this. This is the PR (with justification) that removed this functionality: https://github.com/dpgaspar/Flask-AppBuilder/pull/1707
If you want previous behavior to be used in your deployment, this is what most likely would work (although I didn't test it):
Prepare custom view class, let's call it CustomAuth0AuthView - it would bring back old behavior on login method. (I used v4.1.3 as reference code and modified it slightly).
webserver_config.py:
from flask_appbuilder.security.views import AuthOAuthView
class CustomAuthOAuthView(AuthOAuthView):
#expose("/login/")
#expose("/login/<provider>")
#expose("/login/<provider>/<register>")
def login(self, provider: Optional[str] = None) -> WerkzeugResponse:
log.debug("Provider: {0}".format(provider))
if g.user is not None and g.user.is_authenticated:
log.debug("Already authenticated {0}".format(g.user))
return redirect(self.appbuilder.get_url_for_index)
if provider is None:
if len(self.appbuilder.sm.oauth_providers) > 1:
return self.render_template(
self.login_template,
providers=self.appbuilder.sm.oauth_providers,
title=self.title,
appbuilder=self.appbuilder,
)
else:
provider = self.appbuilder.sm.oauth_providers[0]["name"]
log.debug("Going to call authorize for: {0}".format(provider))
state = jwt.encode(
request.args.to_dict(flat=False),
self.appbuilder.app.config["SECRET_KEY"],
algorithm="HS256",
)
try:
if provider == "twitter":
return self.appbuilder.sm.oauth_remotes[provider].authorize_redirect(
redirect_uri=url_for(
".oauth_authorized",
provider=provider,
_external=True,
state=state,
)
)
else:
return self.appbuilder.sm.oauth_remotes[provider].authorize_redirect(
redirect_uri=url_for(
".oauth_authorized", provider=provider, _external=True
),
state=state.decode("ascii") if isinstance(state, bytes) else state,
)
except Exception as e:
log.error("Error on OAuth authorize: {0}".format(e))
flash(as_unicode(self.invalid_login_message), "warning")
return redirect(self.appbuilder.get_url_for_index)
Prepare custom security manager. Let's call it CustomSecurityManager. It would use your custom view to handle login.
webserver_config.py:
from airflow.www.security import AirflowSecurityManager
class CustomSecurityManager(AirflowSecurityManager):
authoidview = CustomAuthOAuthView
Configure Airflow to use your CustomSecurityManager
webserver_config.py:
SECURITY_MANAGER_CLASS = CustomSecurityManager
More on this:
https://flask-appbuilder.readthedocs.io/en/latest/security.html#your-custom-security
https://developer.squareup.com/blog/secure-apache-airflow-using-customer-security-manager/

How to make an authenticated PUT request to an App Engine service from an other origin

I have an App Engine service with a few methods implemented, where I restrict all routes with the login: admin option in the app.yaml.
Making a POST request to my service works:
fetch('http://localhost:8081/api/foo', {
credentials: 'include'});
But making a PUT request fails
await fetch('http://localhost:8081/api/foo', {
credentials: 'include',
method: 'PUT',
body: 'hi there'});
with the following error:
Response to preflight request doesn't pass access control check:
Redirect is not allowed for a preflight request.
I understand this is because my request is somehow not authenticated, and the server redirects my request to the login page. What I don't understand is how to authenticate it.
I'm using webapp2 to process the requests, and setting the following headers:
self.response.headers['Access-Control-Allow-Credentials'] = 'true'
self.response.headers['Content-Type'] = 'application/json'
# This feels wrong, but I still don't clearly understand what this header's purpose is...
self.response.headers['Access-Control-Allow-Origin'] = self.request.headers['Origin']
I think the deeper problem is that I don't undestand how this login feature works (is it cookie based? Why does it work with GET but not PUT? ...), and I don't truly understand CORS either.
Thanks for any help!
So, after discussing with Dan Cornilescu, here is the solution I came up with (Thanks Dan!)
Instead of having my classes inherit webapp2.RequestHandler, they inherit this custom HandlerWrapper.
The big difference is that when receiving an 'OPTIONS' request (ie. preflight), there is no login required. This is what was causing my problem: I couldn't get the preflight request to be authenticated, so now it doesn't need to be.
The CORS is also handled there, with a list of allowed origins
class HandlerWrapper(webapp2.RequestHandler):
def __init__(self, request, response):
super(HandlerWrapper, self).__init__(request, response)
self.allowed_origins = [
r'http://localhost(:\d{2,})?$', # localhost on any port
r'https://\w+-dot-myproject.appspot.com' # all services in the app engine project
]
self.allowed_methods = 'GET, PUT, POST, OPTIONS'
self.content_type = 'application/json'
# login mode: either 'admin', 'user', or 'public'
self.login = 'admin'
def dispatch(self):
# set the Allow-Origin header.
if self.request.headers.has_key('origin') and match_origin(self.request.headers['Origin'], self.allowed_origins):
self.response.headers['Access-Control-Allow-Origin'] = self.request.headers['Origin']
# set other headers
self.response.headers['Access-Control-Allow-Methods'] = self.allowed_methods
self.response.headers['Content-Type'] = 'application/json'
self.response.headers['Access-Control-Allow-Credentials'] = 'true'
# Handle preflight requests: Never require a login.
if self.request.method == "OPTIONS":
# For some reason, the following line raises a '405 (Method Not Allowed)'
# error, so we just skip the dispatch and it works.
# super(HandlerWrapper, self).dispatch()
return
# Handle regular requests
user = users.get_current_user()
if self.login == 'admin' and not users.is_current_user_admin():
self.abort(403)
elif self.login == 'user' and not user:
self.abort(403)
else:
super(HandlerWrapper, self).dispatch()
def match_origin(origin, allowed_origins):
for pattern in allowed_origins:
if re.match(pattern, origin): return True
return False
The login: admin configuration is based on the Users API, available only in the 1st generation standard environment. Not a CORS problem. From the login row in the handlers element table:
When a URL handler with a login setting other than optional
matches a URL, the handler first checks whether the user has signed in
to the application using its authentication option. If not, by
default, the user is redirected to the sign-in page. You can also use
auth_fail_action to configure the app to simply reject requests
for a handler from users who are not properly authenticated, instead
of redirecting the user to the sign-in page.
To use the Users API the user must literally login before the PUT request is made. Make a GET request first, which will redirect you to the login page, execute the login, then make the PUT request.
If that's not something you can achieve then you need to use a different authentication mechanism, not the one based on login: admin.
Update:
The above is true, but rather unrelated as the Users API authentication is addressed - you did mention that some other request method to the same URL works.
The error you get is indeed CORS-related, see Response to preflight request doesn't pass access control check. But I'd suggest not focusing on the accepted answer (which is only about working around CORS), but rather on this one, which is about doing CORS correctly.

history.push in react router fires new request, nginx return 404

I read a lot of questions about 'history.push nginx react-router' but I didn't find the solution for the problem I'm facing.
The problem is, when I log in my application in my Login component I do something like this after the ajax call is executed:
cb = (res) => {
if (res.token!== null && res.token !== undefined){
localStorage.setItem("token",res.token);
localStorage.setItem("expiresIn",res.expiresIn);
localStorage.setItem("email", res.email);
**history.push('/home/0/0');**
}else{this.setState({errorTextMail:"wrong mail or wrong password!"})}}
Locally it works fine but in the console the history.push('/home/0/0') fires a request that nginx doesn't handle and return 404, but in my browser the app log me in and I don't see the error.
Instead when I build the app and put the build folder under nginx to serve static files, when I try to login it show me the 404 page. Then if I refresh the page (removing the /home/0/0 from the url) it works fine, recognize the token and log me in and I can browse other route of the app.
I was supposed that history.push would have been handled by react-router, just changing the component mapped by the appropriate <Route path="/home/:idOne/:idTwo" component={Home} /> component and not firing a new request to nginx.
My question is, there is a way to switch compoent when the cb function return my token instead of using history.push('/home/0/0')?
Note the history object is defined this way
import history from './history';
and the history.js's content is :
import createHistory from 'history/createBrowserHistory'
export default createHistory({
forceRefresh: true
})
Thanks for any suggestion.

Python requests module login using session

I have a intranet application which i'm able to login using username/password.
Im trying to access the same application using python requests passing my username/password but that does not work. Its a difficult task to get a application support.
Meanwhile, I want to see if I can login using the session the web application created which is in my other browser.
How can I just pass the session using the requests module.
You may need to send data among username and password (usually this data is generated randomly by the server and are inside <input type="hidden"> form elements) which we prevent the CSRF (cross-site request forgery) attack. You may also need to send cookies the Web server send to you.
So, to solve your problem you need to:
Create a requests session to handle cookies;
Request the login page (where the form is) and extract CSRF data;
Post CSRF data among your credentials to the form action URL.
The code will be like this:
import requests
LOGIN_URL = '...' # put URL here
# replace 'user' and 'pass' with form field names for credentials
CREDENTIALS = {'user': '...', 'pass': '...'}
session = requests.session()
# get cookies and CSRF data
response = session.get(LOGIN_URL)
csrf_data = ... # extract data from response.text in a dict
# post data
csrf_data.update(CREDENTIALS)
# you may change LOGIN_URL below with form's 'action' URL if they differ
response = session.post(LOGIN_URL, data=csrf_data)
# now check response.text to see if you're logged in
There are many ways to extract CSRF data, such as: simple string manipulation, regular expressions or more specialized libraries such as lxml and BeautifulSoup (it depends on your input HTML and familiarity with the methods).

Resources