how to access lookup dictionary from check_auth - eve

I have users resource in my app and have implemented a BasicAuth scheme.
Here are my security requirements -
Anybody can create a new user via POST /users endpoint
Only the superuser can call the GET /users endpoint
Any authenticated user can call the GET /users/{{email}} endpoint to get their own profile. If they call it with somebody else's email, then a 404 is returned.
The simplest way to implement these requirements would be to have access to the lookup dictionary in the check_auth method, then I could compare the authenticated user with the email param. But I could not find any way to do this. Instead I came up with the following hacky solution using event hooks.
class UserAuth(BasicAuth):
def check_auth(self, username, password, allowed_roles, resource, method):
try:
user = db.session.query(User).filter(User.email == username).one()
return user.authenticate(password)
except Exception as e:
# TODO: Log this exception
print('Error - {}'.format(e))
return False
def authz(req, lookup):
authn_user = db.session.query(User).filter(User.email == req.authorization['username']).one()
if 'email' in lookup and lookup['email'] == req.authorization['username']:
print('User requesting their own profile')
return
elif authn_user.is_superuser:
print('Superuser requesting something')
return
else:
print('Not authorized. Sabotaging request.')
lookup['email'] = 'None'
app.on_pre_GET_users += authz
Snippet from my settings.py file -
DOMAIN['users']['public_methods'] = ['POST']
DOMAIN['users']['additional_lookup'] = {'url': 'regex("[\w]+#[\w]+\.[\w]+")', 'field': 'email'}
Is there a better way to design this?

One simple option is to use the request object from Flask:
from flask import request
class UserAuth(BasicAuth):
def check_auth(self, username, password, allowed_roles, resource, method):
# you can inspect the request object here
print request.args, request.url, request.headers
return True

Related

Return authorization token with users credentials after sign in using devise-auth-token

I am using devise-auth-token. It is working perfectly. I want when a user is signed in, she/she should get authorization not only in header section but also with the user fields. Following are the screen shots attached for everyone to understand it better.Authorization token is in the headers section and I want that in the user returning variables like under image or nickname.
[1]: https://i.stack.imgur.com/viJOf.png
If you go through devise_token_auth's codebase, you can see how the tokens are generated here.
You can override the sessions_controller's create action like this:
def create
super do
render json: { user: current_user,
token: #token }.to_json and return
end
end
The and return prevents from DoubleRenderError.
It returns token along with your current_user in this format :
"token": {
"client": "ZYUyzKCspyEN1dhUg",
"token": "_y3JeRYQaEqH1cA",
"token_hash": "ZGzsWyFNcmfoq5YlD9c2",
"expiry": 166411321
}

How to check header of request inside a method using Rspec?

I have a method that will hit other API. And I made a condition to decide which auth token that will be used while posting to that API. Example:
class OrderFee
def perform
get_pricing
end
private
def get_pricing
payload = {
from: "a",
to: "b"
}
authorization = ''
if Config.UsingFirstToken?
authorization = "first_token"
else
authorization = "second_token"
end
response = Connection.MyCourier.post(url, payload) do |req|
req.headers['Authorization'] = authorization
end
response.body
end
end
I want to make 2 test case using Rspec to make sure if Config.UsingFirstToken true, it will hit the API using first_token, and otherwise. I can check the return value of this method, but I can't find a way to expect header from that POST action
You have access to the request object in your spec.
Expect what the header equals like this:
expect(request.headers['Authorization']).to eq("first_token")
Or whatever you expect the token to be.

Making conn.assigns available in multiple Phoenix views/templates

I need a user authentication token defined in the SessionControllerto be available in layout/app.html.eex.
My SessionController defines a token and assigns it to a conn.
token = Phoenix.Token.sign(conn, "user socket", user)
assign(conn, :user_token, token)
Then when I try to use the token in app.html.eex like the following,
<script>window.userToken = "<%= assigns[:user_token] %>"</script>
or
<script>window.userToken = "<%= #user_token %>"</script>
I get this error: (ArgumentError) assign #user_token not available in eex template.
conn.assigns are reset on every request. If you want to store something in SessionController and have it available in future requests, you can use put_session;
In your SessionController:
token = Phoenix.Token.sign(conn, "user socket", user)
conn
|> put_session(:user_token, token)
|> render(...)
Then, to access it in other controllers, you can use:
token = get_session(conn, :user_token)
To access it in multiple templates, you can then add a plug to the appropriate pipeline(s) in your Router:
pipeline :browser do
...
plug :fetch_user_token
end
...
def fetch_user_token(conn, _) do
conn
|> assign(:user_token, get_session(conn, :user_token))
end
Now you can access the token in any template with #user_token (or assigns[:user_token] or assigns.user_token or #conn.assigns[:user_token] or #conn.assigns.user_token; all will give the same result here).

what is Instagram API basic access in sandbox?

I try to use instagram API in sandbox.
I use Ruby with gem 'instagram'
But I can only call method
Instagram.user_recent_media( {:count => 5})
def user_recent_media(*args)
options = args.last.is_a?(Hash) ? args.pop : {}
id = args.first || "self"
response = get("users/#{id}/media/recent", options)
response
end
like #instagram = Instagram.user_recent_media( {:count => 5})
I try to call other method like
def follow_user(id, options={})
options["action"] = "follow"
response = post("users/#{id}/relationship", options, signature=true)
response
end
get
Instagram::BadRequest in HomeController#index
GET
https://api.instagram.com/v1/users/[user_ID]/follows.json?access_token=[Access_token]&max_id=10:
400: This request requires scope=follower_list, but this access token
is not authorized with this scope. The user must re-authorize your
application with scope=follower_list to be granted this permissions.
Is there any method I can use ??
instagram-ruby-gem API
Basically, you have to register your application with Instagram here and hope they accept it. There isn't a way around this.
Here's a little reading on how they basically said "buzz off" to third party app developers http://www.businessinsider.com/instagram-made-a-change-that-stopped-lots-of-third-party-apps-from-working-2016-6

Google+ API server side flow Rails 4.0 - Invalid Credentials

I am hoping someone with more experience can help me get my head round this Google API.
I am just building a demo app based off the ruby quickstart sample app, to explore this API. I have a Rails 4.0 app and have successfully (for the most part) installed the Google+ sign in.
It all goes wrong though once the access token for the user expires.
What my test app does successfully:
Signs the user in and retrieves access token for client side along with server code.
Exchanges the server code for access token & refresh token & id token
Creates token pair object that holds access token and refresh token, then stores it in a session hash
My app can then make requests to get user people list, insert moments etc.
So my question is, what is the correct way to get a new access token with the refresh token?
With the code below, after the access token expires I get error of "Invalid Credentials"
If I call $client.authorization.refresh! then I get error of "Invalid Request"
config/initializers/gplus.rb
# Build the global client
$credentials = Google::APIClient::ClientSecrets.load
$authorization = Signet::OAuth2::Client.new(
:authorization_uri => $credentials.authorization_uri,
:token_credential_uri => $credentials.token_credential_uri,
:client_id => $credentials.client_id,
:client_secret => $credentials.client_secret,
:redirect_uri => $credentials.redirect_uris.first,
:scope => 'https://www.googleapis.com/auth/plus.login',
:request_visible_actions => 'http://schemas.google.com/AddActivity',
:accesstype => 'offline')
$client = Google::APIClient.new(application_name: " App", application_version: "0.1")
*app/controllers/google_plus_controller.rb*
class GooglePlusController < ApplicationController
respond_to :json, :js
def callback
if !session[:token]
# Make sure that the state we set on the client matches the state sent
# in the request to protect against request forgery.
logger.info("user has no token")
if session[:_csrf_token] == params[:state]
# Upgrade the code into a token object.
$authorization.code = request.body.read
# exchange the one time code for an access_token, id_token and refresh_token from Google API server
$authorization.fetch_access_token!
# update the global server client with the new tokens
$client.authorization = $authorization
# Verify the issued token matches the user and client.
oauth2 = $client.discovered_api('oauth2','v2')
tokeninfo = JSON.parse($client.execute(oauth2.tokeninfo,
:access_token => $client.authorization.access_token,
:id_token => $client.authorization.id_token).response.body)
# skipped
token_pair = TokenPair.new
token_pair.update_token!($client.authorization)
session[:token] = token_pair
else
respond_with do |format|
format.json { render json: {errors: ['The client state does not match the server state.']}, status: 401}
end
end # if session csrf token matches params token
# render nothing: true, status: 200
else
logger.info("user HAS token")
end # if no session token
render nothing: true, status: 200
end #connect
def people
# Check for stored credentials in the current user's session.
if !session[:token]
respond_with do |format|
format.json { render json: {errors: ["User is not connected"]}, status: 401}
end
end
# Authorize the client and construct a Google+ service
$client.authorization.update_token!(session[:token].to_hash)
plus = $client.discovered_api('plus', 'v1')
# Get the list of people as JSON and return it.
response = $client.execute!(api_method: plus.people.list, parameters: {
:collection => 'visible',
:userId => 'me'}).body
render json: response
end
#skipped
end
Any help appreciated. Extra questions, the sample app I'm using as a guide builds a global authorization object (Signet::OAuth2::Client.new) - however other documentation I have read over the last day has stated building an authorization object for each API request. Which is correct?
This is a fragment I use in an app:
require 'google/api_client'
if client.authorization.expired? && client.authorization.refresh_token
#Authorization Has Expired
begin
client.authorization.grant_type = 'refresh_token'
token_hash = client.authorization.fetch_access_token!
goog_auth.access_token = token_hash['access_token']
client.authorization.expires_in = goog_auth.expires_in || 3600
client.authorization.issued_at = goog_auth.issued_at = Time.now
goog_auth.save!
rescue
redirect_to user_omniauth_authorize_path(:google_oauth2)
end
I am using omniauth OAuth2 (https://github.com/intridea/omniauth-oauth2), omniauth-google-oauth2 (https://github.com/zquestz/omniauth-google-oauth2), and google-api-ruby-client (https://code.google.com/p/google-api-ruby-client/).
Walk through:
1) If the access token is expired & I have a refresh token saved in the DB, then I try a access token refresh.
2) I set the grant type to "refresh_token" & then the "fetch_access_token!" call returns a plain old hash.
3) I use the 'access_token' key to return the new valid access token. The rest of the key/values can pretty much be ignored.
4) I set the "expires_in" attr to 3600 (1hr), and the "issued_at" to Time.now & save.
NOTE: Make sure to set expires_in first & issued_at second. The underlying Signet OAuth2 client resets your OAuth2 client issued_at value to Time.now, not the value you set from the DB, and you will find that all calls to "expired?" return false. I will post Signet source at the bottom.
5) If the access code is expired and I do NOT have a refresh token, I redirect to the omniauth path, and start the whole process over from scratch, which you have already outlined.
Note: I save almost everything in the DB: access_token, refresh_token, even the auth_code. I use the figaro gem to save env specific values such as client_id, client_secret, oauth2_redirect, and things like that. If you use multiple env's to develop, use figaro (https://github.com/laserlemon/figaro).
Here is the Signet source that shows how setting expires_in manually actually resets issued_at to Time.now(!). So you need to set "expires_in" first & THEN "issued_at" using the issued_at value you have from the DB. Setting expires_in second will actually RESET your issued_at time to Time.now... eg; calls to "expired?" will ALWAYS return false!
How do I know this? As Mr T would say, "Pain."
http://signet.rubyforge.org/api/Signet/OAuth2/Client.html#issued_at%3D-instance_method
# File 'lib/signet/oauth_2/client.rb', line 555
def expires_in=(new_expires_in)
if new_expires_in != nil
#expires_in = new_expires_in.to_i
#issued_at = Time.now
else
#expires_in, #issued_at = nil, nil
end
The line saying:
accesstype => 'offline'
should say:
access_type => 'offline'

Resources