logging into d2l lms using valence - valence

I want to authenticate and get a specific course org unit id using valence and java. I have a application id and user key for the application I got from the d2l keytool. I am also using d2l's java client library for authenticating. i.e. com.d2lvalence.idkeyauth.*;
I am getting a http 403 error on the last line of code.
Can someone see what I doing wrong?
URL url = null;
URLConnection connection = null;
String host = "ucbdev.desire2learn.com";
int port = 443;
String appID = "from d2l";
String appKey = "from d2l";
String userId = "";
String userKey = "";
AuthenticationSecurityFactory factory = new AuthenticationSecurityFactory();
// appID and appKey are from d2l
ID2LAppContext appContext = factory.createSecurityContext(appID, appKey);
URI resultUri=new URI("?x_a=fromd2l&x_b=fromd2l");
ID2LUserContext userContext=appContext.createUserContext(resultUri, host, port, true);
if (userContext == null){
System.out.println("USERCONTEXT is NULL");
}
System.out.println("USERCONTEXT HOST NAME IS :"+userContext.getHostName());
userId = userContext.getUserId();
userKey = userContext.getUserKey();
System.out.println("userID is "+userId);
System.out.println("userKey is "+userKey);
URI newUri = userContext.createAuthenticatedUri("/d2l/api/lp/1.0/orgstructure/", "GET");
String res = newUri.toString();
System.out.println("authenticated uri usercontext s "+res);
connection = newUri.toURL().openConnection();
//cast the connection to a HttpURLConnection so we can examin the status code
HttpURLConnection httpConnection = (HttpURLConnection) connection;
httpConnection.setRequestMethod("GET");
StringBuilder sb=new StringBuilder();
BufferedReader in = null;
//if the status code is success then the body is read from the input stream
if(httpConnection.getResponseCode()==200) {
in = new BufferedReader(new InputStreamReader(httpConnection.getInputStream()));
//otherwise the body is read from the output stream
} else {
System.out.println("Error: " + httpConnection.getResponseCode() + ""); //error 403 here
// in = new BufferedReader(new InputStreamReader(httpConnection.getErrorStream()));
}

You do not seem to be clear on how the authentication works for the Valence Learning Framework API.
The AppId/AppKey pair you get back from D2L's KeyTool is the keypair that you'll use to prove that your API call comes from your app (i.e. you pass the AppId in the x_a parameter on a normal call, and you use the AppKey to generate a signature that you then pass in the x_c parameter on the call). But each normal API call also requires user tokens to prove the is being made on behalf of a known user:
All our SDKs work in the same general way:
First you create an application context object that's built using your AppID/Key keypair.
Then, you create an "URL for authentication": this URL will be a call to the special "get user tokens" API call (here the x_a parameter is your AppId, and the x_b parameter is the signature).
You direct the user's browser to go to this URL for authentication, and it's x_target query parameter specifies the callback URL where the LMS should send the user ID/Key pair after it successfully determines who the user is.
Once you have this User ID/Key pair, in subsequent normal API calls, you will pass the User ID in the x_b parameter (as you're passing the App Id in the x_a) and you will use the User Key to make a signature that you will pass in the x_d parameter.
Please follow along the authentication conceptual topic in the docs carefully, as it will show you all the steps involved in the process of your app getting back a UserID/Key pair so you can then use it to make API calls.

Related

Is there a way to avoid using the redirected form in Spring OAuth2 Authorization server when trying to get Authorization code? [duplicate]

I'm trying to create a local Java-based client that interacts with the SurveyMonkey API.
SurveyMonkey requires a long-lived access token using OAuth 2.0, which I'm not very familiar with.
I've been googling this for hours, and I think the answer is no, but I just want to be sure:
Is it possible for me to write a simple Java client that interacts with the SurveyMonkey, without setting up my own redirect server in some cloud?
I feel like having my own online service is mandatory to be able to receive the bearer tokens generated by OAuth 2.0. Is it possible that I can't have SurveyMonkey send bearer tokens directly to my client?
And if I were to set up my own custom Servlet somewhere, and use it as a redirect_uri, then the correct flow would be as follows:
Java-client request bearer token from SurveyMonkey, with
redirect_uri being my own custom servlet URL.
SurveyMonkey sends token to my custom servlet URL.
Java-client polls custom servlet URL until a token is available?
Is this correct?
Yes, it is possible to use OAuth2 without a callback URL.
The RFC6749 introduces several flows. The Implicit and Authorization Code grant types require a redirect URI. However the Resource Owner Password Credentials grant type does not.
Since RFC6749, other specifications have been issued that do not require any redirect URI:
RFC7522: Security Assertion Markup Language (SAML) 2.0 Profile for OAuth 2.0 Client Authentication and Authorization Grants
RFC7523: JSON Web Token (JWT) Profile for OAuth 2.0 Client Authentication and Authorization Grants
RFC8628: OAuth 2.0 Device Authorization Grant
In any case, if the grant types above do not fit on your needs, nothing prevent you from creating a custom grant type.
Not exactly, the whole point of the OAuth flow is that the user (the client you're accessing the data on behalf of) needs to give you permission to access their data.
See the authentication instructions. You need to send the user to the OAuth authorize page:
https://api.surveymonkey.net/oauth/authorize?api_key<your_key>&client_id=<your_client_id>&response_type=code&redirect_uri=<your_redirect_uri>
This will show a page to the user telling them which parts of their account you are requesting access to (ex. see their surveys, see their responses, etc). Once the user approves that by clicking "Authorize" on that page, SurveyMonkey will automatically go to whatever you set as your redirect URI (make sure the one from the url above matches with what you set in the settings for your app) with the code.
So if your redirect URL was https://example.com/surveymonkey/oauth, SurveyMonkey will redirect the user to that URL with a code:
https://example.com/surveymonkey/oauth?code=<auth_code>
You need to take that code and then exchange it for an access token by doing a POST request to https://api.surveymonkey.net/oauth/token?api_key=<your_api_key> with the following post params:
client_secret=<your_secret>
code=<auth_code_you_just_got>
redirect_uri=<same_redirect_uri_as_before>
grant_type=authorization_code
This will return an access token, you can then use that access token to access data on the user's account. You don't give the access token to the user it's for you to use to access the user's account. No need for polling or anything.
If you're just accessing your own account, you can use the access token provided in the settings page of your app. Otherwise there's no way to get an access token for a user without setting up your own redirect server (unless all the users are in the same group as you, i.e. multiple users under the same account; but I won't get into that). SurveyMonkey needs a place to send you the code once the user authorizes, you can't just request one.
You do need to implement something that will act as the redirect_uri, which does not necessarily need to be hosted somewhere else than your client (as you say, in some cloud).
I am not very familiar with Java and Servelets, but if I assume correctly, it would be something that could handle http://localhost:some_port. In that case, the flow that you describe is correct.
I implemented the same flow successfully in C#. Here is the class that implements that flow. I hope it helps.
class OAuth2Negotiator
{
private HttpListener _listener = null;
private string _accessToken = null;
private string _errorResult = null;
private string _apiKey = null;
private string _clientSecret = null;
private string _redirectUri = null;
public OAuth2Negotiator(string apiKey, string address, string clientSecret)
{
_apiKey = apiKey;
_redirectUri = address.TrimEnd('/');
_clientSecret = clientSecret;
_listener = new HttpListener();
_listener.Prefixes.Add(address + "/");
_listener.AuthenticationSchemes = AuthenticationSchemes.Anonymous;
}
public string GetToken()
{
var url = string.Format(#"https://api.surveymonkey.net/oauth/authorize?redirect_uri={0}&client_id=sm_sunsoftdemo&response_type=code&api_key=svtx8maxmjmqavpavdd5sg5p",
HttpUtility.UrlEncode(#"http://localhost:60403"));
System.Diagnostics.Process.Start(url);
_listener.Start();
AsyncContext.Run(() => ListenLoop(_listener));
_listener.Stop();
if (!string.IsNullOrEmpty(_errorResult))
throw new Exception(_errorResult);
return _accessToken;
}
private async void ListenLoop(HttpListener listener)
{
while (true)
{
var context = await listener.GetContextAsync();
var query = context.Request.QueryString;
if (context.Request.Url.ToString().EndsWith("favicon.ico"))
{
context.Response.StatusCode = (int)HttpStatusCode.NotFound;
context.Response.Close();
}
else if (query != null && query.Count > 0)
{
if (!string.IsNullOrEmpty(query["code"]))
{
_accessToken = await SendCodeAsync(query["code"]);
break;
}
else if (!string.IsNullOrEmpty(query["error"]))
{
_errorResult = string.Format("{0}: {1}", query["error"], query["error_description"]);
break;
}
}
}
}
private async Task<string> SendCodeAsync(string code)
{
var GrantType = "authorization_code";
//client_secret, code, redirect_uri and grant_type. The grant type must be set to “authorization_code”
var client = new HttpClient();
client.BaseAddress = new Uri("https://api.surveymonkey.net");
var request = new HttpRequestMessage(HttpMethod.Post, string.Format("/oauth/token?api_key={0}", _apiKey));
var formData = new List<KeyValuePair<string, string>>();
formData.Add(new KeyValuePair<string, string>("client_secret", _clientSecret));
formData.Add(new KeyValuePair<string, string>("code", code));
formData.Add(new KeyValuePair<string, string>("redirect_uri", _redirectUri));
formData.Add(new KeyValuePair<string, string>("grant_type", GrantType));
formData.Add(new KeyValuePair<string, string>("client_id", "sm_sunsoftdemo"));
request.Content = new FormUrlEncodedContent(formData);
var response = await client.SendAsync(request);
if (!response.IsSuccessStatusCode)
{
_errorResult = string.Format("Status {0}: {1}", response.StatusCode.ToString(), response.ReasonPhrase.ToString());
return null;
}
var data = await response.Content.ReadAsStringAsync();
if (data == null)
return null;
Dictionary<string, string> tokenInfo = JsonConvert.DeserializeObject<Dictionary<string, string>>(data);
return(tokenInfo["access_token"]);
}
}

Use HMAC with OData Connected Service

So we want to use a webapi that is build in ASP.net and uses OData as protocol. I did some homework and saw that Microsoft has a very good documenten OData Connected Service. The only thing I can't find is that the webapi we want to use has a HMAC for security. I cannot find an example where the OData Connected Service is used with HMAC. Could someone explain if and how HMAC is possible with the OData Connected Service?
Probably the answer depends on the specific implementation of the HMAC on the server side.
If server receives all the data included in the request along with the Authorization header and extracts the values (APP Id, Signature, Nonce and Request Time stamp) from the Authorization header, then client should:
Build a string by combining all the data that will be sent, this string contains the following parameters (APP Id, HTTP method, request URI, request time stamp, nonce, and Base 64 string representation of the request pay load).
The signature will be sent in the Authorization header using a custom scheme such as ”amx”. The data in the Authorization header will contain the APP Id, request time stamp, and nonce separated by colon ‘:’. The format for the Authorization header will be like: [Authorization: amx APPId:Signature:Nonce:Timestamp].
Client send the request as usual along with the generated data in the Authorization header (just use client hooks or httpclient).
Example (after generating client code):
private string APPId = "65d3a4f0-0239-404c-8394-21b94ff50604";
private string APIKey = "WLUEWeL3so2hdHhHM5ZYnvzsOUBzSGH4+T3EgrQ91KI=";
public async Task<IEnumerable<string>> TestODataHMAC()
{
// add there your OData Uri
var container = new DefaultContainer(new Uri("https://services.odata.org/V4/(S(qc322lduoxrqt13nhydbdcvx))/TripPinServiceRW/"));
container.Configurations.RequestPipeline.OnMessageCreating = (args) =>
{
var request = new HttpWebRequestMessage(args);
// Get the Request URI
string requestUri = HttpUtility.UrlEncode(request.Url.AbsoluteUri.ToLower());
// Calculate UNIX time
var epochStart = new DateTime(1970, 01, 01, 0, 0, 0, 0, DateTimeKind.Utc);
var timeSpan = DateTime.UtcNow - epochStart;
var requestTimeStamp = Convert.ToUInt64(timeSpan.TotalSeconds).ToString();
// Create the random nonce for each request
var nonce = Guid.NewGuid().ToString();
// Creating the raw signature string by combinging
// APPId, request Http Method, request Uri, request TimeStamp, nonce
var signatureRawData = string.Format("{0}{1}{2}{3}{4}", APPId, request.Method, requestUri, requestTimeStamp, nonce);
// Converting the APIKey into byte array
var secretKeyByteArray = Convert.FromBase64String(APIKey);
// Converting the signatureRawData into byte array
var signature = Encoding.UTF8.GetBytes(signatureRawData);
// Generate the hmac signature and set it in the Authorization header
using (HMACSHA256 hmac = new HMACSHA256(secretKeyByteArray))
{
var signatureBytes = hmac.ComputeHash(signature);
var requestSignatureBase64String = Convert.ToBase64String(signatureBytes);
//Setting the values in the Authorization header using custom scheme (hmacauth)
request.SetHeader("Authorization", string.Format("hmacauth {0}:{1}:{2}:{3}", APPId, requestSignatureBase64String, nonce, requestTimeStamp));
}
return request;
};
// add there your OData method call
var nquery = container.People.Where(p => p.Gender == PersonGender.Female).Take(10) as DataServiceQuery<Person>;
var response = await nquery?.ExecuteAsync();
return (response as QueryOperationResponse<Person>).Select(p => p.FirstName).ToArray();
}

How to configure My Web Application as SAML Test Connector (SP) using Onelogin?

I have added my web application into onelogin using SAML Test Connector.
In Configuration tab I have given the following values
Recipient : http://localhost:8080/em/live/pages/samlAuth/
ACS(Consumer) URL Validator* : ^
ACS (Consumer) URL* :http://localhost:8080/ws_em/rest/accounts/consume-saml
Login URL : http://localhost:8080/ws_em/rest/accounts/produce-saml
Where http://localhost:8080/ws_em/rest/accounts/produce-saml creates an SAML Request by taking IssuerUrl, SAML EndPoint Copied From Onelogin SSO Tab and ACS url as http://localhost:8080/ws_em/rest/accounts/consume-saml.
#GET
#Produces(MediaType.APPLICATION_JSON)
#Path("/produce-saml")
public com.virima.em.core.Response SAMLAuthentication(){
com.Response resp = new com.Response();
AppSettings appSettings = new AppSettings();
appSettings.setAssertionConsumerServiceUrl(ACSUrl);
appSettings.setIssuer(IssuerUrl));
AccountSettings accSettings = new AccountSettings();
accSettings.setIdpSsoTargetUrl(IdpSsoTargetUrl);
AuthRequest authReq = new AuthRequest(appSettings,accSettings);
Map<String, String[]> parameters = request.getParameterMap();
String relayState = null;
for(String parameter : parameters.keySet()) {
if(parameter.equalsIgnoreCase("relaystate")) {
String[] values = parameters.get(parameter);
relayState = values[0];
}
}
String reqString = authReq.getSSOurl(relayState);
response.sendRedirect(reqString);
resp.setResponse(reqString);
return resp;
}
http://localhost:8080/ws_em/rest/accounts/consume-saml calls is supposed to take my SAML request and do the authentication . Here I am using the certificate generated in Onelogin SSO Tab
#GET
#Produces(MediaType.APPLICATION_JSON)
#Path("/consume-saml")
public com.onelogin.saml.Response SAMLAuthenticationResponse(){
com.onelogin.saml.Response samlResponse = null;
String certificateS ="c"; //Certificate downloaded from Onelogin SSO Tab
AccountSettings accountSettings = new AccountSettings();
accountSettings.setCertificate(certificateS);
samlResponse = new com.onelogin.saml.Response(accountSettings,request.getParameter("SAMLResponse"),request.getRequestURL().toString());
if (samlResponse.isValid()) {
// the signature of the SAML Response is valid. The source is trusted
java.io.PrintWriter writer = response.getWriter();
writer.write("OK!");
String nameId = samlResponse.getNameId();
writer.write(nameId);
writer.flush();
} else {
// the signature of the SAML Response is not valid
java.io.PrintWriter writer = response.getWriter();
writer.write("Failed\n");
writer.write(samlResponse.getError());
writer.flush();
}
return samlResponse;
}
I am getting this error
Federation Exception: Malformed URL. Please contact your
administrator.
It doesn't seem to come inside the ACS url I have inside my app.
Is there any mistakes in my configuration ? Or is there a better way to do this ?
ACS is Assertion Consumer Service, is the endpoint that process at the SP the SAMLResponse sent by the Identity Provider, so the http://localhost:8080/ws_em/rest/accounts/consume-saml process and validate the SAMLResponse.
Do you have verbose trace error? Malformed URL must be that the code is trying to build a URL var with a non URL string.
BTW, You are using the java-saml toolkit, but the 1.0 version instead the recommended 2.0.
I highly recommend you to use the 2.0 and before work on your integration, try to run the app example

Unity/Android ServerAuthCode has no idToken on Backend

I have an unity app and use the google-play-games plugin with google *.aar versions 9.4.0. I lately changed my Backend (Google App Engine) from php to java. My problem is the following: in php the serverauthcode is used to get the users data (in JWT format) - it was working fine. So I changed to a Java servlet and I am failing since 2 days to get a valid idtoken. I am able to recieve the server auth code from my app and a valid token response is made by GoogleAuthorizationCodeTokenRequest (see code snippet). Unfortunately it does not contain any idtoken content but a valid auth_token. So I can not get the user id to identifiy the user. When I call tokenResponse.parseIdToken(); it is failing with a NullPointerException.
servlet code (authCode is the serverAuthCode I send from the play-games-plugin inside Unity to my GAE):
// (Receive authCode via HTTPS POST)
// Set path to the Web application client_secret_*.json file you downloaded from the
// Google Developers Console: https://console.developers.google.com/apis/credentials?project=_
// You can also find your Web application client ID and client secret from the
// console and specify them directly when you create the GoogleAuthorizationCodeTokenRequest
// object.
String CLIENT_SECRET_FILE = "/mypath/client_secret.json";
// Exchange auth code for access token
GoogleClientSecrets clientSecrets =
GoogleClientSecrets.load(
JacksonFactory.getDefaultInstance(), new FileReader(CLIENT_SECRET_FILE));
GoogleTokenResponse tokenResponse =
new GoogleAuthorizationCodeTokenRequest(
new NetHttpTransport(),
JacksonFactory.getDefaultInstance(),
clientSecrets.getDetails().getTokenUri(),
clientSecrets.getDetails().getClientId(),
clientSecrets.getDetails().getClientSecret(),
authCode,
REDIRECT_URI) // Specify the same redirect URI that you use with your web
// app. If you don't have a web version of your app, you can
// specify an empty string.
.execute();
String accessToken = tokenResponse.getAccessToken();
// Get profile info from ID token -> HERE IT THROWS AN EXCEPTION.
GoogleIdToken idToken = tokenResponse.parseIdToken();
GoogleIdToken.Payload payload = idToken.getPayload();
String userId = payload.getSubject(); // Use this value as a key to identify a user.
String email = payload.getEmail();
boolean emailVerified = Boolean.valueOf(payload.getEmailVerified());
String name = (String) payload.get("name");
String pictureUrl = (String) payload.get("picture");
String locale = (String) payload.get("locale");
String familyName = (String) payload.get("family_name");
String givenName = (String) payload.get("given_name");
the token response looks like (its invalid now):
{
"access_token" : "ya29.CjA8A7O96w-vX4OCSPm-GMEPGVIEuRTeOxKy_75z6fbYVSXsdi9Ot3NmxlE-j_t-BI",
"expires_in" : 3596,
"token_type" : "Bearer"
}
In my PHP GAE I always had a idToken inside this constuct which contained my encrypted data. But it is missing now?! So I asssume I do somthing differently in Java or I made a mistake creating the new OAuth 2.0 Client on the google console.
I checked the accessToken manually via:
https://www.googleapis.com/oauth2/v1/tokeninfo?access_token=ya29.CjA8A7O96w-vX4OCSPm-GMEPGVIEu-RTeOxKy_75z6fbYVSXsdi9Ot3NmxlE-j_t-BI
{
"issued_to": "48168146---------.apps.googleusercontent.com",
"audience": "48168146---------.apps.googleusercontent.com",
"scope": "https://www.googleapis.com/auth/games_lite",
"expires_in": 879,
"access_type": "offline"
}
Is there something I do not see? Help is very much appreciated...
I found a root cause discussion inside the unity plugin "play-games-services" on github:
https://github.com/playgameservices/play-games-plugin-for-unity/issues/1293
and
https://github.com/playgameservices/play-games-plugin-for-unity/issues/1309
It seems that google switching their authentication flow. In the given links they are talking about adding the email scope inside the plugin to get the idtoken again. I'll try that in the next days and share my experience.
Here is a good explaination about what happens:
http://android-developers.blogspot.de/2016/01/play-games-permissions-are-changing-in.html
If you do what paulsalameh said here (Link to Github) it will work again:
paulsalameh: Sure. After you import the unitypackage, download NativeClient.cs and
PlayGamesClientConfig.cs from my commits (#1295 & #1296), and replace
them in the correct locations.
Afte that "unity play-services-plugin" code update you will be able to add AddOauthScope("email") to PlayGamesClientConfiguration, which allows your server to get the idtoken with the serverAuthCode again...
Code snippet from Unity:
PlayGamesClientConfiguration config = new PlayGamesClientConfiguration.Builder()
.AddOauthScope("email")
.AddOauthScope("profile")
.Build();
Now I am back in business:
{
"access_token" : "ya29.Ci8..7kBR-eBdPw1-P7Pe8QUC7e_Zv7qxCHA",
"expires_in" : 3600,
"id_token" : "eyJhbGciOi......I1NiE0v6kqw",
"refresh_token" : "1/HlSZOo......dQV1y4E",
"token_type" : "Bearer"
}

Google JWT Invalid Signature from Salesforce

I am trying to authenticate via a service account from Salesforce.com to Google's DFP. I had the integration working under a previous user/credential pair, but am required to update to a new user.
I created the project/user/key pair in the Google Developer Console and added the new service account to the network in DFP. I then changed the "iss" value to be the new user's email and the private key to be the new private key from the keypair.
I am now receiving an 'Invalid Signature' error.
In SFDC, I am using Crypto.sign method with RSA-SHA256.
https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_classes_restful_crypto.htm#apex_System_Crypto_sign
I have validated the key format to be PKCS#8 with header and new line characters removed per the documentation (I went so far as to decode the ASN.1 format and inspect the nodes for conformity).
Have I missed a step in the connection between the user and the correct credential? Is there a way for me to validate the signature that I am producing locally to see where I am going wrong? The only difference I have seen is that the old private key was shorter than the current private key.
Below is the code I am using to generate the JWT (again, this code functioned properly with a different username and credential key).
JWTHeader head = new JWTHeader();
head.alg = 'RS256';
head.typ = 'JWT';
JWTClaimSet claim = new JWTClaimSet();
claim.iss = '<username>#*.iam.gserviceaccount.com';
claim.scope = 'https://www.googleapis.com/auth/dfp';
claim.aud = 'https://accounts.google.com/o/oauth2/token';
claim.iat = DateTime.now().getTime() / 1000;
claim.exp = claim.iat + 3600;
System.debug(JSON.serialize(head));
System.debug(JSON.serialize(claim));
String key = '<privatekey>’;
String base = EncodingUtil.urlEncode(EncodingUtil.base64Encode(Blob.valueOf(JSON.serialize(head))), 'UTF-8') + '.' + EncodingUtil.urlEncode(EncodingUtil.base64Encode(Blob.valueOf(JSON.serialize(claim))), 'UTF-8');
String sig = EncodingUtil.urlEncode(EncodingUtil.base64Encode(Crypto.sign('RSA-SHA256', Blob.valueOf(base), EncodingUtil.base64Decode(key))), 'UTF-8');
String body = base + '.' + sig;
System.debug(body);
Http http = new Http();
HttpRequest req = new HttpRequest();
req.setEndpoint('https://accounts.google.com/o/oauth2/token');
req.setBody('grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer&assertion=' + body);
req.setHeader('Content-Type', 'application/x-www-form-urlencoded');
req.setMethod('POST');
HttpResponse resp = http.send(req);
Days later I found another solution that solved the issue. The problem was the base64urlsafe encoding. This encoding is not natively done in SFDC and perscribes the removal of trailing padding characters from the base64 string. Luckily, my original username encoded with no padding characters in the claim set. With the new username, the padding characters are present and must be removed before signing.
It all comes down to just a few characters.

Resources