Unable to update graph application based subscription with C# - c#

I am trying to update the subscription expiry for my graph token. This is an application based subscription.
This is what I have tried:
var tokenExpiry = DateTime.UtcNow.AddMinutes(4230).ToUniversalTime().ToString("yyyy-MM-ddTHH:mm:ss.0Z");
Lookup lp = new Lookup();
string access_token = "{MYACCESSTOKEN}";
_CreateRequestBody requestBody = new _CreateRequestBody
{
expirationDateTime = tokenExpiry
};
RestClient rc = new RestClient();
rc.EndPoint = "https://graph.microsoft.com/v1.0/subscriptions/" + subscriptionId;
rc.AccessToken = access_token;
rc.Method = HttpVerbs.PATCH;
rc.PostData = JsonConvert.SerializeObject(requestBody);
Error message is Invalid: expirationDateTime and its value must be included in the payload.

Related

Microsoft Graph API - Determine if calendar event already exists - C#

I am inserting calendar events into our domain users O365 calendars with Microsoft Graph API. I need to determine if the event exists, but my research has only shown how to use the GraphClient.Me.Events scenario to search. I don't believe this would work as we have global access to all calendars (Calendars.ReadWrite) within our domain.
Is there any way to search for the event in the applicable domain users calendar before sync?
var scopes = new string[] { "https://graph.microsoft.com/.default" };
var confidentialClient = ConfidentialClientApplicationBuilder.Create(clientId).WithTenantId(tenantId).WithClientSecret(clientSecret).Build();
var authResult = await confidentialClient.AcquireTokenForClient(scopes).ExecuteAsync();
using (HttpClient c = new HttpClient())
{
string url = "https://graph.microsoft.com/v1.0/users/" + userEmail + " /calendar/events";
ToOutlookCalendar createOutlookEvent = CreateEvent();
HttpContent httpContent = new StringContent(JsonConvert.SerializeObject(createOutlookEvent), Encoding.UTF8, "application/json");
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, url);
request.Content = httpContent;
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", authResult.AccessToken);
var response = await c.SendAsync(request);
var responseString = await response.Content.ReadAsStringAsync();
}
The calendar event is currently very simple for testing
public static ToOutlookCalendar CreateEvent()
{
ToOutlookCalendar outlookEvent = new ToOutlookCalendar
{
Subject = "Code test migration appt",
Body = new Body
{
ContentType = "HTML",
Content = "Testing API with application client authorization"
},
Start = new End
{
DateTime = "2020-06-22T12:30:00",TimeZone = System.TimeZone.CurrentTimeZone.StandardName
},
End = new End
{
DateTime = "2020-06-22T14:00:00",TimeZone = System.TimeZone.CurrentTimeZone.StandardName
},
Location = new LocationName
{
DisplayName = "Sample Location"
}
};
return outlookEvent;
}
Assuming you're targeting the default calendar for the user, yes.
the /me path segment is an alias to the upn or userId, so something like:
"https://graph.microsoft.com/v1.0/users/" + userEmail + "/calendar/events?$filter=subject eq '" + knownTitle + "'"
Should work just fine if you're using an app only token with sufficent permissions

How to generate JWT Bearer Flow OAuth access tokens from a .net core client?

I'm having trouble getting my .NET Core client to generate OAuth access tokens for a salesforce endpoint that requires OAuth of type 'JWT Bearer Flow'.
It seems there are limited .NET Framework examples that show a .NET client doing this, however none that show a .NET Core client doing it
e.g.
https://salesforce.stackexchange.com/questions/53662/oauth-jwt-token-bearer-flow-returns-invalid-client-credentials
So in my .NET Core 3.1 app i've generated a self signed certificate, added the private key to the above example's code when loading in the certificate, however a System.InvalidCastExceptionexception exception occurs on this line:
var rsa = certificate.GetRSAPrivateKey() as RSACryptoServiceProvider;
Exception:
System.InvalidCastException: 'Unable to cast object of type 'System.Security.Cryptography.RSACng' to type 'System.Security.Cryptography.RSACryptoServiceProvider'.'
It appears that this private key is used in the JWT Bearer Flow as part of the signature, and perhaps RSACryptoServiceProvider is not used in .NET core as it was in .NET Framework.
My question is this - is there actually a way in .NET Core to generate access tokens for the OAuth JWT Bearer Flow?
Full code that I'm using:
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
var token = GetAccessToken();
}
static dynamic GetAccessToken()
{
// get the certificate
var certificate = new X509Certificate2(#"C:\temp\cert.pfx");
// create a header
var header = new { alg = "RS256" };
// create a claimset
var expiryDate = GetExpiryDate();
var claimset = new
{
iss = "xxxxxx",
prn = "xxxxxx",
aud = "https://test.salesforce.com",
exp = expiryDate
};
// encoded header
var headerSerialized = JsonConvert.SerializeObject(header);
var headerBytes = Encoding.UTF8.GetBytes(headerSerialized);
var headerEncoded = ToBase64UrlString(headerBytes);
// encoded claimset
var claimsetSerialized = JsonConvert.SerializeObject(claimset);
var claimsetBytes = Encoding.UTF8.GetBytes(claimsetSerialized);
var claimsetEncoded = ToBase64UrlString(claimsetBytes);
// input
var input = headerEncoded + "." + claimsetEncoded;
var inputBytes = Encoding.UTF8.GetBytes(input);
// signature
var rsa = (RSACryptoServiceProvider) certificate.GetRSAPrivateKey();
var cspParam = new CspParameters
{
KeyContainerName = rsa.CspKeyContainerInfo.KeyContainerName,
KeyNumber = rsa.CspKeyContainerInfo.KeyNumber == KeyNumber.Exchange ? 1 : 2
};
var aescsp = new RSACryptoServiceProvider(cspParam) { PersistKeyInCsp = false };
var signatureBytes = aescsp.SignData(inputBytes, "SHA256");
var signatureEncoded = ToBase64UrlString(signatureBytes);
// jwt
var jwt = headerEncoded + "." + claimsetEncoded + "." + signatureEncoded;
var client = new WebClient();
client.Encoding = Encoding.UTF8;
var uri = "https://login.salesforce.com/services/oauth2/token";
var content = new NameValueCollection();
content["assertion"] = jwt;
content["grant_type"] = "urn:ietf:params:oauth:grant-type:jwt-bearer";
string response = Encoding.UTF8.GetString(client.UploadValues(uri, "POST", content));
var result = JsonConvert.DeserializeObject<dynamic>(response);
return result;
}
static int GetExpiryDate()
{
var utc0 = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
var currentUtcTime = DateTime.UtcNow;
var exp = (int)currentUtcTime.AddMinutes(4).Subtract(utc0).TotalSeconds;
return exp;
}
static string ToBase64UrlString(byte[] input)
{
return Convert.ToBase64String(input).TrimEnd('=').Replace('+', '-').Replace('/', '_');
}
Well - it turns out posting to stackoverflow gets the brain cogs turning.
The answer ended up being doing a deep dive to find a similar issue here and using the solution from x509certificate2 sign for jwt in .net core 2.1
I ended up replacing the following code:
var cspParam = new CspParameters
{
KeyContainerName = rsa.CspKeyContainerInfo.KeyContainerName,
KeyNumber = rsa.CspKeyContainerInfo.KeyNumber == KeyNumber.Exchange ? 1 : 2
};
var aescsp = new RSACryptoServiceProvider(cspParam) { PersistKeyInCsp = false };
var signatureBytes = aescsp.SignData(inputBytes, "SHA256");
var signatureEncoded = ToBase64UrlString(signatureBytes);
With this code which makes use of the System.IdentityModel.Tokens.Jwt nuget package:
var signingCredentials = new X509SigningCredentials(certificate, "RS256");
var signature = JwtTokenUtilities.CreateEncodedSignature(input, signingCredentials);
Full code after solution:
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
var token = GetAccessToken();
}
static dynamic GetAccessToken()
{
// get the certificate
var certificate = new X509Certificate2(#"C:\temp\cert.pfx");
// create a header
var header = new { alg = "RS256" };
// create a claimset
var expiryDate = GetExpiryDate();
var claimset = new
{
iss = "xxxxx",
prn = "xxxxx",
aud = "https://test.salesforce.com",
exp = expiryDate
};
// encoded header
var headerSerialized = JsonConvert.SerializeObject(header);
var headerBytes = Encoding.UTF8.GetBytes(headerSerialized);
var headerEncoded = ToBase64UrlString(headerBytes);
// encoded claimset
var claimsetSerialized = JsonConvert.SerializeObject(claimset);
var claimsetBytes = Encoding.UTF8.GetBytes(claimsetSerialized);
var claimsetEncoded = ToBase64UrlString(claimsetBytes);
// input
var input = headerEncoded + "." + claimsetEncoded;
var inputBytes = Encoding.UTF8.GetBytes(input);
var signingCredentials = new X509SigningCredentials(certificate, "RS256");
var signature = JwtTokenUtilities.CreateEncodedSignature(input, signingCredentials);
// jwt
var jwt = headerEncoded + "." + claimsetEncoded + "." + signature;
var client = new WebClient();
client.Encoding = Encoding.UTF8;
var uri = "https://test.salesforce.com/services/oauth2/token";
var content = new NameValueCollection();
content["assertion"] = jwt;
content["grant_type"] = "urn:ietf:params:oauth:grant-type:jwt-bearer";
string response = Encoding.UTF8.GetString(client.UploadValues(uri, "POST", content));
var result = JsonConvert.DeserializeObject<dynamic>(response);
return result;
}
static int GetExpiryDate()
{
var utc0 = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc);
var currentUtcTime = DateTime.UtcNow;
var exp = (int)currentUtcTime.AddMinutes(4).Subtract(utc0).TotalSeconds;
return exp;
}
static string ToBase64UrlString(byte[] input)
{
return Convert.ToBase64String(input).TrimEnd('=').Replace('+', '-').Replace('/', '_');
}
I am replying to this question just because such a similar answer would have helped me a lot when I landed on this page the first time.
First of all you don't have to generate the JWT from the C# client.
To generate a JWT token you can use this website: https://jwt.io/
There is a very well done video showing how to generate a JWT token:
https://www.youtube.com/watch?v=cViU2-xVscA&t=1680s
Once generated, use it from your C# client to call the get access_token endpoint
https://developer.salesforce.com/docs/atlas.en-us.api_iot.meta/api_iot/qs_auth_access_token.htm
(Watch the video on YT)
If all is correct you will get the access_token
To run the API calls, all you need is the access_token and not the JWT.
Once you have it add it to the HTTP calls like this
public static void AddBearerToken(this HttpRequestMessage request, string accessToken)
{
request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", accessToken);
}
From time to time the access_token will expire. To check its validity you can call the token introspect api
https://help.salesforce.com/s/articleView?id=sf.remoteaccess_oidc_token_introspection_endpoint.htm&type=5
You need to pass two additional parameters: client_id and client_secret
The client_id is the Consumer Key. You get it from the Connected App in Salesforce
The client_server is the Consumer Secret. You get it from Connected App in Salesforce
If the introspect token API returns a response with
{ active: false, ... }
it means that the access_token is expired and you need to issue a new one.
To issue a new access_token simply call the "/services/oauth2/token" again using the same JWT.

Bad device token apple push notification

I have stuck in apple push notification. Following the document in https://developer.apple.com/library/archive/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/CommunicatingwithAPNs.html#//apple_ref/doc/uid/TP40008194-CH11-SW1 I created the header and payload with the private key to generate token, but after call api: https://api.development.push.apple.com:443/3/device/, It told bad device token. Check in jwt.io it said invalid token.
Anyone know this problem or idea.
Thank you !
Here is the code .net core:
var header = JsonConvert.SerializeObject(new { alg = "ES256", kid = keyId });
var payload = JsonConvert.SerializeObject(new { iss = teamId, iat = ToEpoch(DateTime.UtcNow) });
var key = CngKey.Import(Convert.FromBase64String(p8privateKey), CngKeyBlobFormat.Pkcs8PrivateBlob);
using (ECDsaCng dsa = new ECDsaCng(key))
{
dsa.HashAlgorithm = CngAlgorithm.Sha256;
var headerBase64 = Convert.ToBase64String(Encoding.UTF8.GetBytes(header));
var payloadBasae64 = Convert.ToBase64String(Encoding.UTF8.GetBytes(payload));
var unsignedJwtData = System.Convert.ToBase64String(Encoding.UTF8.GetBytes(header)) + "." + System.Convert.ToBase64String(Encoding.UTF8.GetBytes(payload));
var unsignedJwtDataBytes = Encoding.UTF8.GetBytes(unsignedJwtData);
var signature =
dsa.SignData(unsignedJwtDataBytes);
return unsignedJwtData + "." + System.Convert.ToBase64String(signature);
}
}

How to add SAML token to SOAP Request in C#

I try to call a SOAP service by authenticating myself with a SAML token.
First I get a SAML token for the target by calling the ADFS:
var stsEndpoint = "https://ADFS.EXAMPLE/adfs/services/trust/13/kerberosmixed";
var reliantPartyUri = "http://reliant-party.com";
var binding = new CustomBinding();
var ssbe = SecurityBindingElement.CreateKerberosOverTransportBindingElement();
ssbe.DefaultAlgorithmSuite = SecurityAlgorithmSuite.Basic128;
ssbe.MessageSecurityVersion = MessageSecurityVersion.WSSecurity10WSTrust13WSSecureConversation13WSSecurityPolicy12BasicSecurityProfile10;
binding.Elements.Add(ssbe);
binding.Elements.Add(new TextMessageEncodingBindingElement());
binding.Elements.Add(new HttpsTransportBindingElement());
var factory = new WSTrustChannelFactory(binding, new EndpointAddress(stsEndpoint));
factory.TrustVersion = TrustVersion.WSTrust13;
var rst = new RequestSecurityToken
{
RequestType = RequestTypes.Issue,
AppliesTo = new EndpointReference(reliantPartyUri)
};
var channel = factory.CreateChannel();
var token = channel.Issue(rst);
Now I want to use the SAML token to call a secured SOAP webservice. How is it possible to add the token? I've tried the following without success (the soap request does not contain any token):
//Service was created by an imported WSDL File - Methods and Types renamed for StackOverflow
var request = new Service.WsdlCreatedRequest();
[...]
var wsdlClient = new Service.WsdlCreatedService("HTTPS_Port");
var wsdlChannel = wsdlClient.ChannelFactory.CreateChannelWithIssuedToken(token);
wsdlChannel.WsdlCreatedMethod(request);
Any idea how to use the token in the request?

Google Analytics Api 400 Bad Request

I want to import data from Google Analytics.
var gas = new AnalyticsService(auth);
var r = gas.Data.Ga.Get("ga:6332XXXX", "2013-02-01", "2013-02-11", "ga:visits");
r.Dimensions = "ga:date";
r.Sort = "ga:visits";
r.StartIndex = 1;
var data = r.Fetch();
I get 400 bad request error in Fetch method. What is the wrong of my code?
My full code like following:
var scope = AnalyticsService.Scopes.AnalyticsReadonly.ToString();
var clientId = "--------.apps.googleusercontent.com";
var keyFile = #"C:\-----------------privatekey.p12";
var keyPassword = "notasecret";
var desc = GoogleAuthenticationServer.Description;
var key = new X509Certificate2(keyFile, keyPassword, X509KeyStorageFlags.Exportable);
var client = new AssertionFlowClient(desc, key)
{
ServiceAccountId = clientId,
Scope = scope
};
var auth = new OAuth2Authenticator<AssertionFlowClient>(client, AssertionFlowClient.GetState);
var gas = new AnalyticsService(auth);
var r = gas.Data.Ga.Get("ga:6332XXXX", "2013-02-01", "2013-02-11", "ga:visits");
r.Dimensions = "ga:date";
r.Sort = "ga:visits";
r.StartIndex = 1;
var data = r.Fetch();
Thaks for your interest.
A couple of ideas:
your clientId should be your SERVICE ACCOUNT EMAIL (not id)
your scope should be "https://www.googleapis.com/auth/analytics.readonly"
If this doesn't work, check #Martyn's answer

Categories

Resources