Skip to content

Commit bae47f0

Browse files
author
Marcos Pereira
committed
Methods: list users, get user, create user.
1 parent fcb47dd commit bae47f0

File tree

5 files changed

+518
-350
lines changed

5 files changed

+518
-350
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -102,4 +102,5 @@ ENV/
102102

103103
.idea/
104104
main.py
105+
main2.py
105106
s3air-authz-config.json

keycloak/__init__.py

+2-348
Original file line numberDiff line numberDiff line change
@@ -14,352 +14,6 @@
1414
#
1515
# You should have received a copy of the GNU Lesser General Public License
1616
# along with this program. If not, see <http://www.gnu.org/licenses/>.
17-
from .authorization import Authorization
18-
from .exceptions import raise_error_from_response, KeycloakGetError, KeycloakSecretNotFound, \
19-
KeycloakRPTNotFound, KeycloakAuthorizationConfigError, KeycloakInvalidTokenError
20-
from .urls_patterns import (
21-
URL_AUTH,
22-
URL_TOKEN,
23-
URL_USERINFO,
24-
URL_WELL_KNOWN,
25-
URL_LOGOUT,
26-
URL_CERTS,
27-
URL_ENTITLEMENT,
28-
URL_INTROSPECT
29-
)
30-
from .connection import ConnectionManager
31-
from jose import jwt
32-
import json
33-
34-
35-
class Keycloak:
36-
37-
def __init__(self, server_url, client_id, realm_name, client_secret_key=None):
38-
self._client_id = client_id
39-
self._client_secret_key = client_secret_key
40-
self._realm_name = realm_name
41-
42-
self._connection = ConnectionManager(base_url=server_url,
43-
headers={},
44-
timeout=60)
45-
46-
self._authorization = Authorization()
47-
48-
@property
49-
def client_id(self):
50-
return self._client_id
51-
52-
@client_id.setter
53-
def client_id(self, value):
54-
self._client_id = value
55-
56-
@property
57-
def client_secret_key(self):
58-
return self._client_secret_key
59-
60-
@client_secret_key.setter
61-
def client_secret_key(self, value):
62-
self._client_secret_key = value
63-
64-
@property
65-
def realm_name(self):
66-
return self._realm_name
67-
68-
@realm_name.setter
69-
def realm_name(self, value):
70-
self._realm_name = value
71-
72-
@property
73-
def connection(self):
74-
return self._connection
75-
76-
@connection.setter
77-
def connection(self, value):
78-
self._connection = value
79-
80-
@property
81-
def authorization(self):
82-
return self._authorization
83-
84-
@authorization.setter
85-
def authorization(self, value):
86-
self._authorization = value
87-
88-
def _add_secret_key(self, payload):
89-
"""
90-
Add secret key if exist.
91-
92-
:param payload:
93-
:return:
94-
"""
95-
if self.client_secret_key:
96-
payload.update({"client_secret": self.client_secret_key})
97-
98-
return payload
99-
100-
def _build_name_role(self, role):
101-
"""
102-
103-
:param role:
104-
:return:
105-
"""
106-
return self.client_id + "/" + role
107-
108-
def _token_info(self, token, method_token_info, **kwargs):
109-
"""
110-
111-
:param token:
112-
:param method_token_info:
113-
:param kwargs:
114-
:return:
115-
"""
116-
if method_token_info == 'introspect':
117-
token_info = self.introspect(token)
118-
else:
119-
token_info = self.decode_token(token, **kwargs)
120-
121-
return token_info
122-
123-
def well_know(self):
124-
""" The most important endpoint to understand is the well-known configuration
125-
endpoint. It lists endpoints and other configuration options relevant to
126-
the OpenID Connect implementation in Keycloak.
127-
128-
:return It lists endpoints and other configuration options relevant.
129-
"""
130-
131-
params_path = {"realm-name": self.realm_name}
132-
data_raw = self.connection.raw_get(URL_WELL_KNOWN.format(**params_path))
133-
134-
return raise_error_from_response(data_raw, KeycloakGetError)
135-
136-
def auth_url(self, redirect_uri):
137-
"""
138-
139-
http://openid.net/specs/openid-connect-core-1_0.html#AuthorizationEndpoint
140-
141-
:return:
142-
"""
143-
return NotImplemented
144-
145-
def token(self, username, password, grant_type=["password"]):
146-
"""
147-
The token endpoint is used to obtain tokens. Tokens can either be obtained by
148-
exchanging an authorization code or by supplying credentials directly depending on
149-
what flow is used. The token endpoint is also used to obtain new access tokens
150-
when they expire.
151-
152-
http://openid.net/specs/openid-connect-core-1_0.html#TokenEndpoint
153-
154-
:param username:
155-
:param password:
156-
:param grant_type:
157-
:return:
158-
"""
159-
params_path = {"realm-name": self.realm_name}
160-
payload = {"username": username, "password": password,
161-
"client_id": self.client_id, "grant_type": grant_type}
162-
163-
payload = self._add_secret_key(payload)
164-
data_raw = self.connection.raw_post(URL_TOKEN.format(**params_path),
165-
data=payload)
166-
return raise_error_from_response(data_raw, KeycloakGetError)
167-
168-
def userinfo(self, token):
169-
"""
170-
The userinfo endpoint returns standard claims about the authenticated user,
171-
and is protected by a bearer token.
172-
173-
http://openid.net/specs/openid-connect-core-1_0.html#UserInfo
174-
175-
:param token:
176-
:return:
177-
"""
178-
179-
self.connection.add_param_headers("Authorization", "Bearer " + token)
180-
params_path = {"realm-name": self.realm_name}
181-
182-
data_raw = self.connection.raw_get(URL_USERINFO.format(**params_path))
183-
184-
return raise_error_from_response(data_raw, KeycloakGetError)
185-
186-
def logout(self, refresh_token):
187-
"""
188-
The logout endpoint logs out the authenticated user.
189-
:param refresh_token:
190-
:return:
191-
"""
192-
params_path = {"realm-name": self.realm_name}
193-
payload = {"client_id": self.client_id, "refresh_token": refresh_token}
194-
195-
payload = self._add_secret_key(payload)
196-
data_raw = self.connection.raw_post(URL_LOGOUT.format(**params_path),
197-
data=payload)
198-
199-
return raise_error_from_response(data_raw, KeycloakGetError, expected_code=204)
200-
201-
def certs(self):
202-
"""
203-
The certificate endpoint returns the public keys enabled by the realm, encoded as a
204-
JSON Web Key (JWK). Depending on the realm settings there can be one or more keys enabled
205-
for verifying tokens.
206-
207-
https://tools.ietf.org/html/rfc7517
208-
209-
:return:
210-
"""
211-
params_path = {"realm-name": self.realm_name}
212-
data_raw = self.connection.raw_get(URL_CERTS.format(**params_path))
213-
return raise_error_from_response(data_raw, KeycloakGetError)
214-
215-
def entitlement(self, token, resource_server_id):
216-
"""
217-
Client applications can use a specific endpoint to obtain a special security token
218-
called a requesting party token (RPT). This token consists of all the entitlements
219-
(or permissions) for a user as a result of the evaluation of the permissions and authorization
220-
policies associated with the resources being requested. With an RPT, client applications can
221-
gain access to protected resources at the resource server.
222-
223-
:return:
224-
"""
225-
self.connection.add_param_headers("Authorization", "Bearer " + token)
226-
params_path = {"realm-name": self.realm_name, "resource-server-id": resource_server_id}
227-
data_raw = self.connection.raw_get(URL_ENTITLEMENT.format(**params_path))
228-
229-
return raise_error_from_response(data_raw, KeycloakGetError)
230-
231-
def introspect(self, token, rpt=None, token_type_hint=None):
232-
"""
233-
The introspection endpoint is used to retrieve the active state of a token. It is can only be
234-
invoked by confidential clients.
235-
236-
https://tools.ietf.org/html/rfc7662
237-
238-
:param token:
239-
:param rpt:
240-
:param token_type_hint:
241-
242-
:return:
243-
"""
244-
params_path = {"realm-name": self.realm_name}
245-
246-
payload = {"client_id": self.client_id, "token": token}
247-
248-
if token_type_hint == 'requesting_party_token':
249-
if rpt:
250-
payload.update({"token": rpt, "token_type_hint": token_type_hint})
251-
self.connection.add_param_headers("Authorization", "Bearer " + token)
252-
else:
253-
raise KeycloakRPTNotFound("Can't found RPT.")
254-
255-
payload = self._add_secret_key(payload)
256-
257-
data_raw = self.connection.raw_post(URL_INTROSPECT.format(**params_path),
258-
data=payload)
259-
260-
return raise_error_from_response(data_raw, KeycloakGetError)
261-
262-
def decode_token(self, token, key, algorithms=['RS256'], **kwargs):
263-
"""
264-
A JSON Web Key (JWK) is a JavaScript Object Notation (JSON) data
265-
structure that represents a cryptographic key. This specification
266-
also defines a JWK Set JSON data structure that represents a set of
267-
JWKs. Cryptographic algorithms and identifiers for use with this
268-
specification are described in the separate JSON Web Algorithms (JWA)
269-
specification and IANA registries established by that specification.
270-
271-
https://tools.ietf.org/html/rfc7517
272-
273-
:param token:
274-
:param key:
275-
:param algorithms:
276-
:return:
277-
"""
278-
279-
return jwt.decode(token, key, algorithms=algorithms,
280-
audience=self.client_id, **kwargs)
281-
282-
def load_authorization_config(self, path):
283-
"""
284-
Load Keycloak settings (authorization)
285-
286-
:param path: settings file (json)
287-
:return:
288-
"""
289-
authorization_file = open(path, 'r')
290-
authorization_json = json.loads(authorization_file.read())
291-
self.authorization.load_config(authorization_json)
292-
authorization_file.close()
293-
294-
def get_policies(self, token, method_token_info='introspect', **kwargs):
295-
"""
296-
Get policies by user token
297-
298-
:param token: user token
299-
:return: policies list
300-
"""
301-
302-
if not self.authorization.policies:
303-
raise KeycloakAuthorizationConfigError(
304-
"Keycloak settings not found. Load Authorization Keycloak settings."
305-
)
306-
307-
token_info = self._token_info(token, method_token_info, **kwargs)
308-
309-
if method_token_info == 'introspect' and not token_info['active']:
310-
raise KeycloakInvalidTokenError(
311-
"Token expired or invalid."
312-
)
313-
314-
user_resources = token_info['resource_access'].get(self.client_id)
315-
316-
if not user_resources:
317-
return None
318-
319-
policies = []
320-
321-
for policy_name, policy in self.authorization.policies.items():
322-
for role in user_resources['roles']:
323-
if self._build_name_role(role) in policy.roles:
324-
policies.append(policy)
325-
326-
return list(set(policies))
327-
328-
def get_permissions(self, token, method_token_info='introspect', **kwargs):
329-
"""
330-
Get permission by user token
331-
332-
:param token: user token
333-
:param method_token_info: Decode token method
334-
:param kwargs: parameters for decode
335-
:return: permissions list
336-
"""
337-
338-
if not self.authorization.policies:
339-
raise KeycloakAuthorizationConfigError(
340-
"Keycloak settings not found. Load Authorization Keycloak settings."
341-
)
342-
343-
token_info = self._token_info(token, method_token_info, **kwargs)
344-
345-
if method_token_info == 'introspect' and not token_info['active']:
346-
raise KeycloakInvalidTokenError(
347-
"Token expired or invalid."
348-
)
349-
350-
user_resources = token_info['resource_access'].get(self.client_id)
351-
352-
if not user_resources:
353-
return None
354-
355-
permissions = []
356-
357-
for policy_name, policy in self.authorization.policies.items():
358-
for role in user_resources['roles']:
359-
if self._build_name_role(role) in policy.roles:
360-
permissions += policy.permissions
361-
362-
return list(set(permissions))
363-
364-
36517

18+
from .keycloak_openid import *
19+
from .keycloak_admin import *

0 commit comments

Comments
 (0)