How to secure Superset '/login/' endpoint - rest

Recently I integrated superset with my web application so that when an user who is authenticated by my web application can enter superset and view/edit/create dashboards based on their role just by clicking the link no need to even login. For doing this I had to bypass the login for which I referred this article.
Custom SecurityManager I used to bypass login
class CustomAuthDBView(AuthDBView):
#expose('/login/', methods=['GET', 'POST'])
def login(self):
redirect_url = self.appbuilder.get_url_for_index
user_name = request.args.get('username')
user_role = request.args.get('role')
if user_name is not None:
user = self.appbuilder.sm.find_user(username=user_name)
if not user:
role = self.appbuilder.sm.find_role(user_role)
user = self.appbuilder.sm.add_user(user_name, user_name, 'last_name', user_name + "#domain.com", role, password = "password")
if user:
login_user(user, remember=False)
return redirect(redirect_url)
else:
print('Unable to auto login', 'warning')
return super(CustomAuthDBView,self).login()
class CustomSecurityManager(SupersetSecurityManager):
authdbview = CustomAuthDBView
def __init__(self, appbuilder):
super(CustomSecurityManager, self).__init__(appbuilder)
So according to above code using url http://localhost:8088/login?username=John will login the user John internally or if user John does not exist account is created with some role which is based on the role of user in my web application
Now the problem is anyone who can guess this url http://localhost:8088/login?username=USER_NAME can create their account in superset, so how to protect or secure this '/login' endpoint

You can use the API so that you dont expose request details over the URL.
from flask_appbuilder.api import BaseApi, expose
from . import appbuilder
class LoginApi(BaseApi):
resource_name = "login"
#expose('/loginapi/', methods=['GET','POST'])
##has_access
def loginapi(self):
if request.method == 'POST':
username = request.json['username']
password = request.json['password']
appbuilder.add_api(LoginApi)

Related

Flask OIDC with Keycloak returning None when trying to get 'openid_id' and 'role' fields

I'm trying to build some test applications using Flask-ODBC + Keycloak.
I successfully started Keycloak, created a Realm, a role and an user to who this role is assigned. Now I'm trying to get the 'role' and 'openid_id' fields from the OpenIDConnect object but these two fields are returning None.
My application code is the following
import json
import flask
from flask import Flask, render_template, g
import flask_login
from flask_login import login_required
from flask_oidc import OpenIDConnect
from model.user import User
app = Flask(__name__)
login_manager = flask_login.LoginManager()
login_manager.init_app(app)
# Our mock database.
users = {'foo#bar.com': {'pw': 'secret'}}
app.config.update({
'SECRET_KEY': 'u\x91\xcf\xfa\x0c\xb9\x95\xe3t\xba2K\x7f\xfd\xca\xa3\x9f\x90\x88\xb8\xee\xa4\xd6\xe4',
'TESTING': True,
'DEBUG': True,
'OIDC_CLIENT_SECRETS': 'client_secrets.json',
'OIDC_ID_TOKEN_COOKIE_SECURE': False,
'OIDC_REQUIRE_VERIFIED_EMAIL': False,
'OIDC_VALID_ISSUERS': ['http://localhost:8080/auth/realms/MyDemo'],
'OIDC_OPENID_REALM': 'http://localhost:5000/oidc_callback'
})
oidc = OpenIDConnect(app)
#app.route('/')
def hello_world():
if oidc.user_loggedin:
return ('Hello, %s, See private '
'Log out') % \
oidc.user_getfield('email')
else:
return 'Welcome anonymous, Log in'
#app.route('/private')
#oidc.require_login
def hello_me():
info = oidc.user_getinfo(['email', 'openid_id', 'role'])
print(info)
return ('Hello, %s (%s)! Return' %
(info.get('email'), info.get('openid_id')))
#app.route('/api')
#oidc.accept_token(True, ['openid'])
def hello_api():
return json.dumps({'hello': 'Welcome %s' % g.oidc_token_info['sub']})
#app.route('/logout')
def logout():
oidc.logout()
return 'Hi, you have been logged out! Return'
if __name__ == '__main__':
app.run('localhost', port=5000)
Inside hello_me() I try to get the 'openid_id' and 'role' and print it. The problem is I'm getting None in these fields. I can get the email correctly.
Can you help me with finding out what mistakes I'm making?
https://flask-oidc.readthedocs.io/en/latest/
user_getinfo(fields, access_token=None)
Returns: The values of the current user for the fields requested. The keys are the field names, values are the values of the fields as indicated by the OpenID Provider. Note that fields that were not provided by the Provider are absent.
Your provider (Keycloak) doesn't expose openid_id details in the token apparently, so field is absent. Very likely you didn't configure OIDC client in the Keycloak correctly. Make sure you have added correct mappers/scopes to used OIDC client in the Keycloak, which expose requested details into openid_id claim.

Keycloak java client 403 when retrieving role detail

I'm working with keycloak 8.0.1 and it's java client keycloak-admin-client library.
this is my Keycloak config
public Keycloak keycloakClient(AdapterConfig config) {
return KeycloakBuilder.builder()
.clientId(config.getResource())
.clientSecret((String) config.getCredentials().get(CredentialRepresentation.SECRET))
.grantType(OAuth2Constants.CLIENT_CREDENTIALS)
.realm(config.getRealm())
.serverUrl(config.getAuthServerUrl())
.build();
}
And with this code I'd like to create user and assign him a role
final UserRepresentation user = createUserRepresentation(data);
final UsersResource userResource = getRealmResource().users();
try (Response response = userResource.create(user)) {
if (response.getStatusInfo().getFamily().equals(Response.Status.Family.SUCCESSFUL)) {
final String userId = response.getLocation().getPath().replaceAll(".*/([^/]+)$", "$1");
final RolesResource rolesResource = getRealmResource().roles();
final RoleResource roleResource = rolesResource.get(data.getRole().getRemoteName());
final RoleRepresentation role = roleResource.toRepresentation();
userResource.get(userId).roles().realmLevel().add(Collections.singletonList(role));
return userId;
} else {
throw new IllegalStateException("Unable to create user " + response.getStatusInfo().getReasonPhrase());
}
}
however it fails on line final RoleRepresentation role = roleResource.toRepresentation(); with message javax.ws.rs.ForbiddenException: HTTP 403 Forbidden.
I don't understand why am I getting this error, because my client has assigned all roles from realm-management client
create-client
impersonation
manage-authorization
manage-clients
manage-events
manage-identity-providers
manage-realm
manage-users
query-clients
query-groups
query-realms
query-users
realm-admin
view-authorization
view-clients
view-events
view-identity-providers
view-realm
view-users
Is there some config which am I missing or is it a bug?
Thanks
I just have the same problem here, while I'm trying to assign roles to an existing user using a service client (using client credentials).
The solution:
Go to Clients > Select "your" client > Go to "Service Account Roles" Tab > Select Client Roles : "realm-management" and add "view-realm" into the assigned roles.
That's it :)

How to set HTTP password in gerrit while making a REST call using python-requests?

Using requests module want to query Gerrit server and for this need set authentication in below code. And this needs to be done without using any other module like pygerrit2. And if this can be done using HTTP password generated from gerrit. If this is not doable, share an example if auth has to be passed along with Get request.
import requests
import json
import os
GERRIT_SERVER = 'https://gerrit.abc.com'
class GerritServer():
def __init__(self):
self._base_url = GERRIT_SERVER
self._endpoints = {
'CHANGES': 'changes',
}
def _get_endpoint(self, token):
try:
endpoint = self._endpoints[token]
except ValueError:
raise Exception('endpoint not defined for {0}'.format(token))
return '{0}/{1}'.format(self._base_url, endpoint)
def get_endpoint_changes(self):
return self._get_endpoint('CHANGES')
Gerrit's REST API uses digest auth.
username = 'foo'
httpassword = 'bar'
api = 'baz'
auth = requests.auth.HTTPDigestAuth(username, httpassword)
r = requests.get(api, auth=auth)

SAML Authentication request to Gluu server

How can pass username password as attributes in SAML Request as shown in the code below. I'm using lastpass-saml-sdk.jar to communicate with the GLUU IDP server.
SAMLInit.initialize();
String dir = Constants.METADATA_LOCATION;
if (dir == null)
throw new SAMLException("Unable to locate SAML metadata");
IdPConfig idpConfig = new IdPConfig(new File(dir + "\\gluu-idp-metadata.xml"));
SPConfig spConfig = new SPConfig(new File(dir + "\\sp-meta.xml"));
SAMLClient client= new SAMLClient(spConfig, idpConfig);
// when a login link is clicked, create auth request and
// redirect to the IdP
String requestId = SAMLUtils.generateRequestId();
String authrequest = client.generateAuthnRequest(requestId);
String url = client.getIdPConfig().getLoginUrl() +
"?SAMLRequest=" + URLEncoder.encode(authrequest, "UTF-8");
// redirect to url...
response.sendRedirect(url);
You do not pass username and passord directly to the Identity Provider. After you have redirected the user, the user himself will enter username and password at the IDP.
Here is one of my blog posts describing the flow in SAML Web rowser profile.

Storage of DriveAPI credentials using oauth2client.django_orm.Storage-class

I want to save the client credentials obtained from Drive API. I tried the following code below, it's working well to store credentials but when I'm accessing the data in upload view it's not returning the credential
views.py
from oauth2client.django_orm import Storage
from drv_app.models import CredentialsModel
#authorization of the client by the user
def authorize_application(request):
#setting flow to get permission and code
flow = OAuth2WebServerFlow(CLIENT_ID, CLIENT_SECRET, OAUTH_SCOPE, REDIRECT_URI, ACCESS_TYPE)
authorize_url = flow.step1_get_authorize_url()
code = request.GET.get('code', '')
if code:
#setting flow step2 to exchage code for access token
credential = flow.step2_exchange(code)
#initialising httplib2 instance and building a DriveAPI service
http = httplib2.Http()
http = credential.authorize(http)
drive_service = build('drive', 'v2', http=http)
user, created = User.objects.get_or_create(username=username, email=email)
#saving credentials to database
if created == True:
storage = Storage(CredentialsModel, 'id', user, 'credential')
storage.put(credential)
return HttpResponseRedirect('/upload/')
else:
return HttpResponseRedirect('/upload/')
else:
return HttpResponseRedirect(authorize_url)
def upload_file(request):
username = request.session['username']
user = User.objects.get(username=username)
storage = Storage(CredentialsModel, 'id', user, 'credential')
credential = storage.get()
http = httplib2.Http()
http = credentials.authorize(http)
drive_service = build('drive', 'v2', http=http)
media_body = MediaFileUpload(FILENAME, mimetype='text/plain', resumable=True)
body = {
'title': 'vishnu_test',
'description': 'A test document',
'mimeType': 'text/plain'
}
file = drive_service.files().insert(body=body, media_body=media_body).execute()
pprint.pprint(file)
return HttpResponse('uploaded')
models.py
from oauth2client.django_orm import FlowField
from oauth2client.django_orm import CredentialsField
class CredentialsModel(models.Model):
id = models.ForeignKey(User, primary_key=True)
credential = CredentialsField()
What I'm doing wrong? Please suggest the necessary improvements.
Replace ForeignKey with OneToOneField in CredentialsModel