move API client authentication to auth.oidc
This commit is contained in:
		
							parent
							
								
									b425462f12
								
							
						
					
					
						commit
						626ff6e673
					
				
					 4 changed files with 64 additions and 51 deletions
				
			
		|  | @ -39,7 +39,8 @@ oidc_params = dict( | ||||||
|     cookie_name=getenv('OIDC_COOKIE_NAME', 'oidc_' + oidc_client_id), |     cookie_name=getenv('OIDC_COOKIE_NAME', 'oidc_' + oidc_client_id), | ||||||
|     cookie_domain=getenv('OIDC_COOKIE_DOMAIN', None), |     cookie_domain=getenv('OIDC_COOKIE_DOMAIN', None), | ||||||
|     cookie_lifetime=getenv('OIDC_COOKIE_LIFETIME', '86400'), |     cookie_lifetime=getenv('OIDC_COOKIE_LIFETIME', '86400'), | ||||||
|     cookie_crypt=getenv('OIDC_COOKIE_CRYPT', None) |     cookie_crypt=getenv('OIDC_COOKIE_CRYPT', None), | ||||||
|  |     private_key_file=getenv('OIDC_SERVICE_USER_PRIVATE_KEY_FILE', '.private-key.json'), | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| # access zitadel API | # access zitadel API | ||||||
|  |  | ||||||
|  | @ -52,10 +52,6 @@ oidc_params = dict( | ||||||
|     cookie_name=getenv('OIDC_COOKIE_NAME', 'oidc_' + oidc_client_id), |     cookie_name=getenv('OIDC_COOKIE_NAME', 'oidc_' + oidc_client_id), | ||||||
|     cookie_domain=getenv('OIDC_COOKIE_DOMAIN', None), |     cookie_domain=getenv('OIDC_COOKIE_DOMAIN', None), | ||||||
|     cookie_lifetime=getenv('OIDC_COOKIE_LIFETIME', '86400'), |     cookie_lifetime=getenv('OIDC_COOKIE_LIFETIME', '86400'), | ||||||
|     cookie_crypt=getenv('OIDC_COOKIE_CRYPT', None) |     cookie_crypt=getenv('OIDC_COOKIE_CRYPT', None), | ||||||
| ) |     private_key_file=getenv('OIDC_SERVICE_USER_PRIVATE_KEY_FILE', '.private-key.json'), | ||||||
| 
 |  | ||||||
| # access zitadel API |  | ||||||
| zitadel_params = dict( |  | ||||||
|     private_key_file=('ZITADEL_SERVICE_USER_PRIVATE_KEY_FILE', '.private-key.json') |  | ||||||
| ) | ) | ||||||
|  |  | ||||||
|  | @ -1,6 +1,7 @@ | ||||||
| # scopes.web.auth.uidc | # scopes.web.auth.uidc | ||||||
| 
 | 
 | ||||||
| from cryptography.fernet import Fernet | from cryptography.fernet import Fernet | ||||||
|  | from datetime import datetime, timedelta, timezone | ||||||
| from email.utils import formatdate | from email.utils import formatdate | ||||||
| import json | import json | ||||||
| import jwt | import jwt | ||||||
|  | @ -22,6 +23,8 @@ import config | ||||||
| logger = logging.getLogger('web.auth.oidc') | logger = logging.getLogger('web.auth.oidc') | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  | # OIDC authentication for browser users (principals) | ||||||
|  | 
 | ||||||
| @implementer(IAuthentication) | @implementer(IAuthentication) | ||||||
| class OidcAuthentication: | class OidcAuthentication: | ||||||
| 
 | 
 | ||||||
|  | @ -241,3 +244,41 @@ def loadOidcProviderData(force=False): | ||||||
| 
 | 
 | ||||||
| def loadOidcKeys(uri): | def loadOidcKeys(uri): | ||||||
|     return dict((item['kid'], item) for item in requests.get(uri).json()['keys']) |     return dict((item['kid'], item) for item in requests.get(uri).json()['keys']) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | # service user authentication | ||||||
|  | 
 | ||||||
|  | def authenticateClient(paramsName='oidc_params'): | ||||||
|  |     loadOidcProviderData() | ||||||
|  |     params = getattr(config, paramsName) | ||||||
|  |     keyData = loadPrivateKeyData(params['private_key_file']) | ||||||
|  |     userId = keyData['userId'] | ||||||
|  |     keyId = keyData['keyId'] | ||||||
|  |     key = keyData['key'] | ||||||
|  |     now = datetime.now(timezone.utc) | ||||||
|  |     token_lifetime=params.get('api_token_lifetime', 60) | ||||||
|  |     payload = dict( | ||||||
|  |             iss=userId, sub=userId, aud=config.oidc_provider, | ||||||
|  |             iat=now, exp=now + timedelta(minutes=token_lifetime), | ||||||
|  |     ) | ||||||
|  |     jwToken = jwt.encode(payload, key, algorithm="RS256",  | ||||||
|  |                          headers=dict(alg='RS256', kid=keyId)) | ||||||
|  |     data = dict( | ||||||
|  |             grant_type='urn:ietf:params:oauth:grant-type:jwt-bearer', | ||||||
|  |             scope='openid urn:zitadel:iam:org:project:id:zitadel:aud', | ||||||
|  |             assertion=jwToken, | ||||||
|  |     ) | ||||||
|  |     headers = {'Content-Type': 'application/x-www-form-urlencoded'} | ||||||
|  |     url = params['op_uris']['token_endpoint'] | ||||||
|  |     resp = requests.post(url, data=data, headers=headers) | ||||||
|  |     if resp.status_code != 200: | ||||||
|  |         #print(resp.text) | ||||||
|  |         logger.error('authenticateClient: %s', resp.text) | ||||||
|  |         return None | ||||||
|  |     tdata = resp.json() | ||||||
|  |     print(tdata) | ||||||
|  |     return tdata['access_token'] | ||||||
|  | 
 | ||||||
|  | def loadPrivateKeyData(fn='.private-key.json'): | ||||||
|  |     with open(fn) as f: | ||||||
|  |         return json.load(f) | ||||||
|  |  | ||||||
|  | @ -1,54 +1,29 @@ | ||||||
| # scopes.web.client | # scopes.web.client | ||||||
| 
 | 
 | ||||||
| """Web client functionality: access to web sites, APIs - including authentication.""" | """Web client functionality: access to web sites, APIs with authentication.""" | ||||||
| 
 | 
 | ||||||
| from datetime import datetime, timedelta, timezone |  | ||||||
| import json |  | ||||||
| import jwt |  | ||||||
| import requests | import requests | ||||||
| 
 | 
 | ||||||
| import config | import config | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| def postApi(url, token=None): | class ApiClient: | ||||||
|     if token is None: |  | ||||||
|         token = authenticateJwt() |  | ||||||
|     headers = dict(Authorization=f'Bearer {token}') |  | ||||||
|     resp = requests.post(url, headers=headers) |  | ||||||
|     data = resp.json() |  | ||||||
|     data['_auth_token'] = token |  | ||||||
|     return data |  | ||||||
| 
 | 
 | ||||||
| def authenticateJwt(paramsName='zitadel_params'): |     def __init__(self, baseUrl): | ||||||
|     params = getattr(config, paramsName) |         self.baseUrl = baseUrl | ||||||
|     keyData = loadPrivateKeyData(params['private_key_file']) |         self.authToken = None | ||||||
|     userId = keyData['userId'] | 
 | ||||||
|     keyId = keyData['keyId'] |     def authentication(self): | ||||||
|     key = keyData['key'] |         if self.authToken = None: | ||||||
|     now = datetime.now(timezone.utc) |             self.authToken = oidc.authenticateClient() | ||||||
|     token_lifetime=params.get('token_lifetime', 60) |         return dict(Authorization=f'Bearer {self.authToken}') | ||||||
|     payload = dict( | 
 | ||||||
|             iss=userId, sub=userId, aud=config.oidc_provider, |     def post(self, endpoint, data): | ||||||
|             iat=now, exp=now + timedelta(minutes=token_lifetime), |         headers = self.authentication() | ||||||
|     ) |         # self.makeUrl(endpoint) | ||||||
|     jwToken = jwt.encode(payload, key, algorithm="RS256",  |         url = '/'.join(self.bareUrl, endpoint) | ||||||
|                          headers=dict(alg='RS256', kid=keyId)) |         resp = requests.post(url, data=data, headers=headers) | ||||||
|     data = dict( |         # check: resp.status_code | ||||||
|             grant_type='urn:ietf:params:oauth:grant-type:jwt-bearer', |         data = resp.json() | ||||||
|             scope='openid urn:zitadel:iam:org:project:id:zitadel:aud', |         return data | ||||||
|             assertion=jwToken, |  | ||||||
|     ) |  | ||||||
|     print(data) |  | ||||||
|     headers = {'Content-Type': 'application/x-www-form-urlencoded'} |  | ||||||
|     url = config.oidc_provider + '/oauth/v2/token' |  | ||||||
|     print(url) |  | ||||||
|     resp = requests.post(url, data=data, headers=headers) |  | ||||||
|     if resp.status_code != 200: |  | ||||||
|         print(resp.text) |  | ||||||
|         return None |  | ||||||
|     tdata = resp.json() |  | ||||||
|     return tdata['access_token'] |  | ||||||
| 
 | 
 | ||||||
| def loadPrivateKeyData(fn='.private-key.json'): |  | ||||||
|     with open(fn) as f: |  | ||||||
|         return json.load(f) |  | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		
		Reference in a new issue