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
Related
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'm using python to create wordpress post taking care also of the YOAST fields, using the wordpress rest api. On YOAST website I found this statement:
The Yoast REST API is currently read-only, and doesn't currently
support POST or PUT calls to update the data.
At the same time, I'm wondering if there is some workaround to be able to update the Yoast fields by post request, something like this (that off-course is not working right know):
post = {
'title' : 'My title',
'content' : 'This is my first post created using rest API Updated',
'yoast_head_json': {'title': 'This field should be UPDATED by POST REQUEST'},
}
I found a code snippet at this link, that maybe would be a useful starting point and I report it below:
class YoastUpdateController extends WP_REST_Controller {
public function register_routes() {
register_rest_route( 'wp/v2/', '/action/', array(
'methods' => 'GET',
'callback' => [$this, 'update_yoast_meta']
));
}
function update_yoast_meta($data) {
$postID = $_GET['postID'];
$metadesc = $_GET['metaDesc'];
if ($postID && $metadesc) {
$this->add_to_yoast_seo($postID, $metadesc);
}
}
function add_to_yoast_seo($post_id, $metadesc){
$ret = false;
$updated_desc = update_post_meta($post_id, '_yoast_wpseo_metadesc', $metadesc);
if($updated_desc){
$ret = true;
}
return $ret;
}
}
function register_yoast_update_controller() {
$controller = new YoastUpdateController();
$controller->register_routes();
}
add_action( 'rest_api_init', 'register_yoast_update_controller' );
I placed the above code in function.php, I hope it is the right place.
How could I update all/some of the fields of YOAST by rest api post request? Below some fields (E.g. title, description...)
"yoast_head_json": {
"title": "Post 1 - MyWebsite",
"description": "Meta description added in backend",
"robots": {
"index": "index",
"follow": "follow",
"max-snippet": "max-snippet:-1",
"max-image-preview": "max-image-preview:large",
"max-video-preview": "max-video-preview:-1"
},
Thank you all,
According to yoast documentation: The Yoast REST API is currently read-only, and doesn't currently support POST or PUT calls to update the data.
https://developer.yoast.com/customization/apis/rest-api/#can-i-use-this-api-to-update-data
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):
...
I am using angular2 as a front end in my html pages.I have a django project that uses postgresql.
Which is the best approach to use the angular2 in the django project to connect to the django models and the database to perform basic operations(CRUD)like Read,Update etc?
Currently I need to fetch the data from the database dynamically.
(e.g.If user clicks on the product from the product list then product details should be retrieved from the database and it is shown to the user)
Any advice or reference example link will be helpful.
Create REST api end points using Django (use DRF for standard REST api's or just use vanilla django to generate json response for the requests and call it REST api).
For ex:
/product/:id is the api end point you've created to fetch the details of a particular product in Django
Then use Angular to request throught those API's and get the responses and do whatever you want with that data.
For ex:
make a get request to /product/1 to fetch the details of a product with PK = 1 when the user clicks that product.
Browse through Github for some inspiration.
Checkout django-rest-framework
DRF is a django app that makes building ReST apps a breeze.
Checkout their quick tutorial to get a sense of how to use DRF in your project.
I'm recently working on the similar project you have. My approach is just like #praba230890 mentioned above.
Here are some samples...
Django
In views.py
class HView(APIView):
def get(self, request, format=None):
request_hero_id = request.query_params.get('id', None)
if request_hero_id:
return Response(
{
'id': 1,
'name': 'test',
'position': 'mid'
},
status=status.HTTP_200_OK
)
return Response(
{ 'error': 'does not exist' },
status=status.HTTP_404_NOT_FOUND
)
class HsView(APIView):
def get(self, request, format=None):
return Response(
[
{
'id': 1,
'name': 'test',
'position': 'mid'
},
{
'id': 2,
'name': 'test',
'position': 'mid'
}
],
status=status.HTTP_200_OK
)
In urls.py
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'', include('shweb.urls')),
]
You will need to install django-cros-headers if you run into CROS errors. Also, you will need to configure your settings.py
Angular2
In api-service.service.ts
import { Injectable } from '#angular/core';
import { Http, Headers } from '#angular/http';
import 'rxjs/add/operator/toPromise';
import { Hero } from '../utils/hero';
#Injectable()
export class ApiService {
/** Modify this later */
private backend_api_url: string = 'http://localhost:8000/api/';
private api_headers: Headers = new Headers(
{ 'Content-Type': 'application/json' }
);
constructor(private http: Http) { }
getHero(id: number): Promise<Hero>{
const url = `${this.backend_api_url}htest/?id=${id}`;
return this.http.get(url).toPromise().then(
response => response.json() as Hero
).catch(this.handleError);
} // END getHero
getHeroes(): Promise<Hero[]>{
const url = `${this.backend_api_url}htests`;
console.log(url);
return this.http.get(url).toPromise().then(
response => {
console.log(response.json());
return response.json() as Hero[];
}
).catch(this.handleError);
} // END getHeroes
private handleError(error: any): Promise<any>{
console.error('An error occurred', error); // TODO: update this
return Promise.reject(error.message || error);
}
}
In hero.ts
export class Hero{
id: number;
name: string;
position: string;
}
In your component file, inject the api-service into component
#Component({
selector: 'app-dashboard',
templateUrl: './dashboard.component.html',
styleUrls: ['./dashboard.component.css']
})
export class DashboardComponent implements OnInit{
title: string = 'Dashboard';
heroes: Hero[] = [];
constructor(private apiService: ApiService){}
ngOnInit(): void{
this.getHeroes();
}
getHeroes(): void{
this.apiService.getHeroes().then(heroes => this.heroes = heroes);
} // END getHeroes
}
Basically, using API to retrieve data and cast into class; then, you can use those data.
PS. I haven't touched the credentials and security part. I believe you need to have some sort of Authentication implemented for secure API communication. (In my case, only GETs are allowed. Therefore, I put the security part for later.)
Hope this would help!
I tried to disable concurrency-control on Eve and trying to add another new id_field: "new_field", but I am not able to make it work. I look through various post in StackOverflow but still I am not able to fix it. Could someone please help?
I disabled : IF_MATCH = False in the global config and:
schema = {
"node_name": {
"type": "string",
"unique": True,
"required": True,
"node_type": {
"type": "string",
"required": True,
}
}
Config:
config = {
'item_title': 'new_title',
'additional_lookup': {
'url': 'regex("[\w]+")',
'field': 'node_name',
},
'id_field': "node_name",
'schema': schema,
}
And here is the url i am trying to send PATCH request:
url: http:localhost:5000/api/v1/resource/end_point/
Here
resource: my resource name
end_point: id_field value.
Could someone please help.
Have you enabled PATCH for your resource? By default, document and collection endpoints are read-only. Try adding the following to your configuration:
ITEM_METHODS = ['GET', 'PATCH', 'DELETE', 'PUT']
Also, you don't want to set an additional_lookup on the same field serving as id_field, as additional lookups are read-only.
Since you are not using a ObjectID as your unique key, you also probably need to change the default URL for the document endpoint. Try setting ITEM_URL to the correct regex:
ITEM_URL: URL rule used to construct default item endpoint URLs. Can be overridden by resource settings. Defaults regex("[a-f0-9]{24}") which is MongoDB standard Object_Id format.