Asana API Key Basic authentication 401 - asana

i am getting a 401 response from Asana with my request.
var url = "https://app.asana.com/api/1.0/users/me";
byte[] encodedByte = System.Text.ASCIIEncoding.ASCII.GetBytes(APIKey);
APIKey = Convert.ToBase64String(encodedByte);
WebRequest wrGETURL;
wrGETURL = WebRequest.Create(url);
wrGETURL.Headers.Add("Authorization: Basic " + APIKey);
string result;
using (StreamReader reader = new StreamReader(wrGETURL.GetResponse().GetResponseStream()))
{
result = reader.ReadToEnd();
}
return result;

The way HTTP basic auth works, you encode the username and password together as base64, separated by a colon. In the Asana API the key is the username and there is no password.
From the docs at https://asana.com/developers/documentation/getting-started/authentication#sts=API%20Keys :
Note: Most utilities and libraries that allow you to specify a username and password will handle proper encoding of the header for you. However, if you need to set the Authorization header manually, the header value is constructed by adding a colon (:) to the API key, then base64-encoding that string. You can read more on basic authentication if you need further details.
So, you should probably do:
byte[] encodedByte = System.Text.ASCIIEncoding.ASCII.GetBytes(APIKey + ":")

Related

Need suggestion on with MS Graph Rest API url

I kinda need to suggestion on picking the accurate MSGraph url between below 2 urls.
https://graph.microsoft.com/v1.0/users/{EmailAddress}.
https://graph.windows.net/{TenantID}/users?$filter=userPrincipalName%20eq%20'{EmailAddress}'&api-version=1.6
More details on my situation:
I need to resolve the UserName and ObjectID from MS Graph Rest api using EmailAddress. I have 100s of EmailAddress that I need to resolve UserName and ObjectID for. I was using below which is working fine for most of the time. But lastnight i realized it is not returning valid response for 2 users.
var httpClient = new HttpClient
{
BaseAddress = new Uri("https://graph.microsoft.com/")
};
string URI = $"/v1.0/users/{EmailAddress}";
Then I realized, below rest API url is retuning accurate response for the missing users as comared to above url
var httpClient = new HttpClient
{
BaseAddress = new Uri("https://graph.windows.net/")
};
string URI = $"/{TenantID}/users?$filter=userPrincipalName%20eq%20'{EmailAddress}'&api-version=1.6";

Why does adding "offline_access" invalidate MS Graph scope and how do I otherwise get a refresh token?

I'm trying to request an access token and a refresh token from Microsoft Graph, but adding "offline_access" to the scope makes the scope invalid.
This is for a service where a user gives us access to an Outlook Calendar once and after that the service checks their calendar every 20 minutes for events. I have managed to get consent and pull data when requesting access to "User.Read" and "Calendars.Read", but when I add "offline_access" I get the message The provided resource value for the input parameter 'scope' is not valid.
I have an array with the permissions I want
private static final String[] ACCESS_PERMISSIONS = {
"https://graph.microsoft.com/User.Read",
"https://graph.microsoft.com/Calendars.Read",
"https://graph.microsoft.com/offline_access",
};
and then combine and encode them
String encodedScope = URLEncoder.encode(
Arrays.stream(ACCESS_PERMISSIONS)
.reduce(
(a,b) -> a + " " + b)
.get(), "UTF-8").replace("+","%20");
which results in the string https%3A%2F%2Fgraph.microsoft.com%2FUser.Read%20https%3A%2F%2Fgraph.microsoft.com%2FCalendars.Read%20https%3A%2F%2Fgraph.microsoft.com%2Foffline_access
Then I request the tokens
String appId = "client_id=" + clientID;
String scope = "&scope=" + encodedScope;
String authCode = "&code=" + code;
String redirect = "&redirect_uri=" + redirectUri;
String grantType = "&grant_type=authorization_code";
String secret = "&client_secret=" + clientSecret;
String data = appId + scope + authCode + redirect + grantType + secret;
// Create POST request
URL url = new URL(postUrl);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
// Configure request
connection.setRequestMethod("POST");
connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
connection.setRequestProperty("Accept", "application/json");
connection.setDoOutput(true);
// Send data
OutputStream os = connection.getOutputStream();
byte[] inputBytes = data.getBytes(ENCODING);
os.write(inputBytes, 0, inputBytes.length); // Bytes, Offset, Length
The final link then looks like
https://login.microsoftonline.com/common/oauth2/v2.0/authorize?
client_id=[CLIENT ID]
&response_type=code
&redirect_uri=http%3A%2F%2Flocalhost%3A8080%2Fsport%2Fauth%2Foutlook
&response_mode=query
&scope=https%3A%2F%2Fgraph.microsoft.com%2FUser.Read%20https%3A%2F%2Fgraph.microsoft.com%2FCalendars.Read%20https%3A%2F%2Fgraph.microsoft.com%2Foffline_access
&state=2737
I would expect this to return an access token and a refresh token, but as previously mentioned I get the message that the resource value for the scope is not valid.
It is essential for the service to be able to refresh the tokens as it should run about a year without intervention.
The scope isn't https://graph.microsoft.com/offline_access, it is simply offline_access. Offline Access is a special AAD scope that tells it to return a Refresh Token, it isn't a Microsoft Graph scope.
You can actually drop https://graph.microsoft.com/ across the board. The only time you need to specify the FQDN is when you're requesting the default set of scopes from the app registration (i.e. https://graph.microsoft.com/.default) but that is generally only used when you're using App-Only/Daemon authentication flow (Client Credentials).

Google oauth1 migration to oauth2: Invalid authorization header

I am stuck with the oauth1 migration to oauth2. I don't want to ask my users to grant contact access again, so I prefer to do migration myself but I am getting hard time figuring out why it doesn't work.
I'm getting this error from Google server:
DEBUG - << " "error" : "invalid_request",[\n]"
DEBUG - << " "error_description" : "Invalid authorization header."[\n]"
here is my code, I did almost the same thing when consuming google api, but for migration it is not working.
GoogleOAuthParameters oauthParameters = new GoogleOAuthParameters();
oauthParameters.setOAuthConsumerKey(getConsumerKey());
oauthParameters.setOAuthConsumerSecret(getConsumerSecret());
oauthParameters.setOAuthToken(token);
ClassPathResource cpr = new ClassPathResource("mykey.pk8");
File file = cpr.getFile();
PrivateKey privKey = getPrivateKey(file);
OAuthRsaSha1Signer signer = new OAuthRsaSha1Signer(privKey);
GoogleOAuthHelper oauthHelper = new GoogleOAuthHelper(signer);
String requestUrl = "https://www.googleapis.com/oauth2/v3/token";
String header = oauthHelper.getAuthorizationHeader(requestUrl, "POST", oauthParameters);
String payload = "grant_type=urn:ietf:params:oauth:grant-type:migration:oauth1&client_id="+com.app.framework.utils.OAuthHelper.OAUTH2_CLIENT_ID+"&client_secret="+com.app.framework.utils.OAuthHelper.OAUTH2_CLIENT_SECRET;
HttpClient httpClient = new DefaultHttpClient();
HttpPost httpPost = new HttpPost(requestUrl);
httpPost.addHeader("Authorization", header);
httpPost.addHeader("Content-Type", "application/x-www-form-urlencoded");
httpPost.setEntity(new ByteArrayEntity(payload.getBytes()));
String response = httpClient.execute(httpPost, new BasicResponseHandler());
After some emails exchange with #Miguel, he successfully points me to the solution.
The issue:
The OAuthHelper that GoogleOAuthHelper extends uses TwoLeggedOAuthHelper to build the base_string. The TwoLeggedOAuthHelper was not injecting 3 required parameters: client_id, client_secret and the grant_type in the base_string.
The solution:
I had to create my own classes: copy/paste code from OAuthHelper to MyOAuthHelper and from TwoLeggedOAuthHelper to MyTwoLeggedOAuthHelper. You need some declarations from GoogleOAuthHelper to resolve compilation errors.
MyOAuthHelper will call MyTwoLeggedOAuthHelper instead of TwoLeggedOAuthHelper.
Now in MyTwoLeggedOAuthHelper, around line 79, locate the
String baseString = OAuthUtil.getSignatureBaseString(baseUrl,
httpMethod,…
and add the following:
String clientId = "client_id%3DXXX123456789XXX.apps.googleusercontent.com%26";
String clientSecret = "client_secret%3DXXXX_XXXX_XX_XX%26";
String grantType = "grant_type%3Durn%253Aietf%253Aparams%253Aoauth%253Agrant-type%253Amigration%253Aoauth1%26";
baseString = StringUtils.replace(baseString, "token&", "token&" + clientId + clientSecret + grantType);
Some notes:
client_id and client_secret must be the one your backend used to get the OAUTH1 access token. Be careful with that especially if you have multiple "Client ID for web application" defined in your Google console.
Notice the crazy grant_type encoded twice.
The Google classes used are located in maven: com/google/gdata/core/1.47.1/core-1.47.1.jar
Kudos to #Miguel
Your request is failing signature verification. Please check out the responses to this related question for detailed instructions on how to construct the base string for your request and sign it.
Hope that helps!

QuickBooks Online querying with filter returns 401 everytime

I've had success creating objects with POST and Content-Type application/xml
I've also had success querying using Content-Type application/x-www-form-urlencoded with a blank request body which returns all of the object type depending on which URI I specify.
I can also get the same to work with something like PageNum=1&ResultsPerPage=1 in the request body and I have figured out how to incorporate that into the signature so I get a valid response.
However no matter how I format it, I cannot get anything other than a 401 response when I try to use a filter (something basic like Filter=FAMILYNAME :EQUALS: Doe). I've read over the OAuth Core 1.0 Revision A specifications on how all parameter names and values are escaped using the [RFC3986] percent-encoding. However I feel like I'm missing a step or formatting incorrectly. I've seen inconsistent information in my searching through Intuit's forums on what exactly is the proper format.
Any help on this would be greatly appreciated. I've been struggling with this for a good week now.
The response I get when trying to use a filter is:
HTTP Status 401 - message=Exception authenticating OAuth; errorCode=003200; statusCode=401
----Update----
I'm am seeing the same error when I try to use filters with the New IPP Developer Tools - IPP API Explorer. I'm using the IDS V2 QBO API Explorer. I'm able to use that tool to do a retrieve all Post and the response shows all of my customers, but when I try to use a filter I get :
Server Error
401 - Unauthorized: Access is denied due to invalid credentials.
You do not have permission to view this directory or page using the credentials that you supplied.
Any Ideas? If I'm getting the same error from the API Explorer tool, it makes me think the problem is something else entirely.
----Final Update----
I have finally had success with filters and I believe I have figure out what my problem was. I was always suspicious that I was able to get queries with pagination like "PageNum=1&ResultsPerPage=1" to work, but could not get something like "Filter=FAMILYNAME :EQUALS: Doe". I suspected there problem was with the white space in the filter format. What threw me off tracking this down earlier was that I could not get the filters to work in the IDS V2 QBO API Explorer. That made me suspect there was something else going on. I decided to ignore the API Explorer all together and focus on why I could get it to work the one way but no the other.
I believe my problem came down to improper encoding of the Filter's value in the signature. That explains the 401 invalid signature errors I was getting.
"Filter=Name :EQUALS: Doe" becomes "Filter=Name%20%3AEQUALS%20%3ADoe" after normalization.
Percent-Encoding that should give "Filter%3DName%2520%253AEQUALS%2520%253ADoe".
In essence you have to "double" encode the blank space and the colons, but not the equal sign. I tried many permutations of doing the encoding, but believe my mistake was that I was either not "double" encoding, or when I was double encoding I was including the '=' sign. Either way breaks your signature. Thanks for everyone's input.
I believe my problem came down to improper encoding of the Filter's value in the signature. That explains the 401 invalid signature errors I was getting.
I used an online tool to take me through the steps in properly signing an Oauth request. While going through those steps I realized my problem was with the steps where you normalize the request parameters and then percent-encode them. I was including the '=' of the filter in the normalization step, which breaks your signature. The tool I used can be found at:
http://hueniverse.com/2008/10/beginners-guide-to-oauth-part-iv-signing-requests/
Thanks for everyone's input.
Do you get a 401 with the same request in the API Explorer?
http://ippblog.intuit.com/blog/2013/01/new-ipp-developer-tool-api-explorer.html
Also, are you using the static base URL or retrieving it at runtime?
https://ipp.developer.intuit.com/0010_Intuit_Partner_Platform/0050_Data_Services/0400_QuickBooks_Online/0100_Calling_Data_Services/0010_Getting_the_Base_URL
If you are using the static base URL, try switching to the runtime base URL to see if you still get the error.
peterl answered one of my questions on here that may also answer yours. I had been trying to put the Filters in the body when they should have gone into the header. Here was peterl's code sample for getting all unpaid invoices (open balance greater than 0.00) for a particular customer.
http://pastebin.com/raw.php?i=7VUB6whp
public List<Intuit.Ipp.Data.Qbo.Invoice> GetQboUnpaidInvoices(DataServices dataServices, int startPage, int resultsPerPage, IdType CustomerId)
{
StringBuilder requestXML = new StringBuilder();
StringBuilder responseXML = new StringBuilder();
var requestBody = String.Format("PageNum={0}&ResultsPerPage={1}&Filter=OpenBalance :GreaterThan: 0.00 :AND: CustomerId :EQUALS: {2}", startPage, resultsPerPage, CustomerId.Value);
HttpWebRequest httpWebRequest = WebRequest.Create(dataServices.ServiceContext.BaseUrl + "invoices/v2/" + dataServices.ServiceContext.RealmId) as HttpWebRequest;
httpWebRequest.Method = "POST";
httpWebRequest.ContentType = "application/x-www-form-urlencoded";
httpWebRequest.Headers.Add("Authorization", GetDevDefinedOAuthHeader(httpWebRequest, requestBody));
requestXML.Append(requestBody);
UTF8Encoding encoding = new UTF8Encoding();
byte[] content = encoding.GetBytes(requestXML.ToString());
using (var stream = httpWebRequest.GetRequestStream())
{
stream.Write(content, 0, content.Length);
}
HttpWebResponse httpWebResponse = httpWebRequest.GetResponse() as HttpWebResponse;
using (Stream data = httpWebResponse.GetResponseStream())
{
Intuit.Ipp.Data.Qbo.SearchResults searchResults = (Intuit.Ipp.Data.Qbo.SearchResults)dataServices.ServiceContext.Serializer.Deserialize<Intuit.Ipp.Data.Qbo.SearchResults>(new StreamReader(data).ReadToEnd());
return ((Intuit.Ipp.Data.Qbo.Invoices)searchResults.CdmCollections).Invoice.ToList();
}
}
protected string GetDevDefinedOAuthHeader(HttpWebRequest webRequest, string requestBody)
{
OAuthConsumerContext consumerContext = new OAuthConsumerContext
{
ConsumerKey = consumerKey,
ConsumerSecret = consumerSecret,
SignatureMethod = SignatureMethod.HmacSha1,
UseHeaderForOAuthParameters = true
};
consumerContext.UseHeaderForOAuthParameters = true;
//URIs not used - we already have Oauth tokens
OAuthSession oSession = new OAuthSession(consumerContext, "https://www.example.com",
"https://www.example.com",
"https://www.example.com");
oSession.AccessToken = new TokenBase
{
Token = accessToken,
ConsumerKey = consumerKey,
TokenSecret = accessTokenSecret
};
IConsumerRequest consumerRequest = oSession.Request();
consumerRequest = ConsumerRequestExtensions.ForMethod(consumerRequest, webRequest.Method);
consumerRequest = ConsumerRequestExtensions.ForUri(consumerRequest, webRequest.RequestUri);
if (webRequest.Headers.Count > 0)
{
ConsumerRequestExtensions.AlterContext(consumerRequest, context => context.Headers = webRequest.Headers);
if (webRequest.Headers[HttpRequestHeader.ContentType] == "application/x-www-form-urlencoded")
{
Dictionary<string, string> formParameters = new Dictionary<string, string>();
foreach (string formParameter in requestBody.Split('&'))
{
formParameters.Add(formParameter.Split('=')[0], formParameter.Split('=')[1]);
}
consumerRequest = consumerRequest.WithFormParameters(formParameters);
}
}
consumerRequest = consumerRequest.SignWithToken();
return consumerRequest.Context.GenerateOAuthParametersForHeader();
}
You can also see my original Question Here on StackOverflow: Query for All Invoices With Open Balances using QuickBooks Online (QBO) Intuit Partner Platform (IPP) DevKit.

Require Google to return email address as part of OAuth

I am using OAuth to access Gmail with dotNetOAuth. How can I force Google to return user's email address as part of callback after authorization?
By default, Google OAuth callback only returns the token secret and access tokens.
First you need to add the following scope (https://www.googleapis.com/auth/userinfo.email) to your oauth request.
After you're back to your app from Google and you have your access token, you can make a request using the access token to https://www.googleapis.com/userinfo/email?alt=json.
This will return the email address. More info at http://sites.google.com/site/oauthgoog/Home/emaildisplayscope
For getting the Email Id, you need to add the scope "https://wwww.googleapis.com/auth/userinfo.email"
Then you will get id_token in the response.
Response={
"access_token" : "ya29.eAG__HY8KahJZN9VmangoliaV-Jn7hLtestkeys",
"token_type" : "Bearer",
"expires_in" : 3600,
"id_token" : "id_token_from_server",
"refresh_token" : "1/GIHTAdMo6zLVKCqNbA"
}
Then use this id_token as below POST request:
https://www.googleapis.com/oauth2/v1/tokeninfo?id_token=id_token_from_server
And you will get response like below:
Response={
"issuer": "accounts.google.com",
"issued_to": "80780.apps.googleusercontent.com",
"audience": "8078909.apps.googleusercontent.com",
"user_id": "1118976557884",
"expires_in": 3598,
"issued_at": 1456353,
"email": "emailId#gmail.com",
"email_verified": true
}
Make sure you add "www" in the APIs as shown above...
OAuth doesn't provide a facility for extra parameters during an OAuth handshake, so I don't think you can force Google to supply it. There is likely a Google API however that you can use your OAuth access token to call to fetch the email address after the handshake, however.
request OAuth scope to include the "Email Display Scope" https://www.googleapis.com/auth/userinfo.email
scope="http://www.google.com/m8/feeds/ https://www.googleapis.com/auth/userinfo.email"
Then use REST API like Hammock to get address
RestClient client = new RestClient
{
Authority = "https://www.googleapis.com",
};
RestRequest request = new RestRequest
{
Path = "userinfo/email?alt=json",
Credentials = OAuthCredentials.ForProtectedResource(
this.requestSettings.ConsumerKey,
this.requestSettings.ConsumerSecret,
this.requestSettings.Token,
this.requestSettings.TokenSecret)
};
var response = client.Request(request);
Here's a c# function for when you have pre-authorized the request as detailed above:
private void FetchUsersEmail(token)
{
var emailRequest = #"https://www.googleapis.com/userinfo/email?alt=json&access_token=" + token;
// Create a request for the URL.
var request = WebRequest.Create(emailRequest);
// Get the response.
var response = (HttpWebResponse) request.GetResponse();
// Get the stream containing content returned by the server.
var dataStream = response.GetResponseStream();
// Open the stream using a StreamReader for easy access.
var reader = new StreamReader(dataStream);
// Read the content.
var jsonString = reader.ReadToEnd();
// Cleanup the streams and the response.
reader.Close();
dataStream.Close();
response.Close();
dynamic json = JValue.Parse(jsonString);
var currentGoogleEmail = json.data.email;
}
(JValue is part of JSON.Net)
In php, apiOauth2Service.php class provides methods to access logged in user info. For this you can use userinfo->get() method. Make sure you also use scope https://www.googleapis.com/auth/userinfo.email.
This will work with same access token. Also you should try looking in other APIs for similar kind of information in return. This is much easier to look through oAuth_playground >> http://code.google.com/apis/explorer/
If you request the userinfo.email scope, Google returns an id_token along with the access_token.
The id_token can be unencrypted to provide the user's email address, at www.googleapis.com?/oauth2/v1/tokeninfo?id_token=IDTOKENHERE
More information here: https://developers.google.com/accounts/docs/OAuth2Login

Resources