I've setup DRF-YASG but am unable to figure out how tell it that is should use different authorizations for different routs.
E.g.: My token endpoint(to POST,GET,DELETE api tokens) only accepts basic authentication
but all the other views only work with token authentication.
I know i can define which Authentication methods are available in Swagge-ui
SWAGGER_SETTINGS = {
"DEFAULT_MODEL_RENDERING": "example",
'USE_SESSION_AUTH': False,
'SECURITY_DEFINITIONS': {
'Basic': {
'type' : 'basic',
'name' : 'Basic access authentication',
'in' : 'header',
},
'Bearer': {
'type' : 'apiKey',
'name' : 'Token Bearer authentikation',
'in' : 'header',
}
}
}
but there the client, browsing the Documentation, can still decide whether he wants to authorize with basic- or token-authentication. And if he doesnt know which works for which route it will probably fail.
Does anyone know a solution?
I have tried adding different authentication_classes
class AuthTokenEndpoint(GenericAPIView):
"""
This endpoint does all your token handling.
Here you can create, get or delete your token
"""
permission_classes = [DjangoModelPermissionsIncludingView,]
authentication_classes = [BasicAuthentication]
authentication_classes = [TokenAuthentication]
but nothing changed drf-yasg still does not restrict authentication methods for these views
Good afternoon experts,
I am trying to implement SAML authentication in my web app.
I tried to use django-saml2-auth-ai and in djangosaml2 libraries but I got into a redirect loop for both libraries.
django-saml2-auth-ai (2.1.6)
urls.py:
url(r'^saml2_auth/', include('django_saml2_auth.urls')),
url(r'^accounts/login/$', django_saml2_auth.views.signin, name='login'),
url(r'^accounts/logout/$', django_saml2_auth.views.signout, name='logout'),
config:
I added django_saml2_auth to INSTALLED_APPS.
SAML2_AUTH = {
'SAML_CLIENT_SETTINGS': { # Pysaml2 Saml client settings
'entityid': 'http://localhost:7000/saml2_auth/acs/',
'metadata': {
'local': [
os.path.join(BASE_DIR, 'management_app/azure-ad-metadata.xml'),
],
},
'service': {
'sp': {
'logout_requests_signed': True,
'idp': 'https://sts.windows.net/8d469bba-ae86-4fe1-a36d-fa9d26ec8ab6/'
}
}
},
'debug': 1,
'DEFAULT_NEXT_URL': '/dashboard',
'NEW_USER_PROFILE': {
'USER_GROUPS': [], # The default group name when a new user logs in
'ACTIVE_STATUS': True, # The default active status for new users
'STAFF_STATUS': False, # The staff status for new users
'SUPERUSER_STATUS': False, # The superuser status for new users
},
'ATTRIBUTES_MAP': { # Change Email/UserName/FirstName/LastName to corresponding SAML2 userprofile attributes.
'email': 'name',
'username': 'name',
'first_name': 'givenname',
'last_name': 'surname',
},
'ASSERTION_URL': 'http://localhost:7000',
}
Azure Ad's basic SAML Configuration
Identifier (Entity ID) : http://localhost:7000/saml2_auth/acs/
Reply URL (Assertion Consumer Service URL) : http://localhost:7000/saml2_auth/acs/
djangosaml2 (1.4.0)
urls.py:
url(r'saml2/', include('djangosaml2.urls')),
config:
I added djangosaml2 to INSTALLED_APPS and djangosaml2.middleware.SamlSessionMiddleware to MIDDLEWARE.
SAML_SESSION_COOKIE_NAME = 'saml_session'
SESSION_COOKIE_SECURE = True
SAML_DJANGO_USER_MAIN_ATTRIBUTE = 'email'
SAML_ATTRIBUTE_MAPPING = {
'uid': ('username', ),
'mail': ('email', ),
'cn': ('first_name', ),
'sn': ('last_name', ),
}
AUTHENTICATION_BACKENDS = (
'django.contrib.auth.backends.ModelBackend',
'djangosaml2.backends.Saml2Backend',
)
LOGIN_URL = '/saml2/login/'
SESSION_EXPIRE_AT_BROWSER_CLOSE = True
import saml2
SAML_DEFAULT_BINDING = saml2.BINDING_HTTP_POST
SAML_LOGOUT_REQUEST_PREFERRED_BINDING = saml2.BINDING_HTTP_POST
SAML_IGNORE_LOGOUT_ERRORS = True
SAML_CREATE_UNKNOWN_USER = True
SAML_USE_NAME_ID_AS_USERNAME = True
from os import path
import saml2.saml
SAML_CONFIG = {
# full path to the xmlsec1 binary program
'xmlsec_binary': '/usr/bin/xmlsec1',
# your entity id, usually your subdomain plus the url to the metadata view
'entityid': 'http://localhost:7000/saml2/acs/',
# directory with attribute mapping
'attribute_map_dir': path.join(BASE_DIR, 'management_app/attribute-maps'),
# Permits to have attributes not configured in attribute-mappings
# otherwise...without OID will be rejected
'allow_unknown_attributes': True,
# this block states what services we provide
'service': {
# we are just a lonely SP
'sp' : {
'name': 'SP',
'name_id_format': saml2.saml.NAMEID_FORMAT_TRANSIENT,
# For Okta add signed logout requests. Enable this:
# "logout_requests_signed": True,
'endpoints': {
# url and binding to the assetion consumer service view
# do not change the binding or service name
'assertion_consumer_service': [
('http://localhost:7000/saml2/acs/', saml2.BINDING_HTTP_POST),
('http://localhost:7000/saml2/acs/', saml2.BINDING_HTTP_REDIRECT),
],
# url and binding to the single logout service view
# do not change the binding or service name
'single_logout_service': [
# Disable next two lines for HTTP_REDIRECT for IDP's that only support HTTP_POST. Ex. Okta:
('http://localhost:7000/saml2/ls/', saml2.BINDING_HTTP_REDIRECT),
('http://localhost:7000/saml2/ls/post', saml2.BINDING_HTTP_POST),
],
},
'signing_algorithm': saml2.xmldsig.SIG_RSA_SHA256,
'digest_algorithm': saml2.xmldsig.DIGEST_SHA256,
# Mandates that the identity provider MUST authenticate the
# presenter directly rather than rely on a previous security context.
'force_authn': False,
# Enable AllowCreate in NameIDPolicy.
'name_id_format_allow_create': False,
# attributes that this project need to identify a user
'required_attributes': ['email'],
# attributes that may be useful to have but not required
'optional_attributes': ['surname'],
'want_response_signed': False,
'authn_requests_signed': False,
'logout_requests_signed': True,
# Indicates that Authentication Responses to this SP must
# be signed. If set to True, the SP will not consume
# any SAML Responses that are not signed.
'want_assertions_signed': True,
'only_use_keys_in_metadata': True,
# When set to true, the SP will consume unsolicited SAML
# Responses, i.e. SAML Responses for which it has not sent
# a respective SAML Authentication Request.
'allow_unsolicited': True,
# in this section the list of IdPs we talk to are defined
# This is not mandatory! All the IdP available in the metadata will be considered instead.
'idp': {
# we do not need a WAYF service since there is
# only an IdP defined here. This IdP should be
# present in our metadata
# the keys of this dictionary are entity ids
'https://localhost/simplesaml/saml2/idp/metadata.php': {
'single_sign_on_service': {
saml2.BINDING_HTTP_REDIRECT: 'https://localhost/simplesaml/saml2/idp/SSOService.php',
},
'single_logout_service': {
saml2.BINDING_HTTP_REDIRECT: 'https://localhost/simplesaml/saml2/idp/SingleLogoutService.php',
},
},
},
},
},
# where the remote metadata is stored, local, remote or mdq server.
# One metadatastore or many ...
'metadata': {
'local': [path.join(BASE_DIR, 'management_app/azure-ad-metadata.xml')],
'remote': [{"url": "https://login.microsoftonline.com/8d469bba-ae86-4fe1-a36d-fa9d26ec8ab6/federationmetadata/2007-06/federationmetadata.xml?appid=3bf6313c-fee7-4925-8c66-b94d7dc44bb3"},],
# 'mdq': [{"url": "https://ds.testunical.it",
# "cert": "certficates/others/ds.testunical.it.cert",
# }]
},
# set to 1 to output debugging information
'debug': 1,
# Signing
'key_file': path.join(BASE_DIR, 'management_app/azure_ad_sso_saml_signing_private.key'), # private part
'cert_file': path.join(BASE_DIR, 'management_app/azure_ad_sso_saml_signing_public.cert'), # public part
# Encryption
'encryption_keypairs': [{
'key_file': path.join(BASE_DIR, 'management_app/azure_ad_sso_saml_signing_private.key'), # private part
'cert_file': path.join(BASE_DIR, 'management_app/azure_ad_sso_saml_signing_public.cert'), # public part
}],
# own metadata settings
'contact_person': [
{'given_name': 'Lorenzo',
'sur_name': 'Gil',
'company': 'Yaco Sistemas',
'email_address': 'lgs#yaco.es',
'contact_type': 'technical'},
{'given_name': 'Angel',
'sur_name': 'Fernandez',
'company': 'Yaco Sistemas',
'email_address': 'angel#yaco.es',
'contact_type': 'administrative'},
],
# you can set multilanguage information here
'organization': {
'name': [('Yaco Sistemas', 'es'), ('Yaco Systems', 'en')],
'display_name': [('Yaco', 'es'), ('Yaco', 'en')],
'url': [('http://www.yaco.es', 'es'), ('http://www.yaco.com', 'en')],
},
}
Azure Ad's basic SAML Configuration
Identifier (Entity ID) : http://localhost:7000/saml2/acs/
Reply URL (Assertion Consumer Service URL) : http://localhost:7000/saml2/acs/
I think I could authenticate against Azure AD with both libraries but when the token is retrieved to django-saml2-auth-ai or djangosaml2, it got into a redirect loop. I checked forums where similar issue occurred but unfortunately their solution didn't work for me.
Could you please give me any hint what goes wrong?
Thanks!
+++++++++++ UPDATE 1 +++++++++++++++
It seems that the issue was that I use custom permission which Azure AD doesn't send so Django rejects it and redirects it to log in. However the cookie is there so the same user will be logged in automatically which Django rejects, etc.
Any help please how I could display the Azure AD login page if the user doesn't have the permission? Thanks!
The problem was that I use custom permission but the logged in (in Azure AD) user didn't have it -> please see the UPDATE 1 section
I changed the code to raise an exception if the user doesn't have the permission
#permission_required('XXX', raise_exception=True)
So this 403 error can be handled with
handler403 = 'XXX.custom_403'
I've implemented Stripe checkout on a Django app and it's all working correctly except that it's not showing up on the Stripe Dashboard, even though it's showing in the event data on the same page. Have I formatted it incorrectly or am I overlooking something obvious?
This is how I added meta data:
checkout_session = stripe.checkout.Session.create(
payment_method_types=['card'],
line_items = line_itemz,
metadata={
"payment_type":"schedule_visit",
"visit_id":visit_id
},
mode='payment',
success_url= 'http://localhost:8000/success',
cancel_url = 'http://localhost:8000/cancel',)
Here is a screenshot of the Metadata section empty, but in the events the Metadata is there as it should be:
Again I can access the metadata every where else but would like it to show up on the dashboard so my team can more easily access that information.
The metadata field you set is for Checkout Session alone, but not on Payment Intent (which is the Dashboard page you are at). To have metadata shown at the Payment Intent, I'd suggest also setting payment_intent_data.metadata [0] in the request when creating a Checkout Session.
For example,
session = stripe.checkout.Session.create(
success_url="https://example.com/success",
cancel_url="https://example.com/cancel",
line_items=[
{
"price": "price_xxx",
"quantity": 1,
},
],
mode="payment",
metadata={
"payment_type": "schedule_visit",
"visit_id": "123"
},
payment_intent_data={
"metadata": {
"payment_type": "schedule_visit",
"visit_id": "123"
}
}
)
[0] https://stripe.com/docs/api/checkout/sessions/create#create_checkout_session-payment_intent_data-metadata
Within my CreateView and UpdateView I am using a form for the main model and multiple forms to directly create/update related models:
class MyModelCreateView(CreateView):
model = MyModel
form_class = MyModelForm
MyModelForm instantiates the required forms for the related fields I mentioned within itself.
My problem is that when I serialize the data and send it to the view, it doesn't know how to handle the data from the extra forms. When I access to request.POST this data gets discarded. I am serializing the forms like this:
let data = $('#main-form').serializeArray();
$('.other-form').each(function() {
data.push({name: 'id-' + $(this).data('id'), value: $(this).serializeArray()});
});
This sends the following array to the server (I stringified it here for a clear display):
[
{
"name": "name",
"value": "some name"
},
{
"name": "id-194",
"value": [
{
"name": "prop1",
"value": "on"
},
{
"name": "prop2",
"value": "some prop"
},
{
"name": "prop3",
"value": "other prop"
}
]
},
{
"name": "id-195",
"value": [
{
"name": "prop2",
"value": "some prop"
},
{
"name": "prop3",
"value": "other prop"
}
]
}
]
However the contents of request.POST are these:
<QueryDict: {u'name': [u'some name']}>
Notice how all other data is ignored. I can get to send it to the server the following way:
let data = $('#main-form').serializeArray();
$('.other-form').each(function() {
data.push({name: 'id-' + $(this).data('id'), value: $(this).serialize()});
});
But this produces the following:
<QueryDict: {u'id-195': [u'prop1=on&prop2=some+prop&prop3=other+prop'], u'id-194': [u'displayable=on&prop2=some+prop&prop3=other+prop']}
Which is of course not what we want because all we get is a string, not prepared data. We need a dictionary I believe to initialize the forms appropriately.
This is my ajax function:
$.ajax({
method: 'POST',
url: editURL,
data: data,
success: function (html) {
$('#mydiv').html(html);
}
});
jQuery's serializeArray method generates a data structure suitable for converting to JSON. But you haven't done that, you're trying to send it as form-encoded data.
You should probably make it simpler by actually sending JSON, and parsing that at the other end.
$.ajax({
method: 'POST',
url: editURL,
data: JSON.stringify(data),
contentType: 'application/json',
success: function (html) {
$('#mydiv').html(html);
}
});
...
class MyModelCreateView(CreateView):
model = MyModel
form_class = MyModelForm
def get_form_kwargs(self):
return {'data': json.loads(request.body)}
Note, I don't know what you are doing in your form to "instantiate the required forms for the related fields", but that's probably the wrong way to do it; you should be using a form and an inline related formset. Although, since this is Ajax posting to Django, it may be even better to switch to Django REST Framework and use serializers for this logic; those can deal easily with nested objects.
You are going to want to use Formsets. If the forms on your page are all the same form (IE you are just trying to make multiple records in the same model at once or whatever) you will probably find formset_factory quite useful.
I try to return a list of select options for countries using django-countries and django rest framework. I use JWT_AUTH for the authentication.
When I try a options request:
curl \
-H "Authentication: JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFsYmVydG9fdmVudGEiLCJ1c2VyX2lkIjoyLCJlbWFpbCI6IiIsImV4cCI6MTUwODE2Mzg4Mn0.svxqTThCahSl1Vu27sMjuJyd1PRLk28-Xgn2OKKb5-g"\
-X OPTIONS \
-v http://127.0.0.1:8000/api/v1/core/perfilViajeroUserPass/
The response is:
{
"name":"Perfil Viajero User Pass Create",
"description":"",
"renders":["application/json","text/html"],
"parses":[
"application/json",
"application/x-www-form-urlencoded",
"multipart/form-data"
]
}
But I think that it should be something like this by default:
{
"name": "To Do List",
"description": "List existing 'To Do' items, or create a new item.",
"renders": [
"application/json",
"text/html"
],
"parses": [
"application/json",
"application/x-www-form-urlencoded",
"multipart/form-data"
],
"actions": {
"POST": {
"note": {
"type": "string",
"required": false,
"read_only": false,
"label": "title",
"max_length": 100
}
}
}
}
Someone could help me? thanks.
If you want to change some of the content:
name is the view's get_view_name which is the view's name slightly reworked.
description is the view's get_view_description which reworks the view's docstring.
Otherwise if you want something more complex, you'll probably want to customize the view's metadata as explained in http://www.django-rest-framework.org/api-guide/metadata/#custom-metadata-classes
I have found the solution.
I change my view class type from APIView to generics.CreateAPIView and know it works. Thank you very much.
Adding another answer since I recently ran into the same issue and found it a bit mystifying -- when making an OPTIONS request, Django Rest Framework uses the view's Metadata class to construct a response. The default Metadata class is SimpleMetadata, as mentioned in the docs. However, SimpleMetadata only adds the actions key to the response body if the view in question defines the method get_serializer(). I'm not sure why this is the case, but see here for the relevant code.
rest_framework.generics.GenericAPIView defines a get_serializer() method, so (authenticated) OPTIONS requests made to these views will return a response body with the actions key. But rest_framework.views.APIView does not define this method, so the actions key will always be absent.
If you have to use rest_framework.views.APIView, you could work around this by defining a get_serializer() method on your APIView class. Which feels a little hacky, but I tested it and it works:
class MyView(views.APIView):
def get_serializer(self):
return MySerializer()
def post(self):
...