How to hit Office 365 API from Ruby? - ruby-on-rails

I am attempting to access the Office 365 API from a Ruby on Rails backend and am having problems.
Whether I use the ruby_outlook gem (github) or follow Microsoft's official Ruby on Rails sample, I am getting 401 unauthorized.
My access_token is being saved using Omniauth and is valid, I checked by pasting it in here.
Am I using the correct access_token? It is over 1400 characters long (1442 to be exact). Can anyone show me an example of how to properly call the Office 365 Mail API from Ruby?
Code Example (using Faraday):
key = #auth[:key]
conn = Faraday.new(:url => 'https://outlook.office.com') do |faraday|
# Outputs to the console
faraday.response :logger
# Uses the default Net::HTTP adapter
faraday.adapter Faraday.default_adapter
end
response = conn.get do |request|
request.url '/api/v2.0/me/contacts'
request.headers['Authorization'] = "Bearer #{key}"
request.headers['Accept'] = 'application/json'
end
Code Example (using ruby_outlook gem):
client = RubyOutlook::Client.new
key = #auth[:key]
page = 1
view_size = 30
fields = [
'DisplayName',
'EmailAddresses'
]
sort = {:sort_field => 'DisplayName', :sort_order => 'ASC'}
contacts = client.get_contacts key, view_size, page, fields, sort
The exact error that the ruby_outlook gem returns is:
{"ruby_outlook_error"=>401}

The problem is a mismatch between the scopes in your token and the API endpoint you're using. The scope has to match the endpoint.
In your case, you requested a Graph API scope, but you're calling the Outlook API endpoint.
You should only have to register in one place for your client ID and secret: https://apps.dev.microsoft.com. It sounds like you may have also registered an app in the Azure Management Portal (which requires you to specify scopes in the registration itself).
Make sure you're using a client ID from apps.dev.microsoft.com and make sure your scopes are requested as 'https://outlook.office.com' scopes, and you should be good to go.
That Omniauth strategy might require that you register in the Azure Management Portal if they are dependent on Azure's v1 auth endpoints. In that case, forget what I said about apps.dev.microsoft.com and instead change your app registration to use the appropriate permissions from Microsoft Exchange Online.
UPDATE: Based on your comments, that Omniauth strategy DOES require the v1 Azure auth/token endpoints, so you have 2 options if you want to keep using that strategy:
Change your code to use the Graph endpoints. You'll need to use the Faraday option above (ruby_outlook is designed for the Outlook endpoints), and change your URL to https://graph.microsoft.com, and the request.url to /v1.0/me/contacts.
Create a new app registration at https://dev.outlook.com/appregistration, which will create the proper scopes for your code. You'll need an Office 365 account to login to the app registration tool.

Related

How to pass OAuth token using google-api-ruby-client People API?

We are migrating a large enterprise application from the Google Contacts API to the People API.
Before, using the OAuth token to make requests to the Contacts API was easy. We would authenticate with OAuth, and then pass the token to the Google Contacts API like so:
# access_token is the token we receive from OAuth
#user = GoogleContactsApi::User.new(access_token)
# make a request
contact_objects = user.contacts
The PeopleService code shows how to use an API key, and then only makes mention of the OAuth token:
# API key. Your API key identifies your project and provides you with API access,
# quota, and reports. Required unless you provide an OAuth 2.0 token
But I've been unable to find an example of how to use the OAuth token to make requests to the People API using the Ruby gem.
Could you please provide a simple example of how to make a People API request using the OAuth token? Specifically, we want access to a user's contacts' email address and phone numbers. I believe we will be using get_people. If you could provide this specific example, that would be wonderful.
Thank you! 😄
It looks like we needed to access access_token.token and set it like so:
require 'google/apis/people_v1'
class GooglePeopleApiWrapper
attr_reader :service, :access_token
def initialize(oauth_object)
# outh_object is the `access_token` from my question, which is
# provided in the OAuth response
#oauth_object = oauth_object
#service = Google::Apis::PeopleV1::PeopleServiceService.new
#service.authorization = #oauth_object.token
end
def fetch_contacts
# Fetch the next 10 events for the user
contact_objects = #service.list_person_connections(
'people/me',
page_size: 10,
person_fields: 'names,emailAddresses,phoneNumbers',
)
etc...
end
end

Authenticating docusign via Rails API (Omniauth + Devise) + JS Frontend

I'm trying to create an authentication flow using Auth Code Grant where I've added necessary omniauth strategy for Docusign to create /auth/docusign routes in Rails API only application.
Here are the steps followed
I'm issuing a request to the route from VueJS client.
window.open("http://localhost:4000/auth/docusign", "targetWindow", "width=350,height=250")
After user enters credentials and on successful login I'm calling the callback:
class SessionsController < Devise::SessionsController
def docusign
internal_destroy
#success = false
userinfo = request.env['omniauth.auth']
request_info = request.env['omniauth.params']
if userinfo
info = userinfo.info
cred = userinfo.credentials
user = User.find_by(email: info['email']) || User.find_by(id: session[:user_id])
if user
organization = user.organization
organization.organization_providers.where(provider_name: 'Docusign').destroy_all
OrganizationProvider.create(email: info['email'], token_expires_at: Time.at(cred['expires_at']), token_expires_at: Time.now, provider_name: 'Docusign', organization_id: organization.id, token: cred.token)
#success = true
end
end
render 'sessions/docusign'
end
end
I'd like to pass some params (which I'm accessing in the callback as request.env['omniauth.params']) for executing some backend tasks in the method.
When I try window.open("http://localhost:4000/auth/docusign?email='"+email+"'", "targetWindow", "width=350,height=250")
It says that the url doesn't match with any redirect urls
I have also tried passing in redirect_to('/auth/docusign', query: query) but on doing so, it doesn't open in a browser due to CORS.
I'm also trying to set it in session cookie, but since it's an API only server, I'm still working towards setting up cookie store.
Question
Which is the best way to achieve this? To pass some params in the callback and retrieve it.
Then the execution flow continues on the Rails server and the window serves a page with an appropriate response as per authentication status. However during this time, the client window which started the request is not aware of the authentication outcome.
Question
How can I communicate to the VueJS client that the authentication process is completed?
Question
Am I doing the above flow correctly or are there any better ways to achieve the same?
Thanks in advance
You need to log into your DocuSign Developer Account, Click on Admin and go on the left nav down to "API and Keys" where you can find the integration key you set. Did you set one?
If you did, you should find it and then add the redirectUri to the OAuth settings for that key (client ID in OAuth).
That is why DocuSign login tells you that the redirectURI doesn't match. You can add http://localhost:4000/auth to the list and that should work for your local env.
You cannot past custom variables on the redirectUri, it has to match exactly to the one you entered. If you need to pass values to it, there's a way to do that using state.
Here is how the URL should look, notice the &state= part of it:
https://account-d.docusign.com/oauth/auth?
response_type=code
&scope=YOUR_REQUESTED_SCOPES
&client_id=YOUR_INTEGRATION_KEY
&state=YOUR_CUSTOM_STATE
&redirect_uri=YOUR_REDIRECT_URI
&login_hint=YOUR_LOGIN_HINT
You can put whatever you want in there (URI encoded of course) and that value would come back to you when redirected back also with &state= parameter.
This solves the problem and allows you to pass arguments back to your redirect URI.

How to set the Google API user to impersonate using a Service Account (Ruby Client)?

I have a service account and I am using the OAuth2 gem together with the Google API Ruby Client:
require 'oauth2'
require 'google/api_client'
My objective is to fetch an access token which allows access to a specific user’s account and no other. I think this is often achieved using the sub parameter when using HTTP, but how do I do this using the Ruby client libraries above?
I can get an access token successfully and use it to access the drive v2 file list API. I always see a single "How to get started with Drive" document in the response and no other.
I suspect my attempt to impersonate a user has not succeeded. Basically I’m passing in an email address as a sub option to the function below:
client = Google::APIClient.new(...)
access_token = OAuth2::AccessToken.new(oauth2_client, client.authorization.access_token, {‘sub’ => ‘user#domain.com’} )
Presumably that isn’t the way to do this.
How to I retrieve an access token which is scoped to permit access impersonating a single user and no other?
Following Steve's comment I have dropped the intridea Gem and am now using Signet. I get a little further but I am stuck getting an access_denied error when I specify a :person. Without that I can authenticate but obviously I get access as the issuer.
require 'google/api_client'
...
client = Google::APIClient.new(:application_name => application_name, :application_version => application_version)
...
opts = {
:token_credential_uri => 'https://accounts.google.com/o/oauth2/token',
:audience => 'https://accounts.google.com/o/oauth2/token',
:scope => scope,
:issuer => service_account_email_address,
:signing_key => key,
:person => 'my.email#address.com'
})
client.authorization = Signet::OAuth2::Client.new(opts)
access_token = client.authorization.fetch_access_token!
...
>> client.authorization.fetch_access_token!
Signet::AuthorizationError: Authorization failed. Server message:
{
"error" : "access_denied",
"error_description" : "Requested client not authorized."
}
I have a console project which I do a 'test install flow' on but the client does not seem to be trusted. Where do I look?
Thanks.
Not sure how to do it with intridea's oauth2 gem, but the signet, the default for the ruby client library, handles this case directly.
There's a snip-it for how to do this in the readme. The only addition is, as you point out, adding the subject to get a token for the specific user. That can be specified either when instantiating the OAuth2 client or as an option to fetch_access_token!
Here's the summary.
Do not use the OAuth2 gem from intridea if you are only trying to authenticate with Google. Rely on the signet Gem.
If you use a service account, you can specify a :person element and it will work so you impersonate that user and work on their behalf.
When you test your integration you must do so against the domain in which your project is hosted. You cannot test in a third-party domain until you've published your app!

Rails 3 Linkedin API gem

I'm trying to set up the linkedin api in a rails 3 app using the linkedin gem. I don't want the user to have to authenticate my app in order for the API to get their info. I only need one piece of their public profile (the headline). So, maybe I should just be using xml or json to pull this off (not exactly sure how to get that with linkedin either).
I have the following in a helper so that I can call linkedin_header() in a loop of users. I only have 'client' as the last line of the following code while debugging. It outputs as expected (#). It seems like I am only a step away from success. How can I access a given users headline? I have tried using "client = client.profile(:url => 'linkedin_user_url')", but that return "Call must be made on behalf of a member".
def linkedin_header(account_user)
user = User.find(account_user)
account = Account.where(:user_id => user, :external_id => 1)
api_key = 'aaaaaaaa'
api_secret = 'bbbbbbbb'
client = LinkedIn::Client.new(api_key, api_secret)
rtoken = client.request_token.token # this returns correctly
rsecret = client.request_token.secret # this returns correctly
client
# client = client.profile(:url => 'linkedin_user_url')
end
So, I guess I have two questions. Is my request (public headline of any user) too simple for the above...should I be using XML or JSON. And, if Im close...can I make the API work for me without the user having to authenticate via linkedin.
Based off of what I read from the LinkedIn API reference (http://developer.linkedin.com/documents/authentication)
You have to make requests to their API only after being authenticated. (Using OAuth Keys) Rather than just grabbing the publicly available information.
It seems like since you want a small piece of information (the public headline of any user) you'd want some sort of implementation like Facebook's OpenGraph. After looking around on LinkedIn, I don't see any sort of public implementation like that.
I would suggest checking out this gem:
https://github.com/yatishmehta27/linkedin-scraper
It seems to be the type of solution you're looking for.

Using user's token to make requests to Twitter API in Rails

I'm using Omniauth to get credentials from Twitter for a specific User. Part of the OmniAuth object that I get is like this:
credentials=#
<Hashie::Mash secret="XXXX" token="XXXX">
extra=#<Hashie::Mash access_token=#<OAuth::AccessToken:xxxx #token="xxxx", #secret="xxxxx", ..
Right now I'm storing the credentials[token] and the UID for that specific User. At some point I want to fetch the Twitter API using the auth for that specific User to avoid getting the 150 max requests for a specific IP. Right now I'm just doing this:
twitter_user_name = Twitter.user(user_id).screen_name
So, how can I do to make those requests using the Twitter gem using the auth provided by OmniAuth instead of doing requests from my own IP (unauthenticated calls and therefore limited)
After playing with the gem, I've figured how to do it:
You want to create a new API object for a specific User, so you want to do this:
client = Twitter::Client.new(:oauth_token => 'XXXXX', :oauth_token_secret => 'XXXX')
And use this client to do the requests.

Resources