You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@superset.apache.org by GitBox <gi...@apache.org> on 2022/04/04 10:19:33 UTC

[GitHub] [superset] nigzak opened a new issue, #19510: OAuth Login: user details are not synchronized, only the entitlements are synced on login

nigzak opened a new issue, #19510:
URL: https://github.com/apache/superset/issues/19510

   I have enabled the OIDC login
   I have enabled the userinfo endpoint
   I have activated the synchronized for the entitlements (AUTH_ROLES_SYNC_AT_LOGIN = True)
   
   
   #### How to reproduce the bug
   
   1. Login as Admin
   2. Navigate to some user and overwrite full name or email and SAVE
   3. let the user login with his account via OIDC
   4. the overwritten details are still persist and not updated
   
   ### Expected results
   
   Similar to the entitlements the user details should be updated from OIDC (if available)
   HINT: could be an expected result - but in this case probably an additional feature flag could be added? (like AUTH_USER_SYNC_AT_LOGIN) 
   
   ### Actual results
   
   User details (except entitlements) are not udpated
   
   
   ### Environment
   
   - browser type: CHROME
   - superset version: 1.4.1 Docker Image with AuthLib 0.x
   
   
   
   ### Checklist
   
   Make sure to follow these steps before submitting your issue - thank you!
   
   - [x] I have checked the superset logs for python stacktraces and included it here as text if there are any.
   - [x] I have reproduced the issue with at least the latest released version of superset. => with 1.4.1 (latest stable)
   - [x] I have checked the issue tracker for the same issue and I haven't found one similar.
   
   ### superset_config.py
   
   ```
   from flask_appbuilder.security.manager import AUTH_OID,AUTH_REMOTE_USER,AUTH_DB, AUTH_LDAP, AUTH_OAUTH
   import os
   
   from custom_sso_security_manager import CustomSsoSecurityManager
   CUSTOM_SECURITY_MANAGER = CustomSsoSecurityManager
   
   
   basedir = os.path.abspath(os.path.dirname(__file__))
   SUPERSET_WORKERS = 4
   
   # CRYPTION STUFF -----------------------------------------------------------------------------------
   SECRET_KEY = os.getenv('SUPERSET_SECRET_KEY')
   
   # DATABASE STUFF -----------------------------------------------------------------------------------
   PSQLHOST=os.getenv('PSQL_HOST')
   PSQLPORT=os.getenv('PSQL_PORT')
   PSQLUSER=os.getenv('PSQL_USER')
   PSQLPASS=os.getenv('PSQL_PASS')
   PSQLDB=os.getenv('PSQL_DB')
   
   PSQLSSLMODE = os.getenv('PSQL_SSL_MODE')
   PSQLSSLROOTCA = os.getenv('PSQL_SSL_ROOTCA')
   
   print (' USING DB FOR SQLALCHEMY ')
   print ('HOST: ' + str(PSQLHOST))
   print ('PORT: ' + str(PSQLPORT))
   print ('SSL_MODE: ' + PSQLSSLMODE)
   print ('SSL_CA: ' + PSQLSSLROOTCA)
   
   
   if PSQLSSLMODE != None and PSQLSSLMODE != "" and PSQLSSLROOTCA != None and PSQLSSLROOTCA != "":
   	print('PSQL: SSL MODE USED MODE=' + PSQLSSLMODE + ' CA=' + PSQLSSLROOTCA)
   	SQLALCHEMY_DATABASE_URI = 'postgresql+psycopg2://' + PSQLUSER + ':' + PSQLPASS + '@' + PSQLHOST + ':' + str(PSQLPORT) + '/' + PSQLDB + '?sslmode=' + PSQLSSLMODE + '&sslrootcert=' + PSQLSSLROOTCA  # PSQL must be parsed to STR, otherwise concatenation fails (INT vs STR)
   else:
   	print('PSQL: NO SSL MODE USED')
   	SQLALCHEMY_DATABASE_URI = 'postgresql+psycopg2://' + PSQLUSER + ':' + PSQLPASS + '@' + PSQLHOST + ':' + str(PSQLPORT) + '/' + PSQLDB 
   
   
   # AUTHENTIFICATION STUFF ---------------------------------------------------------------------------
   OIDCCID=os.getenv('OIDC_CLIENT_ID')
   OIDCCS=os.getenv('OIDC_CLIENT_SECRET')
   CSRF_ENABLED = True
   AUTH_TYPE = AUTH_OAUTH
   AUTH_USER_REGISTRATION = False
   
   
   AUTH_ROLES_MAPPING = {
       "X.Y": ["test"],
       "x.z": ["Admin"]
   }
   AUTH_ROLES_SYNC_AT_LOGIN = True
   PERMANENT_SESSION_LIFETIME = 600
   OAUTH_PROVIDERS = [
       {
           'name': 'XX',
           'icon': 'XX',
           'token_key': 'access_token',
           'remote_app': {
               'client_id': OIDCCID,
               'client_secret': OIDCCS,
               'api_base_url': 'https://sso.xx.com/as/',
               'client_kwargs':{
                 'scope': 'openid email profile authorization_group entitlement_group scoped_entitlement offline_access'
               },
               'request_token_url': None,
               'access_token_url': 'https://sso.xx.com/as/token.oauth2',
   			'refresh_token_url' : 'https://sso.xx.com/as/token.oauth2',
               'authorize_url': 'https://sso.xx.com/as/authorization.oauth2',
   			'code_challenge_method':'S256',
           }
       }
   ]
   
   ENABLE_PROXY_FIX = True
   PREFERRED_URL_SCHEME = 'https'
   ```
   
   ### custom_sso_security_manager.py
   
   ```
   from superset.security import SupersetSecurityManager
   import logging
   
   
   class CustomSsoSecurityManager(SupersetSecurityManager):
   	def oauth_user_info(self, provider, response=None):
   		access_token = response["access_token"]
   		headers = {'Authorization': 'Bearer %s' % (access_token)}
   		me = self.appbuilder.sm.oauth_remotes[provider].get("https://sso.xx.com/idp/userinfo.openid", headers=headers)
   		data = me.json()
   		logging.debug("User info from xx: %s", data)      
   		logging.debug("Entitlements: %s", data.get("entitlement_group", []))
   		return {
               "username": data.get("sub", ""),
               "first_name": data.get("given_name", ""),
               "last_name": data.get("family_name", ""),
               "email": data.get("email", ""),
               "role_keys": data.get("entitlement_group", []),
   		}
   
   ```


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


[GitHub] [superset] nigzak closed issue #19510: OAuth Login: user details are not synchronized, only the entitlements are synced on login

Posted by GitBox <gi...@apache.org>.
nigzak closed issue #19510: OAuth Login: user details are not synchronized, only the entitlements are synced on login
URL: https://github.com/apache/superset/issues/19510


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org


[GitHub] [superset] nigzak commented on issue #19510: OAuth Login: user details are not synchronized, only the entitlements are synced on login

Posted by GitBox <gi...@apache.org>.
nigzak commented on issue #19510:
URL: https://github.com/apache/superset/issues/19510#issuecomment-1195459638

   found a solution for this: overwrite in custom_sso_security_manager.py required
   
   in addition this code has a modification for add_user (only adding an user if ANY relevant entitlement is available)
   
   in this example code the user must have ANY role which contains "MY_ROLE" (refer matcher variable) to be added
   
   user is known => sync userdetails (+email + username)
   user is not known
   a) has relevant entitlement => add him
   b) has no relevant entitlement => no adding
   
   
   
   
   ```
   	def auth_user_oauth(self, userinfo):
   		logging.debug('calling auth_user_oauth')
   		try:
   			# CLONED ORIGIN FROM https://flask-appbuilder.readthedocs.io/en/latest/_modules/flask_appbuilder/security/manager.html
   
   			
   			if "username" in userinfo:
   				username = userinfo["username"]
   			elif "email" in userinfo:
   				username = userinfo["email"]
   			else:
   				log.error(
   						"OAUTH userinfo does not have username or email {0}".format(userinfo)
   				)
   				return None
   
   			# If username is empty, go away
   			if (username is None) or username == "":
   				return None
   
   			# Search the DB for this user
   			user = self.find_user(username=username)
   			
   			
   			log.debug(user)
   
   			# If user is not active, go away
   			if user and (not user.is_active):
   				return None
   
   			# If user is not registered, and not self-registration, go away
   			if (not user) and (not self.auth_user_registration):
   				return None
   
   			# Sync the user's roles
   			if user and self.auth_roles_sync_at_login:
   				user.roles = self._oauth_calculate_user_roles(userinfo)
   				log.debug(
   						"Calculated new roles for user='{0}' as: {1}".format(
   							username, user.roles
   						)
   				)
   
   
   			rolecheck = userinfo.get("role_keys")
   			matcher = "MY_ROLE"
   			
   
   			# If the user is new, register them
   			if (not user) and self.auth_user_registration:
   																	## MANUAL ADDED: add user only if relevant entitlement_group
   				if (rolecheck is None):
   					return   # no role
   				
   				addAllowed = False
   				for r in rolecheck:
   					if matcher in r:
   						addAllowed = True
   						log.debug("adding allowed because of %s", r)
   				
   				log.debug(addAllowed)
   				
   				
   				if (addAllowed):
   					user = self.add_user(
   							username=username,
   							first_name=userinfo.get("first_name", ""),
   							last_name=userinfo.get("last_name", ""),
   							email=userinfo.get("email", "") or f"{username}@email.notfound",
   							role=self._oauth_calculate_user_roles(userinfo),
   					)
   					log.debug("New user registered: {0}".format(user))
   
   					# If user registration failed, go away
   					if not user:
   							log.error("Error creating a new OAuth user {0}".format(username))
   							return None
   			else: 											## MANUAL ADDED: sync data if already registered
   				logging.debug("SYNCING USER")
   				user.first_name = userinfo.get("first_name") 	## MANUAL ADDED: sync data
   				user.last_name = userinfo.get("last_name")		## MANUAL ADDED: sync data
   				user.email = userinfo.get("email")				## MANUAL ADDED: sync data
   
   			# LOGIN SUCCESS (only if user is now registered)
   			if user:
   				self.update_user_auth_stat(user)
   				return user
   			else:
   				return None
   		except ValueError as err:
   			logging.debug("ERROR CSOM")
   			logging.debug(err)
   			logging.debug(err.args)
   			raise
   ```


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@superset.apache.org
For additional commands, e-mail: notifications-help@superset.apache.org