I'm trying to pull google contacts information (photos, gender and more) and we used Google People API to do that.
I use in createContact service to search contact by an Email.
I created a client id and client secret, and with the simple code of oAuth2.0, I got a refresh token that my server used to generate credentials.
HttpTransport httpTransport = new NetHttpTransport();
JacksonFactory jsonFactory = new JacksonFactory();
String clientId = "";
String clientSecret = "";
String scope = "https://www.googleapis.com/auth/contacts";
GoogleAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow.Builder(
httpTransport, jsonFactory, clientId, clientSecret,
Arrays.asList(scope))
.setAccessType("offline")
.setApprovalPrompt("force")
.build();
LocalServerReceiver localReceiver = new LocalServerReceiver.Builder().setPort(8089).build();
Credential credential = new AuthorizationCodeInstalledApp(flow, localReceiver).authorize("user");
In that way, I get the refreshToken and save it offline in a secured file.
After that, my application connect to google and get the clientId, ClientToken, and RefreshToken from that file and try to connect -
GoogleCredential googleCredential = new GoogleCredential.Builder()
.setJsonFactory(jsonFactory)
.setTransport(transport)
.setClientSecrets(googlePeopleKey.getClientId(), googlePeopleKey.getClientSecret())
.build().setRefreshToken(googlePeopleKey.getRefreshToken());
It seems that the result is different per user. When I operate with a refresh code that my user authenticate earlier, I can't get all the information that another user in my company (that authorized by the same way) does. Today, it flipped, and in another test, I get results that he didn't. Very strange.
I was looking up about limit rate but, it seems that I even not near the limitations.
Do you have any idea why it returns different result for different users? The different result means that sometimes one of the users can see the profile picture but the other one can't.
Thank you!
Related
We are planning to move from Organization Service to Common Data Service Web API so we could utilize OAuth 2.0 authentication instead of a service account which customer has some security concerns.
Once we did some prototype, we discovered that the Web API authentication is a little different from typical Graph API authentication. It only supports Delegated Permission. Thus a user credential must be presented for acquiring the access token.
Here is the Azure AD Graph API permission for CRM Web API:
Here is the code in acquiring the access token for the sample code at Web API Global Discovery Service Sample (C#)
string GlobalDiscoUrl = "https://globaldisco.crm.dynamics.com/";
AuthenticationContext authContext = new AuthenticationContext("https://login.microsoftonline.com", false);
UserCredential cred = new UserCredential(username, password);
AuthenticationResult authResult = authContext.AcquireToken(GlobalDiscoUrl, clientId, cred);
Here is another similar post Connect to Dynamics 365 Customer Engagement web services using OAuth although it is more than one year old.
Do you know when MS would support Application permission to completely eliminate the user from authentication? Or there is any particular reason to keep the user here. Thanks for any insights.
[Update 1]
With below answer from James, I did the modification for the code, here is my code
string clientId = "3f4b24d8-61b4-47df-8efc-1232a72c8817";
string secret = "xxxxx";
ClientCredential cred = new ClientCredential(clientId, secret);
string GlobalDiscoUrl = "https://globaldisco.crm.dynamics.com/";
AuthenticationContext authContext = new AuthenticationContext("https://login.microsoftonline.com/common", false);
AuthenticationResult authResult = authContext.AcquireToken(GlobalDiscoUrl, cred);
HttpClient client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", authResult.AccessToken);
client.Timeout = new TimeSpan(0, 2, 0);
client.BaseAddress = new Uri(GlobalDiscoUrl);
HttpResponseMessage response = client.GetAsync("api/discovery/v1.0/Instances", HttpCompletionOption.ResponseHeadersRead).Result;
if (response.IsSuccessStatusCode)
{
//Get the response content and parse it.
string result = response.Content.ReadAsStringAsync().Result;
JObject body = JObject.Parse(result);
JArray values = (JArray)body.GetValue("value");
if (!values.HasValues)
{
return new List<Instance>();
}
return JsonConvert.DeserializeObject<List<Instance>>(values.ToString());
}
else
{
throw new Exception(response.ReasonPhrase);
}
}
so I am able to acquire the access token, but it still could not access the global discovery services.
Here is what the access token looks like:
{
"aud": "https://globaldisco.crm.dynamics.com/",
"iss": "https://sts.windows.net/f8cdef31-a31e-4b4a-93e4-5f571e91255a/",
"iat": 1565802457,
"nbf": 1565802457,
"exp": 1565806357,
"aio": "42FgYEj59uDNtwvxTLnprU0NYt49AA==",
"appid": "3f4b24d8-61b4-47df-8efc-1232a72c8817",
"appidacr": "1",
"idp": "https://sts.windows.net/f8cdef31-a31e-4b4a-93e4-5f571e91255a/",
"tid": "f8cdef31-a31e-4b4a-93e4-5f571e91255a",
"uti": "w8uwKBSPM0y7tdsfXtAgAA",
"ver": "1.0"
}
By the way, we did already create the application user inside CRM by following the instruction.
Anything I am missing here?
[Update 2]
For WhoAmI request, there are different results. If I am using latest MSAL and with authority "https://login.microsoftonline.com/AzureADDirectoryID/oauth2/authorize", I would be able to get the correct result. If I am using MSAL with "https://login.microsoftonline.com/common/oauth2/authorize", it won't work, I would get unauthorized error. If I am using ADAL 2.29, it is not working for both authority. Here is the working code:
IConfidentialClientApplication app = ConfidentialClientApplicationBuilder.Create("3f4b24d8-61b4-47df-8efc-1232a72cxxxx")
.WithClientSecret("xxxxxx")
// .WithAuthority("https://login.microsoftonline.com/common/oauth2/authorize", false)
.WithAuthority("https://login.microsoftonline.com/3a984a19-7f55-4ea3-a422-2d8771067f87/oauth2/authorize", false)
.Build();
var authResult = app.AcquireTokenForClient(new String[] { "https://crmxxxxx.crm5.dynamics.com/.default" }).ExecuteAsync().Result;
HttpClient client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", authResult.AccessToken);
client.Timeout = new TimeSpan(0, 2, 0);
client.BaseAddress = new Uri("https://crm525842.api.crm5.dynamics.com/");
HttpResponseMessage response = client.GetAsync("api/data/v9.1/WhoAmI()", HttpCompletionOption.ResponseHeadersRead).Result;
if (response.IsSuccessStatusCode)
{
//Get the response content.
string result = response.Content.ReadAsStringAsync().Result;
Console.WriteLine(result);
}
else
{
throw new Exception(response.ReasonPhrase);
}
The documentation isn't the easiest to follow, but from what I understand you should start with Use OAuth with Common Data Service.
You then have two subtle options when registering your app. The second does not require the Access Dynamics 365/Common Data Service as organization users permission
Giving access to Common Data Service
If your app will be a client which allows the authenticated user to
perform operations, you must configure the application to have the
Access Dynamics 365 as organization users delegated permission.
Or
If your app will use Server-to-Server (S2S) authentication, this step
is not required. That configuration requires a specific system user
and the operations will be performed by that user account rather than
any user that must be authenticated.
This is elaborated further.
Connect as an app
Some apps you will create are not intended to be run interactively by
a user. ... In these cases you can create a special application user
which is bound to an Azure Active Directory registered application and
use either a key secret configured for the app or upload a X.509
certificate. Another benefit of this approach is that it doesn't
consume a paid license.
Register your app
When registering an app you follow many of the same steps ... with the
following exceptions:
You do not need to grant the Access Dynamics 365 as organization users permission.
You will still have a system user record in Dynamics to represent the application registration. This supports a range of basic Dynamics behaviours and allows you to apply Dynamics security to you app.
As opposed to a username and password you can then use the secret to connect.
string serviceUrl = "https://yourorg.crm.dynamics.com";
string clientId = "<your app id>";
string secret = "<your app secret>";
AuthenticationContext authContext = new AuthenticationContext("https://login.microsoftonline.com/common", false);
ClientCredential credential = new ClientCredential(clientId, secret);
AuthenticationResult result = authContext.AcquireToken(serviceUrl, credential);
string accessToken = result.AccessToken;
Or a certificate.
string CertThumbPrintId = "DC6C689022C905EA5F812B51F1574ED10F256FF6";
string AppID = "545ce4df-95a6-4115-ac2f-e8e5546e79af";
string InstanceUri = "https://yourorg.crm.dynamics.com";
string ConnectionStr = $#"AuthType=Certificate;
SkipDiscovery=true;url={InstanceUri};
thumbprint={CertThumbPrintId};
ClientId={AppID};
RequireNewInstance=true";
using (CrmServiceClient svc = new CrmServiceClient(ConnectionStr))
{
if (svc.IsReady)
{
...
}
}
You may also want to check out Build web applications using Server-to-Server (S2S) authentication which appears to be a similar (but different).
Use server-to-server (S2S) authentication to securely and seamlessly
communicate with Common Data Service with your web applications and
services. S2S authentication is the common way that apps registered on
Microsoft AppSource use to access the Common Data Service data of
their subscribers. ... Rather than user credentials, the application is authenticated based on a service principal identified by an Azure AD Object ID value which is stored in the application user record.
Aside; if you are currently using the Organization Service .NET object, that is being migrated to using the Web API internally.
Microsoft Dynamics CRM 2011 endpoint
The Dynamics 365 SDK assemblies will be updated to use the Web API.
This update will be fully transparent to you and any code written
using the SDK itself will be supported.
I'm trying to find a contact by his email using Google People API.
We are using the Create Contact method.
I'm run this method with all the relevant scopes and using oAuth2.0 with refresh token.
HttpTransport transport = GoogleNetHttpTransport.newTrustedTransport();
JacksonFactory jsonFactory = JacksonFactory.getDefaultInstance();
GoogleCredential googleCredential = new GoogleCredential.Builder()
.setJsonFactory(jsonFactory)
.setTransport(transport)
.setClientSecrets(googlePeopleKey.getClientId(), googlePeopleKey.getClientSecret())
.build().setRefreshToken(googlePeopleKey.getRefreshToken());
this.peopleService = new PeopleService.Builder(transport, jsonFactory, googleCredential)
.setApplicationName(APPLICATION_NAME)
.build();
When we use the Create Contact method - Google search for the given email in the system (google plus and more tools) and enrich the contact with an existing data about the user such as photos, gender, profiles and a lot more.
public Person createContact(String email) {
Person requiredPerson = new Person();
EmailAddress emailAddress = new EmailAddress();
emailAddress.set("value", email);
requiredPerson.setEmailAddresses(Arrays.asList(emailAddress));
try {
PeopleService.People.CreateContact createContact = peopleService.people().createContact(requiredPerson);
return createContact.execute();
} catch (IOException e) {
log.error("Could not perform Google people search for email {}", email, e);
}
return null;
}
But, I struggled to get all the information about the users.
We have limitations of 90 calls per second but our system not near this limit.
With two different users, it seems like after 40-50 calls the API start to return empty contact (without photos, gender and more..)
Although the next day it found all the data about a specific person that the day before it returns me fewer data.
I know we have limitations of 25,000 contacts per day but we are not near this limit too.
I can really use your help to find why after 40/50 new contact, google people API start to create the contact without retrieving all the possible data in google systems.
Thank you!
I am new to azure, tokens and so on...
I have "digged" microsoft documentation and google and stackoverflow, but still didn't get full understanding.
So I using openId with Owin library to connect to azure from web app(VS2013 .net 4.5.1). And I have next code to do it:
public void Configuration(IAppBuilder app)
{
app.SetDefaultSignInAsAuthenticationType(
CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions());
app.UseOpenIdConnectAuthentication(
new OpenIdConnectAuthenticationOptions
{
MetadataAddress = String.Format(aadInstance, tenant, policy),
AuthenticationType = policy,
ClientId = clientId,
RedirectUri = redirectUri,
PostLogoutRedirectUri = redirectUri,
Notifications = new OpenIdConnectAuthenticationNotifications
{
AuthenticationFailed = AuthenticationFailed
,SecurityTokenValidated = OnSecurityTokenValidated
,AuthorizationCodeReceived = OnAuthorizationCodeReceived
,SecurityTokenReceived = OnSecurityTokenReceived
},
Scope = "openid profile",
ResponseType = "id_token"
};
);
}
private Task OnSecurityTokenValidated(SecurityTokenValidatedNotification<OpenIdConnectMessage, OpenIdConnectAuthenticationOptions> notification)
{
var identity = notification.AuthenticationTicket.Identity;
var claims = notification.OwinContext.Authentication.User.Claims;
ClaimsPrincipal.Current.AddIdentity(identity);
return Task.FromResult(0);
}
And it is working, but in microsoft documentation I found next instruction "Currently, ID tokens are signed but not encrypted. When your app receives an ID token, it must validate the signature to prove the token's authenticity and validate a few claims in the token to prove its validity. The claims validated by an app vary depending on scenario requirements, but your app must perform some common claim validations in every scenario."
But there is SecurityTokenValidated-callback , which have AuthenticationTicket. So do I still need to somehow validate token/ticked or now it is handled automatically (I been tough in army that nothing happening automatically, but still)?
The library that you are using handles the validation for you.
It will check the signature is what it should be based on the keys provided by Azure AD.
So you don't need to do manual checks, other than your app's specific checks. For example, an app might allow only members of a certain group to access the app. You would need to do that check if that is the case.
Presently i am working on google classroom API to integrate classroom into my .NET product.My problem is when i execute this method it asking authentication for first time but when i execute this code next time it directly log in as previous log in credentials.Even i change the client_secret.json of another(2nd) domain also it directly login as 1st domain authenticated user.My requirement is when i change the client_secret.json file dynamically in code at run time it will directly log in as domain user of this client_secret.json file rather than previous domain user of client_secret.json file.Is this possible?
If yes How can i achieve this.Please any one help on this.
private ClassroomService getservice()
{
using (var stream =
new FileStream(Server.MapPath("client_secret.json"), FileMode.Open, FileAccess.Read))
{
string credPath = System.Environment.GetFolderPath(
System.Environment.SpecialFolder.Personal);
credPath = Path.Combine(credPath, ".credentials");
credential = GoogleWebAuthorizationBroker.AuthorizeAsync(
GoogleClientSecrets.Load(stream).Secrets,
Scopes,
"user",
CancellationToken.None,
new FileDataStore(credPath, true)).Result;
}
var service = new ClassroomService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = ApplicationName,
});
return service;k
}
The client_secret.json file holds application-specific credentials, not user- or domain-specific credentials. Changing the client_secret.json file is not the correct way to log in/out users. Instead, you'll need to use a FileDataStore with a different path.
How do I revoke access that has been granted to my Google Drive web application so that upon the user's next use he is asked for permissions afresh?
For revoking your access token, you need to "GET" (!) this url:
https://accounts.google.com/o/oauth2/revoke?token={token}
where {token} is the value of your token, as explained here:
https://developers.google.com/accounts/docs/OAuth2WebServer#tokenrevoke
For Java API (don't know for other languages), as of 9th of Sept 2012, there is no API for this.
I managed to revoke a token with this code:
class myGoogleApi {
private static final HttpTransport HTTP_TRANSPORT = new NetHttpTransport();
...
public revoke(String token) {
HttpRequestFactory factory = HTTP_TRANSPORT.createRequestFactory();
GoogleUrl url = new GoogleUrl("https://accounts.google.com/o/oauth2/revoke?token="+token);
HttpRequest request = factory.buildGetRequest(url);
HttpResponse response = request.execute();
...
}
If you clobbered all the refresh tokens in your DB, adding the query parameter approval_prompt=force to the auth request will fix that. It'll result in the refresh tokens getting reissued when the user next approves the request.
In order to revoke the access go to the below url
https://security.google.com/settings/security/permissions?pli=1
Choose your apps that you need to revoke and click on remove.
Visit https://accounts.google.com/b/0/IssuedAuthSubTokens?hl=en for the list of applications and sites that you granted access to. Next to each of them you'll find a Revoke Access button.
The instructions to get to that page are at http://support.google.com/accounts/bin/answer.py?hl=en&answer=41236
Using Google Play Services:
http://developer.android.com/reference/com/google/android/gms/auth/GoogleAuthUtil.html
Add https://www.googleapis.com/auth/userinfo.profile to your scope.
Example:
String scope="oauth2:https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile"
final String token = GoogleAuthUtil.getToken(context, "xxxx#gmail.com", scope);
OR "brute force"
Intent res = new Intent();
res.addCategory("account:xxxx#gmail.com");
res.addCategory("scope:oauth2:https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile");
res.putExtra("service", "oauth2:https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile");
Bundle extra= new Bundle();
extra.putString("androidPackageName","com.your.package");
res.putExtra("callerExtras",extra);
res.putExtra("androidPackageName","com.your.package");
res.putExtra("authAccount","xxxx#gmail.com");
String mPackage = "com.google.android.gms";
String mClass = "com.google.android.gms.auth.TokenActivity";
res.setComponent(new ComponentName(mPackage,mClass));
startActivityForResult(res,100);
Now, when you revoke the access here https://accounts.google.com/IssuedAuthSubTokens the application shows you the window for permission again in the device.