There are many SO QAs and other articles about how to create multiple constructors.
But I couldn't find how to achieve what I'm looking for,
Consider the below methods, the requirement is to create __init__ with zero args and creds args, so at initialization it will return session based on arguments.
Since that is not possible, a classmethod is introduced to initialize the instance with creds as shown below. But as you can see, it will not work.
#classmethod
def with_keys(cls, access_key, secret_key):
"""
Initialize boto3 session based on key and secret.
"""
# Initialize variables and session <= How to do this?
# self._access_key = access_key
# self._secret_key = secret_key
# self._session = boto3.Session(
# aws_access_key_id=self._access_key,
# aws_secret_access_key=self._secret_key,
# )
return cls() # <= this will overwrite the session
def __init__(self):
"""
Initialize boto3 session based on IAM Role.
"""
self._access_key = None
self._secret_key = None
self._session = boto3.Session()
of course, I can achieve this the other way as shown below. But is it possible to do it without providing args to __init__?
#classmethod
def default(cls):
"""
Initialize boto3 session based on IAM Role/Default Credentials
"""
return cls(None, None)
def __init__(self, access_key=None, secret_key=None):
"""
Initialize boto3 session based key and secret.
"""
self._access_key = access_key
self._secret_key = secret_key
if access_key == None:
self._session = boto3.Session()
else:
self._session = boto3.Session(
aws_access_key_id=self._access_key,
aws_secret_access_key=self._secret_key,
)
Related
I have a class which is intended to create an IBM Cloud Object Storage object. There are 2 functions I can use for initialization : resource() and client(). In the init function there is an object_type parameter which will be used to decide which function to call.
class ObjectStorage:
def __init__(self, object_type: str, endpoint: str, api_key: str, instance_crn: str, auth_endpoint: str):
valid_object_types = ("resource", "client")
if object_type not in valid_object_types:
raise ValueError("Object initialization error: Status must be one of %r." % valid_object_types)
method_type = getattr(ibm_boto3, object_type)()
self._conn = method_type(
"s3",
ibm_api_key_id = api_key,
ibm_service_instance_id= instance_crn,
ibm_auth_endpoint = auth_endpoint,
config=Config(signature_version="oauth"),
endpoint_url=endpoint,
)
#property
def connect(self):
return self._conn
If I run this, I receive the following error:
TypeError: client() missing 1 required positional argument: 'service_name'
If I use this in a simple function and call it by using ibm_boto3.client() or ibm_boto3.resource(), it works like a charm.
def get_cos_client_connection():
COS_ENDPOINT = "xxxxx"
COS_API_KEY_ID = "yyyyy"
COS_INSTANCE_CRN = "zzzzz"
COS_AUTH_ENDPOINT = "----"
cos = ibm_boto3.client("s3",
ibm_api_key_id=COS_API_KEY_ID,
ibm_service_instance_id=COS_INSTANCE_CRN,
ibm_auth_endpoint=COS_AUTH_ENDPOINT,
config=Config(signature_version="oauth"),
endpoint_url=COS_ENDPOINT
)
return cos
cos = get_cos_client_connection()
It looks like it calls the client function on this line, but I am not sure why:
method_type = getattr(ibm_boto3, object_type)()
I tried using:
method_type = getattr(ibm_boto3, lambda: object_type)()
but it was a silly move.
The client function looks like this btw:
def client(*args, **kwargs):
"""
Create a low-level service client by name using the default session.
See :py:meth:`ibm_boto3.session.Session.client`.
"""
return _get_default_session().client(*args, **kwargs)
which refers to:
def client(self, service_name, region_name=None, api_version=None,
use_ssl=True, verify=None, endpoint_url=None,
aws_access_key_id=None, aws_secret_access_key=None, aws_session_token=None,
ibm_api_key_id=None, ibm_service_instance_id=None, ibm_auth_endpoint=None,
auth_function=None, token_manager=None,
config=None):
return self._session.create_client(
service_name, region_name=region_name, api_version=api_version,
use_ssl=use_ssl, verify=verify, endpoint_url=endpoint_url,
aws_access_key_id=aws_access_key_id,
aws_secret_access_key=aws_secret_access_key,
aws_session_token=aws_session_token,
ibm_api_key_id=ibm_api_key_id, ibm_service_instance_id=ibm_service_instance_id,
ibm_auth_endpoint=ibm_auth_endpoint, auth_function=auth_function,
token_manager=token_manager, config=config)
Same goes for resource()
If you look at the stracktrace, it will probably point to this line:
method_type = getattr(ibm_boto3, object_type)()
And not the one after where you actually call it. The reason is simple, those last two parenthese () mean you're calling the function you just retrieved via getattr.
So simply do this:
method_type = getattr(ibm_boto3, object_type)
Which means that method_type is actually the method from the ibm_boto3 object you're interested in.
Can confirm that by either debugging using import pdb; pdb.set_trace() and inspect it, or just add a print statement:
print(method_type)
Try to follow the follow steps to integrate keycloak with Django system without using the builtin contrib.auth app.
My question is:
If I am not using the built-in User objects, how do I determine if a user is authenticated or not, by using the SSO session Keycloak generated?
Step 5, associate it with a session, what does it mean?
Thanks!
This might be a little late but could be helpful to someone in the future.
The contrib.auth is useful when you want to implement Django Authentication only.
The given approach assumes you are using the users from Keycloak and don't have a Django User Object to work with. If you are looking for later case, look at this package - https://django-keycloak.readthedocs.io/en/latest/
My usecase for integration of Keycloak with React Frontend and a REST DRF Backend. For integration with keycloak, you can make a custom middleware responsible for unpacking access_tokens generated from keycloak send in the Authorization Header on each request and validate permissions based on your client config file you need to export from keycloak from your respective client.
This is the middleware I used for my usecase
import re
import logging
from django.conf import settings
from django.http.response import JsonResponse
from django.utils.deprecation import MiddlewareMixin
from keycloak import KeycloakOpenID
from keycloak.exceptions import (
KeycloakInvalidTokenError,
)
from rest_framework.exceptions import (
PermissionDenied,
AuthenticationFailed,
NotAuthenticated,
)
logger = logging.getLogger(__name__)
class KeycloakMiddleware(MiddlewareMixin):
"""
Custom KeyCloak Middleware for Authentication and Authorization
"""
def __init__(self, get_response):
"""
:param get_response:
"""
super().__init__(get_response=get_response)
self.config = settings.KEYCLOAK_CONFIG
# Read configurations
try:
self.server_url = self.config["KEYCLOAK_SERVER_URL"]
self.client_id = self.config["KEYCLOAK_CLIENT_ID"]
self.realm = self.config["KEYCLOAK_REALM"]
except KeyError as keycloak_snippet_no_exist:
raise Exception(
"KEYCLOAK_SERVER_URL, KEYCLOAK_CLIENT_ID or KEYCLOAK_REALM not found."
) from keycloak_snippet_no_exist
self.client_secret_key = self.config.get("KEYCLOAK_CLIENT_SECRET_KEY", None)
self.client_public_key = self.config.get("KEYCLOAK_CLIENT_PUBLIC_KEY", None)
self.default_access = self.config.get("KEYCLOAK_DEFAULT_ACCESS", "DENY")
self.method_validate_token = self.config.get("KEYCLOAK_METHOD_VALIDATE_TOKEN", "INTROSPECT")
self.keycloak_authorization_config = self.config.get("KEYCLOAK_AUTHORIZATION_CONFIG", None)
# Create Keycloak instance
self.keycloak = KeycloakOpenID(
server_url=self.server_url,
client_id=self.client_id,
realm_name=self.realm,
client_secret_key=self.client_secret_key,
)
# Read policies
if self.keycloak_authorization_config:
self.keycloak.load_authorization_config(self.keycloak_authorization_config)
# Django
self.get_response = get_response
#property
def keycloak(self):
"""
Getter KeyCloak Instance
"""
return self._keycloak
#keycloak.setter
def keycloak(self, value):
self._keycloak = value
#property
def config(self):
"""
Getter Config Instance
"""
return self._config
#config.setter
def config(self, value):
self._config = value
#property
def server_url(self):
"""
Getter Server URL
"""
return self._server_url
#server_url.setter
def server_url(self, value):
self._server_url = value
#property
def client_id(self):
"""
Getter Client_ID of KeyCloak Client
"""
return self._client_id
#client_id.setter
def client_id(self, value):
self._client_id = value
#property
def client_secret_key(self):
"""
Getter Client Secret Key
"""
return self._client_secret_key
#client_secret_key.setter
def client_secret_key(self, value):
self._client_secret_key = value
#property
def client_public_key(self):
"""
Getter Client Public Key
"""
return self._client_public_key
#client_public_key.setter
def client_public_key(self, value):
self._client_public_key = value
#property
def realm(self):
"""
Getter KeyClaok Realm
"""
return self._realm
#realm.setter
def realm(self, value):
self._realm = value
#property
def keycloak_authorization_config(self):
"""
Getter KeyCloak Authorization Config
"""
return self._keycloak_authorization_config
#keycloak_authorization_config.setter
def keycloak_authorization_config(self, value):
self._keycloak_authorization_config = value
#property
def method_validate_token(self):
"""
Getter Validate Token Private Method
"""
return self._method_validate_token
#method_validate_token.setter
def method_validate_token(self, value):
self._method_validate_token = value
def __call__(self, request):
"""
:param request:
:return:
"""
return self.get_response(request)
def process_view(self, request, view_func, view_args, view_kwargs):
# pylint: disable=unused-argument
"""
Validate only the token introspect.
:param request: django request
:param view_func:
:param view_args: view args
:param view_kwargs: view kwargs
:return:
"""
if hasattr(settings, "KEYCLOAK_BEARER_AUTHENTICATION_EXEMPT_PATHS"):
path = request.path_info.lstrip("/")
if any(re.match(m, path) for m in settings.KEYCLOAK_BEARER_AUTHENTICATION_EXEMPT_PATHS):
logger.debug("** exclude path found, skipping")
return None
try:
view_scopes = view_func.cls.keycloak_scopes
except AttributeError as _keycloak_attribute_error:
logger.debug(
"Allowing free acesss, since no authorization configuration (keycloak_scopes) \
found for this request route :%s",
request,
)
return None
if "HTTP_AUTHORIZATION" not in request.META:
return JsonResponse(
{"detail": NotAuthenticated.default_detail},
status=NotAuthenticated.status_code,
)
auth_header = request.META.get("HTTP_AUTHORIZATION").split()
token = auth_header[1] if len(auth_header) == 2 else auth_header[0]
# Get default if method is not defined.
required_scope = (
view_scopes.get(request.method, None)
if view_scopes.get(request.method, None)
else view_scopes.get("DEFAULT", None)
)
# DEFAULT scope not found and DEFAULT_ACCESS is DENY
if not required_scope and self.default_access == "DENY":
return JsonResponse(
{"detail": PermissionDenied.default_detail},
status=PermissionDenied.status_code,
)
try:
# >>>>>>> Added Options kwargs to verify decode permissions to django middleware
options = {
"verify_signature": True,
"verify_aud": False,
"verify_exp": True,
}
user_permissions = self.keycloak.get_permissions(
token,
method_token_info=self.method_validate_token.lower(),
key=self.client_public_key,
options=options,
)
except KeycloakInvalidTokenError as _keycloak_invalid_token_error:
return JsonResponse(
{"detail": AuthenticationFailed.default_detail},
status=AuthenticationFailed.status_code,
)
for perm in user_permissions:
if required_scope in perm.scopes:
return None
# User Permission Denied
return JsonResponse(
{"detail": PermissionDenied.default_detail},
status=PermissionDenied.status_code,
)
With this approach, you can define policy authorization straight inside your view class similar to this.
class GetMockData(APIView):
"""
Another GET API to Fetch fake Data
"""
keycloak_scopes = {"GET": "medicine:view"}
def get(self, request: HttpRequest) -> HttpResponse:
"""
V1 API to get some medicine data from the database
"""
data = get_all_medicines()
if not data:
res = CustomResponse(
success=False,
payload=None,
error=E_RANGE_MESSAGE,
status=status.HTTP_404_NOT_FOUND,
)
return res.send_response()
logger.warning("Testing a fake warning >>>>>>>> WARNING <<<<<<<<?")
data = MedicineSerializer(data, many=True).data
res = CustomResponse(success=True, payload=data)
return res.send_response()
I have a fixture that returns the endpoint for the name of that endpoint (passed in)
The name is a string set in the test. I have messed up by calling the endpoint each time in the tests (parameterised) and now I can't figure out how to get the same functionality working without calling the endpoint each time.
Basically I just need to call the endpoint once and then pass that data between all my tests in that file (Ideally without anything like creating a class and calling it in the test. I have about 12 files each with similar tests and I want to reduce the boiler plate. Ideally if it could be done at the fixture/parametrisation level with no globals.
Here's what I have so far:
#pytest.mark.parametrize('field', [('beskrivelse'), ('systemId')])
def test_intgra_001_elevforhold_req_fields(return_endpoint, field):
ep_to_get = 'get_elevforhold'
ep_returned = return_endpoint(ep_to_get)
apiv2 = Apiv2()
apiv2.entity_check(ep_returned, field, ep_to_get, False)
#pytest.fixture()
def return_endpoint():
def endpoint_initialisation(ep_name):
apiv2 = Apiv2()
ep_data = apiv2.get_ep_name(ep_name)
response = apiv2.get_endpoint_local(ep_data, 200)
content = json.loads(response.content)
apiv2.content_filt(content)
apiv2_data = content['data']
return apiv2_data
return endpoint_initialisation
Create return_endpoint as a fixture with scope session and store data in a dictionary after it is fetched. The fixture doesn't return the initialization function, but a function to access the dictionary.
#pytest.mark.parametrize('field', [('beskrivelse'), ('systemId')])
def test_intgra_001_elevforhold_req_fields(return_endpoint, field):
ep_to_get = 'get_elevforhold'
ep_returned = return_endpoint(ep_to_get)
apiv2 = Apiv2()
apiv2.entity_check(ep_returned, field, ep_to_get, False)
#pytest.fixture(scope='session')
def return_endpoint():
def endpoint_initialisation(ep_name):
apiv2 = Apiv2()
ep_data = apiv2.get_ep_name(ep_name)
response = apiv2.get_endpoint_local(ep_data, 200)
content = json.loads(response.content)
apiv2.content_filt(content)
apiv2_data = content['data']
return apiv2_data
ep_data = dict()
def access(ep_name):
try:
return ep_data[ep_name] # or use copy.deepcopy
except KeyError:
ep_data[ep_name] = endpoint_initialisation(ep_name)
return ep_data[ep_name] # or use copy.deepcopy
return access
There are some caveats here. If the object returned by endpoint_initialisation() is mutable, then you potentially create unwanted dependencies between your tests. You can avoid this by returning a (deep) copy of the object. You can use the copy module for that.
I want to build a python client on top of a REST API that uses authentication with a api_token. Hence all api calls require the api_token. As it is pretty ugly to add a field
'token=...'
e.g.
a = f1(5, token='token')
b = f2(6, 12, token='token')
c = f3(2, 'a', token='token')
where internally f1 and f2 delegate to the REST api
to each function call. What I would like to have is something like:
auth = authenticate('token')
a = f1(5)
b = f2(6, 12,)
c = f3(2, 'a')
What I can do is to create a class and make all functions member functions. Hence, we would have:
auth = calculator('token')
a = auth.f1(5)
b = auth.f2(6, 12,)
c = auth.f3(2, 'a')
but that would also be somewhat ugly. I am trying to get this to work with decorators, but to no avail so far.
class authenticate:
def __init__(self, token):
self.token = token
def __call__(self, func):
def functor(*args, **kwargs):
return func(*args, **kwargs, key=self.authentication)
return functor
#authenticate
def f1(a, key):
data = a
result = requests.get(1, data, key)
return result
However, this seems to be going nowhere. I am also wondering whether this might work at all as decorators are executed at import time and the token is added at runtime.
Any suggestions on how to make this work or anyone know if there is another standard pattern for this?
So after some hacking around we came up with the following:
class authenticate:
# start empty key
key = None
#classmethod
""" add the token """
def set_key(cls, token):
cls.token = token
def __init__(self, func=None):
if func is not None:
self.func = func
else:
print('no function')
def __call__(self, *arg):
"""
add authentication to function func
"""
ret = self.func(*arg, auth_key=self.key)
return ret
#authenticate
def f1(a, key):
data = a
result = requests.get(1, data, key)
return result
Then you can run code like:
authentication_key = 'token'
print('Initiate class')
authenticate().set_key(key=authentication_key)
print('Run f1(5)')
a1 = f1(5) # no token needed!
a2 = f2(6, 12) # again no token needed as it is in the decorator
print(a1)
This works more or less as I hoped and I find it cleaner than the class methods. If anyone has a better suggestion or improvements let me know.
#app.route('/path/<user>/<id>', methods=['POST'])
#cache.cached(key_prefix='/path/<user>', unless=True)
def post_kv(user, id):
cache.set(user, id)
return value
#app.route('/path/<user>', methods=['GET'])
#cache.cached(key_prefix='/path/<user>', unless=True)
def getkv(user):
cache.get(**kwargs)
I want to be able to make POST calls to the path described, store them, and GET their values from the user. The above code runs, but has bugs and doesn't perform as needed. Frankly, the flask-cache docs aren't helping. How can I properly implement cache.set and cache.get to perform as needed?
In Flask, implementing your custom cache wrapper is very simple.
from werkzeug.contrib.cache import SimpleCache, MemcachedCache
class Cache(object):
cache = SimpleCache(threshold = 1000, default_timeout = 100)
# cache = MemcachedCache(servers = ['127.0.0.1:11211'], default_timeout = 100, key_prefix = 'my_prefix_')
#classmethod
def get(cls, key = None):
return cls.cache.get(key)
#classmethod
def delete(cls, key = None):
return cls.cache.delete(key)
#classmethod
def set(cls, key = None, value = None, timeout = 0):
if timeout:
return cls.cache.set(key, value, timeout = timeout)
else:
return cls.cache.set(key, value)
#classmethod
def clear(cls):
return cls.cache.clear()
...
#app.route('/path/<user>/<id>', methods=['POST'])
def post_kv(user, id):
Cache.set(user, id)
return "Cached {0}".format(id)
#app.route('/path/<user>', methods=['GET'])
def get_kv(user):
id = Cache.get(user)
return "From cache {0}".format(id)
Also, the simple memory cache is for single process environments and the SimpleCache class exists mainly for the development server and is not 100% thread safe. In production environments you should use MemcachedCache or RedisCache.
Make sure you implement logic when item is not found in the cache.