I am trying to do an APEX Trigger Callout using POST to Google Cloud. The problem I have is: whenever the trigger happened, I see the authentication is happening fine, but message is coming as "Undefined" only from Stackdriver logs, hence the cloud function will fail since the message should be in JSON format. Not sure why it is happening. I am pretty much new to Salesforce. Please provide some hints on this.
If I test the cloud function directly (Using Testing option inside cloud function), I see the insert to BigQuery table as expected, but from Salesforce trigger it seems, the message is not being captured properly. PFB Codes used in Salesforce.Expecting the same message which we are inserting into LEAD table in Salesforce as Rest API request
Salesforce Class:
Public class Callout {
#future(callout=true)
Public static void httpcallout(){
Lead c = [Select Name from Lead Limit 1] ;
system.debug('Halo Trigger');
JSONGenerator gen = JSON.createGenerator(true);
gen.writeStartObject();
gen.writeStringField('Name', c.Name);
gen.writeEndObject();
String jsonS = gen.getAsString();
System.debug('jsonMaterials'+jsonS);
String endpoint = 'https://us-central1-valid-weaver- 235212.cloudfunctions.net/Salesforce-GCP';
HttpRequest req = new HttpRequest();
req.setEndpoint(endpoint);
req.setMethod('POST');
req.setbody(jsonS);
Http http = new Http();
HTTPResponse response = http.send(req);
}
}
Salesforce Trigger
trigger SFGCP on Lead (after insert) {
callout.httpcallout();
system.debug('Hello');
}
Debug from Salesforce
Lead c = new Lead(Company ='Test',LastName='Admin');
insert c;
Below code works fine with some minor change in Cloud Function . Thanks
Public class Callout {
#future(callout=true)
Public static void httpcallout()
{
Lead c = [select Name, LeadSource, Company from Lead order by createdDate DESC limit 1] ;
system.debug('Halo Trigger');
JSONGenerator gen = JSON.createGenerator(true);
gen.writeStartObject();
gen.writeStringField('Name', c.Name);
gen.writeStringField('LeadSource', c.LeadSource);
gen.writeStringField('Company', c.Company);
gen.writeEndObject();
String jsonS = gen.getAsString();
System.debug('jsonMaterials'+jsonS);
String endpoint = 'https://us-central1-valid-weaver-235212.cloudfunctions.net /Salesforce-GCP';
HttpRequest req = new HttpRequest();
req.setEndpoint(endpoint);
req.setMethod('POST');
req.setbody(jsonS);
Http http = new Http();
HTTPResponse response = http.send(req);
}
}
Related
This relates to my earlier question - How to iterate/loop through next pages in an API request in PowerQuery/PowerBI? ; which was resolved using below code:
//Declare base variables
let
BaseURL = "https://api.aaaaaa.com",
Entity = "/api/v1/user?&limit=1000",
Token = "zzzzzzzzzzzzzzzzzzzzzzzzzzzz",
Options = [Headers=[APITOKEN=Token]],
URL = BaseURL & Entity,
//Define a function that would take step/page as parameter and return results
GetData=(page as number) =>
let
Source = Json.Document(Web.Contents(URL & "&step=" & Number.ToText(page), Options)),
Data = try Source[results] otherwise null
in
Data,
//Iterate over GetData () to return all the records until last page i.e. until no "result" is retrieved from the API call
GeneratePageList =
List.Generate( ()=>
[Result = try GetData(1) otherwise null, Page=1],
each [Result] <> null,
each [Result = try GetData([Page]+1) otherwise null, Page=[Page]+1],
each [Result]
)
in
GeneratePageList
However, once this code is published to PowerBI service, we cannot schedule refresh for it, since it gives below error as:-
This dataset includes a dynamic data source. Since dynamic data sources aren't refreshed in the Power BI service, this dataset won't be refreshed. Learn more: https://aka.ms/dynamic-data-sources.
• Data source for Query1Discover Data Sources
Tried RelativePath & Query method as suggested here - https://blog.crossjoin.co.uk/2016/08/16/using-the-relativepath-and-query-options-with-web-contents-in-power-query-and-power-bi-m-code/ and here-
https://blog.crossjoin.co.uk/2019/04/25/skip-test-connection-power-bi-refresh-failures/
But, without any luck, see below how am using it:
let
BaseURL = "https://api.crewhu.com",
Entity = "/api/v1/user?&limit=1000&step=",
Token = "60afbdaf5d7d584762771f36",
Options = [Headers=[X_CREWHU_APITOKEN=Token]],
URL = BaseURL & Entity,
//Define a function that would take step/page as parameter and return results
GetData=(page as number) =>
let
Source = Json.Document(Web.Contents(BaseURL & [RelativePath = Entity, Query=[page]], Options)),
The BaseURL is reachable; but redirects to the login page, where our admin credentials (username+password) on the vendor site works well. However, same credentials do not work when using "Basic" connection method during accessing Web Content. Therefore, tried adding #Authorization = Basic in the header along with API key like - [Headers=[Authorization = Basic, X_CREWHU_APITOKEN=Token]]; but this also didn't work.
We've only got an Open API token/key from the vendor; but even that token/key also doesn't work from when providing that in "Web API" section during connecting/accessing Web Content, it gives error as:- "a web api key can only be specified when a web api key name is provided", but the same key/token works well from within PowerQuery (M) code using anonymous web api call method.
Have tried multiple permutation combinations of providing key/token in the username/password fields as suggested in some sites, but still no luck.
I am having some problems attempting to post to an API gateway endpoint.
On my API gateway I have my gateway all set up, and tested via the tool and am getting results and can verify that the step function is in fact executing the request appropriately.
{
"executionArn": "arn:aws:states:us-east-2:xxxxxxxxxxxx:execution:DevStateMachine-XXXXXXXXXXX:c9047982-e7f8-4b72-98d3-281db0eb4c30",
"startDate": 1531170720.489
}
I have set up a Stage for this for my dev environment and all looks good there as well. where I am given a URL to post against.
https://xxxxxxxxxx.execute-api.us-east-2.amazonaws.com/dev/assignments
In my c# code I have the web client defined as follows:
public Guid QueueAssignment(AssignmentDTO assignment)
{
using (var client = new HttpClient())
{
var data = JsonConvert.SerializeObject(assignment);
var content = new StringContent(data);
var uri = "https://xxxxxxxxxx.execute-api.us-east-2.amazonaws.com/dev/assignments"
content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
var response = client.PostAsync(uri, content).Result;
if (response.IsSuccessStatusCode)
{
_logger.Info("Successfully posted to AWS Step Function");
_logger.Info(response);
}
else
_logger.Error("Error posting to AWS Step Function");
_logger.Error(response);
}
}
Everytime this post is attempted I get the following error:
System.Net.WebException: The remote name could not be resolved: 'https://xxxxxxxxxx.execute-api.us-east-2.amazonaws.com'
Is there something I am missing in posting to this URI or some type of conversion I need to do? Im kind of at a loss on where to go on this on.
Identity server is implemented and working well. Google login is working and is returning several claims including email.
Facebook login is working, and my app is live and requests email permissions when a new user logs in.
The problem is that I can't get the email back from the oauth endpoint and I can't seem to find the access_token to manually request user information. All I have is a "code" returned from the facebook login endpoint.
Here's the IdentityServer setup.
var fb = new FacebookAuthenticationOptions
{
AuthenticationType = "Facebook",
SignInAsAuthenticationType = signInAsType,
AppId = ConfigurationManager.AppSettings["Facebook:AppId"],
AppSecret = ConfigurationManager.AppSettings["Facebook:AppSecret"]
};
fb.Scope.Add("email");
app.UseFacebookAuthentication(fb);
Then of course I've customized the AuthenticateLocalAsync method, but the claims I'm receiving only include name. No email claim.
Digging through the source code for identity server, I realized that there are some claims things happening to transform facebook claims, so I extended that class to debug into it and see if it was stripping out any claims, which it's not.
I also watched the http calls with fiddler, and I only see the following (apologies as code formatting doesn't work very good on urls. I tried to format the querystring params one their own lines but it didn't take)
(facebook.com)
/dialog/oauth
?response_type=code
&client_id=xxx
&redirect_uri=https%3A%2F%2Fidentity.[site].com%2Fid%2Fsignin-facebook
&scope=email
&state=xxx
(facebook.com)
/login.php
?skip_api_login=1
&api_key=xxx
&signed_next=1
&next=https%3A%2F%2Fwww.facebook.com%2Fv2.7%2Fdialog%2Foauth%3Fredirect_uri%3Dhttps%253A%252F%252Fidentity.[site].com%252Fid%252Fsignin-facebook%26state%3Dxxx%26scope%3Demail%26response_type%3Dcode%26client_id%3Dxxx%26ret%3Dlogin%26logger_id%3Dxxx&cancel_url=https%3A%2F%2Fidentity.[site].com%2Fid%2Fsignin-facebook%3Ferror%3Daccess_denied%26error_code%3D200%26error_description%3DPermissions%2Berror%26error_reason%3Duser_denied%26state%3Dxxx%23_%3D_
&display=page
&locale=en_US
&logger_id=xxx
(facebook.com)
POST /cookie/consent/?pv=1&dpr=1 HTTP/1.1
(facebook.com)
/login.php
?login_attempt=1
&next=https%3A%2F%2Fwww.facebook.com%2Fv2.7%2Fdialog%2Foauth%3Fredirect_uri%3Dhttps%253A%252F%252Fidentity.[site].com%252Fid%252Fsignin-facebook%26state%3Dxxx%26scope%3Demail%26response_type%3Dcode%26client_id%3Dxxx%26ret%3Dlogin%26logger_id%3Dxxx
&lwv=100
(facebook.com)
/v2.7/dialog/oauth
?redirect_uri=https%3A%2F%2Fidentity.[site].com%2Fid%2Fsignin-facebook
&state=xxx
&scope=email
&response_type=code
&client_id=xxx
&ret=login
&logger_id=xxx
&hash=xxx
(identity server)
/id/signin-facebook
?code=xxx
&state=xxx
I saw the code parameter on that last call and thought that maybe I could use the code there to get the access_token from the facebook API https://developers.facebook.com/docs/facebook-login/manually-build-a-login-flow
However when I tried that I get a message from the API telling me the code has already been used.
I also tried to change the UserInformationEndpoint to the FacebookAuthenticationOptions to force it to ask for the email by appending ?fields=email to the end of the default endpoint location, but that causes identity server to spit out the error "There was an error logging into the external provider. The error message is: access_denied".
I might be able to fix this all if I can change the middleware to send the request with response_type=id_token but I can't figure out how to do that or how to extract that access token when it gets returned in the first place to be able to use the Facebook C# sdk.
So I guess any help or direction at all would be awesome. I've spent countless hours researching and trying to solve the problem. All I need to do is get the email address of the logged-in user via IdentityServer3. Doesn't sound so hard and yet I'm stuck.
I finally figured this out. The answer has something to do with Mitra's comments although neither of those answers quite seemed to fit the bill, so I'm putting another one here. First, you need to request the access_token, not code (authorization code) from Facebook's Authentication endpoint. To do that, set it up like this
var fb = new FacebookAuthenticationOptions
{
AuthenticationType = "Facebook",
SignInAsAuthenticationType = signInAsType,
AppId = ConfigurationManager.AppSettings["Facebook:AppId"],
AppSecret = ConfigurationManager.AppSettings["Facebook:AppSecret"],
Provider = new FacebookAuthenticationProvider()
{
OnAuthenticated = (context) =>
{
context.Identity.AddClaim(new System.Security.Claims.Claim("urn:facebook:access_token", context.AccessToken, ClaimValueTypes.String, "Facebook"));
return Task.FromResult(0);
}
}
};
fb.Scope.Add("email");
app.UseFacebookAuthentication(fb);
Then, you need to catch the response once it's logged in. I'm using the following file from the IdentityServer3 Samples Repository, which overrides (read, provides functionality) for the methods necessary to log a user in from external sites. From this response, I'm using the C# Facebook SDK with the newly returned access_token claim in the ExternalAuthenticationContext to request the fields I need and add them to the list of claims. Then I can use that information to create/log in the user.
public override async Task AuthenticateExternalAsync(ExternalAuthenticationContext ctx)
{
var externalUser = ctx.ExternalIdentity;
var claimsList = ctx.ExternalIdentity.Claims.ToList();
if (externalUser.Provider == "Facebook")
{
var extraClaims = GetAdditionalFacebookClaims(externalUser.Claims.First(claim => claim.Type == "urn:facebook:access_token"));
claimsList.Add(new Claim("email", extraClaims.First(k => k.Key == "email").Value.ToString()));
claimsList.Add(new Claim("given_name", extraClaims.First(k => k.Key == "first_name").Value.ToString()));
claimsList.Add(new Claim("family_name", extraClaims.First(k => k.Key == "last_name").Value.ToString()));
}
if (externalUser == null)
{
throw new ArgumentNullException("externalUser");
}
var user = await userManager.FindAsync(new Microsoft.AspNet.Identity.UserLoginInfo(externalUser.Provider, externalUser.ProviderId));
if (user == null)
{
ctx.AuthenticateResult = await ProcessNewExternalAccountAsync(externalUser.Provider, externalUser.ProviderId, claimsList);
}
else
{
ctx.AuthenticateResult = await ProcessExistingExternalAccountAsync(user.Id, externalUser.Provider, externalUser.ProviderId, claimsList);
}
}
And that's it! If you have any suggestions for simplifying this process, please let me know. I was going to modify this code to do perform the call to the API from FacebookAuthenticationOptions, but the Events property no longer exists apparently.
Edit: the GetAdditionalFacebookClaims method is simply a method that creates a new FacebookClient given the access token that was pulled out and queries the Facebook API for the other user claims you need. For example, my method looks like this:
protected static JsonObject GetAdditionalFacebookClaims(Claim accessToken)
{
var fb = new FacebookClient(accessToken.Value);
return fb.Get("me", new {fields = new[] {"email", "first_name", "last_name"}}) as JsonObject;
}
I need to utilize Dynamic CRM Data Service Endpoint exposed to get data from one of the methods.
Service(microsoft) account has access to this service.
I've tried authenticating to Discovery Service and Organization Service using sample code provided here [https://msdn.microsoft.com/en-us/library/hh675404.aspx] and succeed. However am not able to use same authentication to access data Service as I could find anyway to relate Data Service with the other two. Doing basic authentication using Network Credentials does not work.
I have downloaded the CSDL exposed and added that as service reference to my project, which created an class of web service which extends from DataServiceContext. Am trying to retrieve data of one of the methods using LinQ queries. It returs following error:
"The response payload is a not a valid response payload. Please make sure that the top level element is a valid Atom or JSON element or belongs to 'http://schemas.microsoft.com/ado/2007/08/dataservices' namespace." On capturing using fiddle I realized that on hitting data service URL it is redirected to sign in page 'login.microsoftonline.com/'
Can anybody suggest a way to authenticate the user to access Data Serivce?
Adding code:
//<snippetAuthenticateWithNoHelp1>
IServiceManagement<IDiscoveryService> serviceManagement =
ServiceConfigurationFactory.CreateManagement<IDiscoveryService>(
new Uri(_discoveryServiceAddress));
AuthenticationProviderType endpointType = serviceManagement.AuthenticationType;
// Set the credentials.
AuthenticationCredentials authCredentials = GetCredentials(serviceManagement, endpointType);
String organizationUri = String.Empty;
// Get the discovery service proxy.
using (DiscoveryServiceProxy discoveryProxy =
GetProxy<IDiscoveryService, DiscoveryServiceProxy>(serviceManagement, authCredentials))
{
// Obtain organization information from the Discovery service.
if (discoveryProxy != null)
{
// Obtain information about the organizations that the system user belongs to.
OrganizationDetailCollection orgs = DiscoverOrganizations(discoveryProxy);
// Obtains the Web address (Uri) of the target organization.
organizationUri = FindOrganization(_organizationUniqueName,
orgs.ToArray()).Endpoints[EndpointType.OrganizationService];
}
}
//</snippetAuthenticateWithNoHelp1>
if (!String.IsNullOrWhiteSpace(organizationUri))
{
//<snippetAuthenticateWithNoHelp3>
IServiceManagement<IOrganizationService> orgServiceManagement =
ServiceConfigurationFactory.CreateManagement<IOrganizationService>(
new Uri(organizationUri));
// Set the credentials.
AuthenticationCredentials credentials = GetCredentials(orgServiceManagement, endpointType);
// Get the organization service proxy.
using (OrganizationServiceProxy organizationProxy =
GetProxy<IOrganizationService, OrganizationServiceProxy>(orgServiceManagement, credentials))
{
// This statement is required to enable early-bound type support.
organizationProxy.EnableProxyTypes();
// Now make an SDK call with the organization service proxy.
// Display information about the logged on user.
Guid userid = ((WhoAmIResponse)organizationProxy.Execute(
new WhoAmIRequest())).UserId;
SystemUser systemUser = organizationProxy.Retrieve("systemuser", userid,
new ColumnSet(new string[] { "firstname", "lastname" })).ToEntity<SystemUser>();
Console.WriteLine("Logged on user is {0} {1}.",
systemUser.FirstName, systemUser.LastName);
Uri x = new Uri("https://<MyOrgainzationName>.crm.dynamics.com/XRMServices/2011/OrganizationData.svc/");
MyOrgainzationContext saContext = new MyOrgainzationContext(x);
NetworkCredential nc = new NetworkCredential();
nc.UserName = "*****#microsoft.com";
nc.Password = "********";
saContext.Credentials = nc;
var query_where3 = from c in saContext.new_productSet
select new
{
ProductStatus = c.new_ProductStatus,
LineofBusiness = c.new_LineofBusiness
};
var temp = saContext.Entities;
foreach (var c in query_where3)
{
System.Console.WriteLine("ProductStatus: " +
c.ProductStatus +
"\t\t\t" +
"LineofBusiness: " +
c.LineofBusiness);
}
}
//</snippetAuthenticateWithNoHelp3>
}
MyOrganizationContext is the context class created on adding CSDL file exposed at service endpoints
Have a look at the CRM Web Api Preview: https://msdn.microsoft.com/en-us/dynamics/crm/webapipreview.aspx. You can call this endpoint from outside xRM and you can authenticate with OAuth 2.0.
im trying to write a UnitTest for a WebAPI and EF
Im trying to add Data do the database in den TestInitialize, but it didnt work. When i do the same command in a Console Applications, it works.
Is there a different in calling that webapi for tests?
[TestInitialize]
public void CreateEntityObjects()
{
HttpClient client = new HttpClient();
client.BaseAddress = new Uri("http://localhost:60609/");
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
Department dep1 = new Department() { Id = Guid.NewGuid(), Name = "IT" };
client.PostAsJsonAsync("api/Department", dep1);
}
Edit:
So i still just do the initialize no cleanup (later). I was looking manually if there is some data in the database. But no data, no Error, nothing.
You may try looking at the result property of the request:
var response = client.PostAsJsonAsync("api/Department", dep1).Result;
and then look at what does the server return:
string responseContent = response.Result.Content.ReadAsStringAsync().Result;
Now analyze the responseContent variable to see what possible error message you might have gotten from the server. Also you probably want to self-host your Web API in the unit test before sending an HTTP request to it, otherwise your API might not even be started.