I am working on creating an API for my ruby application that authenticates users based on HTTP Digest Authentication. I decided to use the Grape API library because it makes creating an API cleaner in ruby. The Grape documentation states that you can use Digest Authentication like:
http_digest({ :realm => 'Test Api', :opaque => 'app secret' }) do |username|
# lookup the user's password here
{ 'user1' => 'password1' }[username]
end
The Grape implementation above is a wrapper for Rack::Auth::Digest::MD5
Now also for security i read that as of RFC 2617 you don't need to store the password as plain text in the database you store an MD5 digest of the username:realm:password and authticate against that so i created a DataMapper model:
class Key
include DataMapper::Resource
property :id, Serial
property :username, String
property :password, String
property :active, Boolean, :default => true
property :created_at, DateTime, :default => DateTime.now
property :updated_at, DateTime
end
Now with what I provided, I am lost as to how to connect these two and make it work.
Unfortunately, Rack::Auth::Digest::MD5 expects a plaintext password on the server side.
The Grape example code shows a hard-coded lookup of password.
You could replace { 'user1' => 'password1' }[username] with
Key.first( :username => username ).password
provided you stored plaintext passwords in the Key class. You could store these reversibly-encrypted I suppose, although that doesn't add much security unless you construct relatively complex/costly schemes for key management.
Not sure if there is a way around this that would let you store hashed passwords. MD5 isn't the most secure hashing choice (although better than nothing!). If security is an important concern for your API, you will want to look beyond digest auth - using https would help, for example.
Edit: Following a bit of to-and-fro in discussions, the following variation of Grape's example does allow you to store the MD5'd password:
auth :http_digest, { :realm => { :realm => 'Llama', :passwords_hashed => true, :opaque => "7302c32d39bbacb5ed0ace096723fd" } } do |username|
Digest::MD5.hexdigest( 'fred:Llama:654321' )
end
The example gives a hard-coded username:'fred', password:'654321' response. So I think your target code is something like:
auth :http_digest, { :realm => { :realm => 'Llama', :passwords_hashed => true, :opaque => "7302c32d39bbacb5ed0ace096723fd" } } do |username|
k = Key.first( :username => username )
k ? k.password : nil
end
And you store the result of Digest::MD5.hexdigest( "#{username}:#{realm}:#{password}" ) in each user's password property.
Note the double-level hash with :realm twice. This is a bit hacky, but at least you don't have to write your own middleware, Grape is still dealing with it. This is not a documented feature of Grape or covered with tests, so may not work in future versions.
Related
I'm sure that this is a simple error, but I'm interested in writing a program that collects information on all of my github repositories. While this seems simple enough to do with Octokit, I've run into issues associated with authenticating my session.
client = Octokit::Client.new \
:login => 'MY_USER_NAME',
:password => 'MY_PASSWORD'
puts client
user = client.user("MY_USER_NAME", :headers => { "PERSONAL_ACCESS_TOKEN_NAME" => "TOKEN" })
puts user
Unfortunately this results in the following:
GET https://api.github.com/users/mccoleman75225: 401 - Must specify two-factor authentication OTP code. // See: https://developer.github.com/v3/auth#working-with-two-factor-authentication (Octokit::OneTimePasswordRequired)
How does someone go about authenticating their session?
As of January 2022, you can create a PAT (Personal Access Token) in your GitHub Developer Settings and use that to connect through the Octokit client like so:
client = Octokit::Client.new(:access_token => "<Your Personal Access Token>")
user = client.user
user.login
# => "monacat"
Here's a step-by-step guide on how to create a PAT. Try to select the correct permissions when creating your token or you'll get back a 403 error with a message explaining the missing scope. You can always go back and edit your scopes later though.
Sources:
Octokit.rb — Authentication
GitHub API Authentication - Personal Access Tokens
Looks like you have 2 Factor Authentication enabled on your account so you'll need to add your 2FA token:
client = Octokit::Client.new \
:login => 'defunkt',
:password => 'c0d3b4ssssss!'
client.create_authorization(:scopes => ["user"], :note => "Name of token",
:headers => { "X-GitHub-OTP" => "<your 2FA token>" })
# => <your new oauth token>
See documentation
According to this, the ActiveMerchant PayPal Express Gateway is initialized like this:
paypal_options = {
login: "API_USERNAME_HERE",
password: "API_PASSWORD_HERE",
signature: "API_SIGNATURE_HERE"
}
::EXPRESS_GATEWAY = ActiveMerchant::Billing::PaypalExpressGateway.new(paypal_options)
I'm definitely supplying a signature, yet I'm getting this error:
An API Certificate or API Signature is required to make requests to PayPal
The PayPal initializer looks like this (found here):
def initialize(options = {})
requires!(options, :login, :password)
headers = {'X-PP-AUTHORIZATION' => options.delete(:auth_signature), 'X-PAYPAL-MESSAGE-PROTOCOL' => 'SOAP11'} if options[:auth_signature]
options = {
:pem => pem_file,
:signature => signature,
:headers => headers || {}
}.update(options)
if options[:pem].blank? && options[:signature].blank?
raise ArgumentError, "An API Certificate or API Signature is required to make requests to PayPal"
end
super(options)
end
I don't understand what this initializer is doing with the signature and why it's not accepting it as per the example.
Here are the options I'm passing, which I've put to STDOUT:
{
"password" =>"***************",
"signature" =>"AVtrAKGQXoUNJFduUU0pn1dewq80AK9KYWenyFwYcduz8elS85B8T0Wc",
"allow_guest_checkout" =>true,
"login" =>"********************",
"test" =>true
}
Can someone help me with this please?
Note that I'm using this in JRuby, but I don't think that makes any difference in this case.
EDIT after #PiersC's comments:
I hardcoded this instead of taking them as params from Java and it worked:
options = {
login: "*************",
password: "*****************",
signature: "AVtrAKGQXoUNJFduUU0pn1dewq80AK9KYWenyFwYcduz8elS85B8T0Wc"
}
However this has led to another question. I've been converting the Java maps to Ruby hashes like this:
def self.convert_hash(map)
hsh = {}
map.each {|key, value| hsh[key] = value}
hsh.with_indifferent_access
end
And this has worked on all other gateways. How do I convert the Java map correctly to the options hash in Ruby?
Your option keys are strings but should be symbols, eg. { password: '***', ... } ActiveSupport::HashWithInvalidAccess hides (obscures?) the difference between symbol keys and string keys, but if you are using a regular Hash then { 'signature' => signature } is not the same as { signature: signature }.
I'm having some trouble getting the sample code for instantiating a Drive Service Account working. I've set up the service account in the API console as directed and included the scope for the 'https://www.googleapis.com/auth/drive', but running this generates the following error: "Authorization failed. Server message: (Signet::AuthorizationError)".
Oddly, if I omit the user_email address it doesn't generate an error.
My objective is to be able to do an audit on all the files stored on the organization's Drive, and it's my understanding that using a service account would be the way to get a listing of all the files stored.
Have I missed some special setting on the server side for this?
require 'google/api_client'
## Email of the Service Account #
SERVICE_ACCOUNT_EMAIL = '<service account email>#developer.gserviceaccount.com'
## Path to the Service Account's Private Key file #
SERVICE_ACCOUNT_PKCS12_FILE_PATH = '<private key file>-privatekey.p12'
def build_client(user_email)
key = Google::APIClient::PKCS12.load_key(SERVICE_ACCOUNT_PKCS12_FILE_PATH, 'notasecret')
asserter = Google::APIClient::JWTAsserter.new(SERVICE_ACCOUNT_EMAIL, 'https://www.googleapis.com/auth/drive', key)
client = Google::APIClient.new
client.authorization = asserter.authorize(user_email)
return client
end
client = build_client("<users email address>")
This looks to me like you are using an older example. I think that's how you used to do it about a year ago. Back in late 2012 that method of setting up the app was deprecated because Signet was updated to handle all aspects of the OAuth2 setup.
Here is the code I generally use to create a service account. You can tweak it to fit into your method.
client.authorization = Signet::OAuth2::Client.new(
:token_credential_uri => 'https://accounts.google.com/o/oauth2/token',
:audience => 'https://accounts.google.com/o/oauth2/token',
:scope => "https://www.googleapis.com/auth/drive",
:issuer => "<service account email>#developer.gserviceaccount.com",
:signing_key => Google::APIClient::KeyUtils.load_from_pkcs12("<private key file>-privatekey.p12", "notasecret"),
:person => "<users email address>")
client.authorization.fetch_access_token!
If you are still having issues let me know and I'll see if I can help.
Using version 0.9.13 of google-api-client, I succeeded in using the following slight adaptation of Woodward's answer (note the absence of the person parameter):
def service_account_authorization(credentials_file, scope)
credentials = JSON.parse(File.open(credentials_file, 'rb').read)
authorization = Signet::OAuth2::Client.new(
:token_credential_uri => 'https://accounts.google.com/o/oauth2/token',
:audience => 'https://accounts.google.com/o/oauth2/token',
:scope => scope,
:issuer => credentials['client_id'],
:signing_key => OpenSSL::PKey::RSA.new(credentials['private_key'], nil),
)
authorization.fetch_access_token!
authorization
end
This snippet takes a file as it was downloaded from Google Cloud Console for a service account and returns an auth object that can be fed to Google::Apis::*Service.authorization.
Thanks James!
I have worked with service account+Drive+file permissions using Java. In order to use permissions for a particular user, I had to allow certain scope. The only thing I can guess about your issue is that you might have missed the Delegation part
I'm creating custom strategy for Nimble.com API. As they're using OAuth, it's pretty simple.
require 'omniauth-oauth2'
module OmniAuth
module Strategies
class Nimble < OmniAuth::Strategies::OAuth2
option :name, "nimble"
option :client_options, {
:site => "https://api.nimble.com",
:authorize_url => '/oauth/authorize',
:token_url => '/oauth/token'
}
# option :access_token_options, {
# :mode => :query,
# :param_name => :access_token
# }
option :provider_ignores_state, true
uid { raw_info['email'] }
info do
{
'uid' => raw_info['email'],
'name' => raw_info['name'],
'email' => raw_info['email']
}
end
extra do
{ 'raw_info' => raw_info }
end
def raw_info
access_token.options[:mode] = :query
access_token.options[:param_name] = :access_token
#raw_info ||= access_token.get('/api/users/myself/', {:parse => :json}).parsed
end
end
end
end
For passing tokens, they need to use access_token parameter in URL. When I specify options in raw_info function directly, as in sample — it's OK.
When I'm trying to specify this options in access_token_options hash (like in commented section) — parameters aren't passing to token. I'm not very good in Ruby, so I didn't figure out from libraries sources — how correctly pass parameters to access_token in OmniAuth OAuth2 descendants.
I'd like to make it "right way", so access_token initialised with correct options, plese someone point me the right way.
Thank you!
I've explored several existing strategies (GitHub, 4SQ), and looks like it's normal practice to directly modify access token options.
So I'll stay with it :)
I am a beginner with Ruby and Rails but I managed to use OmniAuth for authentication via Facebook. Everything works fine, I am able to create users and they are able to login with Facebook.
The problem is, I would like to take some of the user data (such as email, profile photo, etc.) and save it.
Going through the README (https://github.com/mkdynamic/omniauth-facebook), I managed to find:
Here's an example Authentication Hash available in request.env['omniauth.auth']:
{
:provider => 'facebook',
:uid => '1234567',
:info => {
:nickname => 'jbloggs',
:email => 'joe#bloggs.com',
:name => 'Joe Bloggs',
:first_name => 'Joe',
:last_name => 'Bloggs',
:image => 'http://graph.facebook.com/1234567/picture?type=square',
:urls => { :Facebook => 'http://www.facebook.com/jbloggs' },
:location => 'Palo Alto, California',
:verified => true
}
}
I tried to do more searching on the Authentication Hash and got this which lists some of the information that can be fetched: https://github.com/intridea/omniauth/wiki/Auth-Hash-Schema
The thing is, I asked for certain permissions. The question is, how do I know what sort of information Facebook is sending? Unfortunately, saying the info is in request.env['omniauth.auth'] does me not much good. How do I fetch the information from here?
I am a real beginner going through the Rails tutorial (http://ruby.railstutorial.org/) but trying to create my own app by trial and error.
request.env['omniauth.auth'] will give you a hash. Check the Ruby docs for what you can do with a hash.
http://ruby-doc.org/core-1.9.3/Hash.html
For any elements that are not always going to be there you can just check for blank? (a Rails convenience method), e.g.
omniauth = request.env['omniauth.auth']
unless omniauth['info']['email'].blank?
send_spam omniauth['info']['email'] # ;)
end
You would probably find the following screencast useful if you haven't seen it already:
http://railscasts.com/episodes/235-omniauth-part-1