How to access OAuth2 client_id in django rest framework? - django-rest-framework

I have my django rest framework API protected by Oauth2 toolkit, but I don't know how to get the client_id of the current authorized requests.
class RequestTransactionView(APIView):
def post(self, request, format=None):
transaction = self.parse_dictionary(request.DATA)
return Response(str(transaction.goid))
I have inspected the request object, which gave:
['DATA', 'FILES', 'QUERY_PARAMS', '_CONTENTTYPE_PARAM', '_CONTENT_PARAM', '_METHOD_PARAM', '__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattr__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_auth', '_authenticate', '_authenticator', '_content_type', '_data', '_default_negotiator', '_files', '_load_data_and_files', '_load_method_and_content_type', '_load_stream', '_method', '_not_authenticated', '_parse', '_perform_form_overloading', '_request', '_stream', '_user', 'accepted_media_type', 'accepted_renderer', 'auth', 'authenticators', 'content_type', 'method', 'negotiator', 'parser_context', 'parsers', 'stream', 'successful_authenticator', 'user']
then I inspected the successful_authenticator I got:
['__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'authenticate', 'authenticate_header', 'www_authenticate_realm']
I also inspected other obvious hints but no luck.

It's easier than I thought, when I was printing out request.auth I got a string, and I thought it's a string type, but then I figured out it's a AccessToken, so from there I can get the application directly.
print request.auth.application

Related

Every error in the book from google-api-ruby-client but no data

I have been attempting to work on a request from my boss this week that requires using the google admin directory api.
At this point I am questioning if what I am trying to do is even possible.
Can I retrieve data from the scope "https://www.googleapis.com/auth/admin.directory.device.mobile.readonly" with a service account? Is it even possible?
The errors I have seen in the past hour are below...
Many of them sound the same and I have no idea what is going on or why this is such a difficult journey for such basic information.
PERMISSION_DENIED: Request had insufficient authentication scopes. (Google::Apis::ClientError)
`check_status': Unauthorized (Google::Apis::AuthorizationError)
Authorization failed. Server message: (Signet::AuthorizationError)
{
"error": "unauthorized_client",
"error_description": "Client is unauthorized to retrieve access tokens using this method, or client not authorized for any of the scopes requested."
}
`check_status': permission_denied: request had insufficient authentication scopes
`check_status': badRequest: Bad Request
My current test script is below...
require "google/apis/admin_directory_v1"
require "googleauth"
require "googleauth/stores/file_token_store"
require "fileutils"
APPLICATION_NAME = "Directory API Ruby Quickstart".freeze
CREDENTIALS_PATH = "credentials.json".freeze
CUSTOMER_ID = "thasgunnabeanopefrommedawg".freeze
SCOPE = ["https://www.googleapis.com/auth/admin.directory.device.mobile.readonly"].freeze
authorizer = Google::Auth::ServiceAccountCredentials.make_creds(
json_key_io:
File.open('credentials.json'),
scope: SCOPE)
authorizer.update!(sub: "fullbl00m#citadelny.com")
authorizer.fetch_access_token!
# puts authorize
# Initialize the API
service = Google::Apis::AdminDirectoryV1::DirectoryService.new
service.client_options.application_name = APPLICATION_NAME
service.authorization = Google::Auth.get_application_default(SCOPE)
response = service.list_mobile_devices(customer_id: CUSTOMER_ID)
puts response.to_json
EDITS BELOW *** [27th, MAY, 2022]
I have been trying with ruby, python, and postman for two weeks at this point :/
Last night I took the ruby snippet that was posted by user:Daimto below.
I was able to return a token with the following modified version of the ruby snippet provided in the answer below.
require 'googleauth'
require 'google/apis/admin_directory_v1'
creds = {
"type": "service_account",
"project_id": "MYPROJECTNAME",
"private_key_id": "MYPRIVATEKEYID",
"private_key": "-----BEGIN PRIVATE KEY-----\n-MY PRIVATE KEY
WILL BE HERE BUT REMOVED FOR SECURITY-----END PRIVATE KEY-----\n",
"client_email": "emailfromserviceaccount-compute#developer.gserviceaccount.com",
"client_id": "MYCLIENTIDISACTUALLYHERE",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/compute%40developer.gserviceaccount.com"
}
creds_json = creds.to_json
creds_json_io = StringIO.new(creds_json)
auth = Google::Auth::ServiceAccountCredentials.make_creds(
json_key_io: creds_json_io,
scope["https://www.googleapis.com/auth/admin.directory.device.mobile.readonly","https://www.googleapis.com/auth/admin.directory.device.chromeos.readonly","https://www.googleapis.com/auth/admin.directory.device.mobile"]
)
auth.sub = "emailfrommyserviceaccount-
compute#developer.gserviceaccount.com"
puts auth.fetch_access_token
Please excuse the formatting.
I took the service account out of the env variable for now to make sure I can get it to work without adding extra layers of abstraction at this time.
When trying to add the additional code from the Directory Api Quickstart to the above snip I STILL RETURN THE ERROR
/var/lib/gems/2.7.0/gems/google-apis-core-0.5.0/lib/google/apis/core/http_command.rb:224:in `check_status': Unauthorized (Google::Apis::AuthorizationError)
The additional code added is below...
The last line of the previous snip gets changed to the first line of the snip that comes after this. This is to properly pass the token to the example after modifying user:Daimto's response.
authorize = auth.fetch_access_token
# Initialize the API
service = Google::Apis::AdminDirectoryV1::DirectoryService.new
service.client_options.application_name = "my-application-name"
service.authorization = authorize
# List the first 10 users in the domain
response = service.list_users(customer: "my_customer",
max_results: 10,
order_by: "email")
puts "Users:"
puts "No users found" if response.users.empty?
response.users.each { |user| puts "- #{user.primary_email} (#{user.name.full_name})" }
The method Method: mobiledevices.list requires one of the following scopes.
So to answer your first question yes you can use the https://www.googleapis.com/auth/admin.directory.device.mobile.readonly scope.
Error number 1
PERMISSION_DENIED: Request had insufficient authentication scopes.
You were probably getting this error when you had supplied a different scope.
Error 3;
Client is unauthorized to retrieve access tokens using this method, or client not authorized for any of the scopes requested.
There are three types of clients you can create on google cloud console.
web client
native client
service account
The json file you get from creating these clients is all different. The code that uses them is also different. The error is telling you that you have a client.json file that you are using which does not match the type of code you are using.
How to create service account credetinals
The code for a service account would be like this Not tested you may need to fix the scope. Remember that the service account needs to be configured properly on your workspace domain for the sub to work.
require 'googleauth'
require 'google/apis/admin_v1'
creds = ENV['GOOGLE_SERVICE_ACCOUNT'] # JSON downloaded from cloud console
# is saved in this ENV variable
creds_json = JSON.parse(creds)
creds_json_io = StringIO.new(creds_json.to_json)
auth = Google::Auth::ServiceAccountCredentials.make_creds(
json_key_io: creds_json_io,
scope: [Google::Apis::ADMINV1::ADMIN_DIRECTORY_MOBILE_READONLY]
)
auth.sub = 'admin#yourdomain.com'
auth.fetch_access_token
Tip: You have a lot of errors there, I feel that you have been struggling with this for a while. Advice step back, have a look at the sample on the readme for the Google-api-ruby-client. Start over. Just get your auth to work. Once you get the code right and the client right all the pieces will fit into place.

Allauth will not return the desired token payload

I have a django-rest-auth project called merchant. within it I have implemented django-restauth and allauth packages with JWT.
Everything works OK. However, I wish to return additional fields in the JWT token and here's my implementation of it.
In app.views.py
def jwt_response_payload_handler(token, user=None, request=None):
return {
'token': token,
'user': User_Serializer(user, context={'request':request}).data
}
serializers.py
class User_Serializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ['email', 'username', 'is_staff']
settings.py
JWT_AUTH = {
'JWT_RESPONSE_PAYLOAD_HANDLER': 'merchant.coin_app.views.jwt_response_payload_handler',
'JWT_EXPIRATION_DELTA': datetime.timedelta(minutes=10),
'JWT_AUTH_HEADER_PREFIX': 'JWT'
}
The payload returned does not contain email/is_staff. Perhaps I am missing something.
The registered handler in the JWT_RESPONSE_PAYLOAD_HANDLER setting option is invoked after the JWT token is generated.
The handler requiring customization is JWT_PAYLOAD_HANDLER which creates the payload object that is tokenized and not JWT_RESPONSE_PAYLOAD_HANDLER.
In your project settings, configure
JWT_AUTH = {
'JWT_PAYLOAD_HANDLER': 'merchant.coin_app.views.jwt_payload_handler',
'JWT_EXPIRATION_DELTA': datetime.timedelta(minutes=10),
'JWT_AUTH_HEADER_PREFIX': 'JWT'
}
Then in your view, extend the result of rest_framework_jwt.utils.jwt_payload_handler
import rest_framework_jwt.utils.jwt_payload_handler as base_jwt_payload_handler
def jwt_response_payload_handler(user):
payload = base_jwt_payload_handler(user)
payload['user'] = User_Serializer(user).data
return payload

savon-multipart - How to obtain attachments

I am using savon-multipart https://github.com/savonrb/savon-multipart to request a SOAP multipart response with an attachment (PDF). So far, this is my code:
require "savon-multipart"
client = Savon.client(
wsdl: "http://something.de?wsdl",
wsse_auth: [username: "uu", password: "??"]
)
reponse = client.call(:get_report, message: {
pdfId: 1
})
response.attachments
Authentication works fine. I can also fetch the XML-reponse. What I can't do is extract the attachment. There does not seem to exist a method for it.
According to savon-multipart's documentation
response.attachments
should contain the attachment(s). Unfortunately ruby tells me that this method is not defined.
I could't find an example implementation of savon-multipart so I'm coming to you guys :) Hope you can help me.
We had this same problem in some code. I hope this saves someone else some time in finding the solution.
When using savon-multipart, we had to add multipart: true to the parameters in call. When that parameter was added the response returned was of type Savon::Multipart::Response which has the attachments and parts methods.
reponse = client.call(:get_report, message: {
pdfId: 1
}, multipart: true)
Without that parameter, or with it set to false, the returned response is a Savon::Response object which does not have those methods.

RestClient basic auth with # in username

I have a problem with a daemon accessing a REST api.
The access requires basic authentication. The username and password are fixed and can not be changed.
The problem seems to be, that the username looks like this: #ws+R4nd0mS7r1n
I access the API like this:
resource = RestClient::Resource.new( "#{base_url}/failover/#{failover_ip}", { :user => user_name, :password => user_password})
response = resource.get
This gets me an bad URI error:
bad URI(absolute but no path): https://#ws+R4nd0mS7r1n:RaNdOmPaSsWoRd#robot-ws.your-server.de/failover/11.11.11.11
When I itentionally remove the # from the username it works, but I get a NOT Authenticated error.
Is there a way to pass a username or password containing # to restclient?
Passing the complete URI manually to a .get does not work either.
I don't get the same error. What version of rest-client do you have installed?
You may simply be able to update the version to fix your problem (I tested with version 1.6.7 of the gem)
Alternatively, this works around the URI failure by directly writing to the Authorization header (which is where this data ends up anyway):
require 'base64'
auth = 'Basic ' + Base64.encode64( "#{user_name}:#{user_password}" ).chomp
resource = RestClient::Resource.new( "#{base_url}/failover/#{failover_ip}", { :headers => { 'Authorization' => auth } } )
resource.get

Oauth2 google-api-ruby-client: How to set Approval Prompt to Auto?

Question:
How to set the Approval Prompt to Auto? It defaults to 'approval_prompt=force'
Code:
I am setting up the client like this.
#client = Google::APIClient.new(
:authorization => :oauth_2,
:host => 'www.googleapis.com',
:http_adapter => HTTPAdapter::NetHTTPAdapter.new
)
#client.authorization.client_id = 'xxxx.apps.googleusercontent.com'
#client.authorization.client_secret = 'xxxx'
Context: Google OAuth2
Client Library: google-api-ruby-client
Reference: Same question for the php client :
Google+ OAuth API store and retrieve tokens after first login and authorization
Signet Documentation. I can't find the approval_prompt setter
http://signet.rubyforge.org/api/Signet/OAuth2/Client.html
This is how I solved the problem.
I wrote a separate helper method that will generate the Google OAuth URI
def build_auth_uri
return #client.authorization.authorization_uri(
:approval_prompt => :auto
).to_s
end
Next, instead of referring to Google OAuth URI directly in my view, I called the helper.
That did the trick.
This is how I solved the problem:
In /app/views/devise/shared/_links.haml (it's similar for _links.erb):
- if devise_mapping.omniauthable?
- resource_class.omniauth_providers.each do |provider|
- if provider == :google_oauth2
= link_to "Sign in with Google", omniauth_authorize_path(resource_name, provider, approval_prompt: :auto)
- else
= link_to "Sign in with #{provider.to_s.titleize}", omniauth_authorize_path(resource_name, provider)
%br/
EDIT: Even easier: Add this to your devise.rb or omniauth.rb initializer (in /config/initializers):
provider :google_oauth2, ENV["GOOGLE_KEY"], ENV["GOOGLE_SECRET"], {
approval_prompt: "auto"
}
Check the documentation here for more info.

Resources