Google calendar - Insert events from azure not working - asp.net-mvc-4

I have a requirement where I need to update a user's google calendar when an event is inserted in the application's fullcalendar. I am using the peleyal's example for ASP.Net MVC and OAuth for Google API (using GoogleAuthorizationCodeFlow and AuthorizationCodeMvcApp) to handle this. I believe I have set up the credentials right in the google developer console as well.
I am able to create events on the google calendar locally without a problem. But from the azure deployed site I am not able to create the event. There are no exceptions/errors either. Is there anything that needs to be done from azure side to be able to create events and to use Google API?
var finalREsult = System.Threading.Tasks.Task.Run(async () =>
{
try
{
var result = await new AuthorizationCodeMvcApp(this, new AppFlowMetaData()).AuthorizeAsync(cancellationToken);
if (result.Credential != null)
{
var service = new CalendarService(new BaseClientService.Initializer
{
HttpClientInitializer = result.Credential,
ApplicationName = "****"
});
var startDate = patientappointment.DateScheduled.Value.ToUniversalTime();
var endDate = patientappointment.DateScheduled.Value.AddMinutes(patientappointment.AppointmentLengthMinutes).ToUniversalTime();
var myEvent = new Event
{
Summary = string.Format("{0}/{1}/{2} - {3}", patientappointment.CurrentUserName, patientappointment.LocationName, patientappointment.RoomName, patientappointment.Notes),
Location = "Ireland",
Start = new EventDateTime
{
DateTime = new DateTime(startDate.Year, startDate.Month, startDate.Day, startDate.Hour, startDate.Minute, 0),
TimeZone = "(GMT+01:00) Dublin"
},
End = new EventDateTime
{
DateTime = new DateTime(endDate.Year, endDate.Month, endDate.Day, endDate.Hour, endDate.Minute, 0),
TimeZone = "(GMT+01:00) Dublin"
},
Recurrence = new String[] { "RRULE:FREQ=WEEKLY;BYDAY=MO" }
// Attendees = new List<EventAttendee> { new EventAttendee { Email = "**0#gmail.com" } },
};
EventsResource.InsertRequest request = service.Events.Insert(myEvent, "******#group.calendar.google.com");
request.Execute();
}
}
catch (Exception ex)
{
throw ex;
}
Looking at the requests information, I dont see the request being sent to first autheticate and get the auth code from the deployed site.
internal class ForceOfflineGoogleAuthorizationCodeFlow : GoogleAuthorizationCodeFlow
{
public ForceOfflineGoogleAuthorizationCodeFlow
(AuthorizationCodeFlow.Initializer initializer)
: base((GoogleAuthorizationCodeFlow.Initializer)initializer) { }
public override AuthorizationCodeRequestUrl CreateAuthorizationCodeRequest(string redirectUri)
{
return new GoogleAuthorizationCodeRequestUrl(new Uri(AuthorizationServerUrl))
{
ResponseType = "code",
ClientId = ClientSecrets.ClientId,
Scope = string.Join(" ", Scopes),
RedirectUri = redirectUri,
AccessType = "offline",
ApprovalPrompt = "force",
State = ""
};
}
};
public class AppFlowMetaData : FlowMetadata
{
private static readonly IAuthorizationCodeFlow flow = new ForceOfflineGoogleAuthorizationCodeFlow(new GoogleAuthorizationCodeFlow.Initializer
{
ClientSecrets = new ClientSecrets
{
ClientId = ConfigurationManager.AppSettings["ClientId"],
ClientSecret = ConfigurationManager.AppSettings["ClientSecret"]
},
Scopes = new[] { CalendarService.Scope.Calendar },
DataStore = null
});
public override string GetUserId(System.Web.Mvc.Controller controller)
{
return "user1";
}
public override IAuthorizationCodeFlow Flow
{
get { return flow; }
}
}

Related

Using MQTT ManagedClient with ASP NET API, how to?

I'm currently working on a project that has to rely heavily on MQTT - one of the parts that needs to utilize MQTT is a ASP Net API, but I'm having difficulties receiving messages.
Here is my MQTTHandler:
public MQTTHandler()
{
_mqttUrl = Properties.Resources.mqttURL ?? "";
_mqttPort = Properties.Resources.mqttPort ?? "";
_mqttUsername = Properties.Resources.mqttUsername ?? "";
_mqttPassword = Properties.Resources.mqttUsername ?? "";
_mqttFactory = new MqttFactory();
_tls = false;
}
public async Task<IManagedMqttClient> ConnectClientAsync()
{
var clientID = Guid.NewGuid().ToString();
var messageBuilder = new MqttClientOptionsBuilder()
.WithClientId(clientID)
.WithCredentials(_mqttUsername, _mqttPassword)
.WithTcpServer(_mqttUrl, Convert.ToInt32(_mqttPort));
var options = _tls ? messageBuilder.WithTls().Build() : messageBuilder.Build();
var managedOptions = new ManagedMqttClientOptionsBuilder()
.WithAutoReconnectDelay(TimeSpan.FromSeconds(5))
.WithClientOptions(options)
.Build();
_mqttClient = new MqttFactory().CreateManagedMqttClient();
await _mqttClient.StartAsync(managedOptions);
Console.WriteLine("Klient startet");
return _mqttClient;
}
public async Task PublishAsync(string topic, string payload, bool retainFlag = true, int qos = 1)
{
await _mqttClient.EnqueueAsync(new MqttApplicationMessageBuilder()
.WithTopic(topic)
.WithPayload(payload)
.WithQualityOfServiceLevel((MQTTnet.Protocol.MqttQualityOfServiceLevel)qos)
.WithRetainFlag(retainFlag)
.Build());
Console.WriteLine("Besked published");
}
public async Task SubscribeAsync(string topic, int qos = 1)
{
var topicFilters = new List<MQTTnet.Packets.MqttTopicFilter>
{
new MqttTopicFilterBuilder()
.WithTopic(topic)
.WithQualityOfServiceLevel((MQTTnet.Protocol.MqttQualityOfServiceLevel)(qos))
.Build()
};
await _mqttClient.SubscribeAsync(topicFilters);
}
public Status GetSystemStatus(MqttApplicationMessageReceivedEventArgs e)
{
try
{
var json = Encoding.UTF8.GetString(e.ApplicationMessage.Payload);
var status = JsonSerializer.Deserialize<Status>(json);
if (status != null)
{
return status;
}
else
{
return null;
}
}
catch (Exception)
{
throw;
}
}
The above has been tested with a console app and works as it should.
The reason I need MQTT in the APi is that a POST method has to act on the value of a topic;
In particular I need to check a systems status before allowing the post;
[HttpPost]
public async Task<ActionResult<Order>> PostOrder(Order order)
{
if (_lastStatus != null)
{
if (_lastStatus.OpStatus)
{
return StatusCode(400, "System is busy!");
}
else
{
var response = await _orderManager.AddOrder(order);
return StatusCode(response.StatusCode, response.Message);
}
}
return StatusCode(400, "Something went wrong");
}
So I will need to set up a subscriber for this controller, and set the value of _lastStatus on received messages:
private readonly MQTTHandler _mqttHandler;
private IManagedMqttClient _mqttClient;
private Status _lastStatus;
public OrdersController(OrderManager orderManager)
{
_orderManager = orderManager;
_mqttHandler = new MQTTHandler();
_mqttClient = _mqttHandler.ConnectClientAsync().Result;
_mqttHandler.SubscribeAsync("JSON/Status");
_mqttClient.ApplicationMessageReceivedAsync += e =>
{
_lastStatus = _mqttHandler.GetSystemStatus(e);
return Task.CompletedTask;
};
}
However, it's behaving a little odd and I'm not experienced enough to know why.
The first time I make a POST request, _lastStatus is null - every following POST request seem to have the last retained message.
I'm guessing that I am struggling due to stuff being asynchronous, but not sure, and every attempt I've attempted to make it synchronous have failed.
Anyone have a clue about what I'm doing wrong?

Sending mail multiple times by Microsoft Graph

Blazor server application
I have a web appliction that is using AzureAd and OpenIdConnect to login to this application.
I am sending mail by using Microsoft graph and I am using the example in Microsoft doc with some changes like this:
#inject Microsoft.Graph.GraphServiceClient GraphServiceClient
#inject MicrosoftIdentityConsentAndConditionalAccessHandler ConsentHandler
var message = new Message
{
Subject = "Meet for lunch?",
Body = new ItemBody
{
ContentType = BodyType.Text,
Content = "The new cafeteria is open."
},
ToRecipients = new List<Recipient>()
{
new Recipient
{
EmailAddress = new EmailAddress
{
Address = "fannyd#contoso.onmicrosoft.com"
}
}
},
CcRecipients = new List<Recipient>()
{
new Recipient
{
EmailAddress = new EmailAddress
{
Address = "danas#contoso.onmicrosoft.com"
}
}
}
};
var saveToSentItems = false;
try
{
await GraphServiceClient.Me
.SendMail(message,saveToSentItems)
.Request()
.PostAsync();
}
catch(Exception ex)
{
ConsentHandler.HandleException(ex);
}
Scenario of the error
This works perfect but just for one time , if I try to send the same email again it doesn't work and I got no error but just redirect me to empty page.
If I logout and login again, then it works fine.
The error that I got in the second time:
Message = "IDW10502: An MsalUiRequiredException was thrown due to a challenge for the user. See https://aka.ms/ms-id-web/ca_incremental-consent. "
MsalUiRequiredException = {"No account or login hint was passed to the AcquireTokenSilent call. "}
My Question
How can fix the code up to send multi emails?. I think that I have a problem with token but I don't know where should I start?.
Thanks
Add the other catch block to retrieve the specific issue if we have any related to permissions or other ServiceExceptions.
try
{
SendMail(); // Sending mail code here.
}
catch (Microsoft.Graph.ServiceException e)
{
// get the error here if we have any.
}
After some search, I think that I have to move the the permission Delegated permissions to Application Permissions like this:
Why should I use application permission?
In my case the user logged in for the first time and clicked on the button then the email will be send, but in the second time the application has to communicate with API graph without interaction from the user, that means without user and this exactly what I need(application permission).
I adjust the code like the following:
Client credentials provider:
The client credential flow enables service applications to run without user interaction. Access is based on the identity of the application. this is from Microsoft doc
private GraphServiceClient CreateGraphServiceClient()
{
// The client credentials flow requires that you request the
// /.default scope, and preconfigure your permissions on the
// app registration in Azure. An administrator must grant consent
// to those permissions beforehand.
var scopes = new[] { "https://graph.microsoft.com/.default" };
// Multi-tenant apps can use "common",
// single-tenant apps must use the tenant ID from the Azure portal
var tenantId = "common";
// Values from app registration
var clientId = "YOUR_CLIENT_ID";
var clientSecret = "YOUR_CLIENT_SECRET";
// using Azure.Identity;
var options = new TokenCredentialOptions
{
AuthorityHost = AzureAuthorityHosts.AzurePublicCloud
};
var clientSecretCredential = new ClientSecretCredential(
tenantId, clientId, clientSecret, options);
return new GraphServiceClient(clientSecretCredential, scopes);
}
Send mail with UserId, you can see the code in Microsoft doc:
puplic SendMyEmail()
{
GraphServiceClient graphClient = CreateGraphServiceClient;
var message = new Message
{
Subject = "Meet for lunch?",
Body = new ItemBody
{
ContentType = BodyType.Text,
Content = "The new cafeteria is open."
},
ToRecipients = new List<Recipient>()
{
new Recipient
{
EmailAddress = new EmailAddress
{
Address = "fannyd#contoso.onmicrosoft.com"
}
}
},
CcRecipients = new List<Recipient>()
{
new Recipient
{
EmailAddress = new EmailAddress
{
Address = "danas#contoso.onmicrosoft.com"
}
}
}
};
var saveToSentItems = false;
//See GetUserId down
string userId = await GetUserId();
await graphClient.Users[UserId]
.SendMail(message,saveToSentItems)
.Request()
.PostAsync();
}
}
UserId:
To get user Id you need AuthenticationStateProvider, this has to inject in the service of your application and then add to the constructor of your class, then you can use it.
puplic class MyClass
{
private readonly MicrosoftIdentityConsentAndConditionalAccessHandler ConsentHandler;
private readonly AuthenticationStateProvider authenticationState;
puplic MyClass(
MicrosoftIdentityConsentAndConditionalAccessHandler ConsentHandler,
AuthenticationStateProvider authenticationState)
{
this.authenticationState = authenticationState;
this.ConsentHandler = ConsentHandler;
}
public async Task<string> GetUserId()
{
var authSate = await authenticationState.GetAuthenticationStateAsync();
return authSate.User.FindFirstValue("http://schemas.microsoft.com/identity/claims/objectidentifier");
}
//Here your
private GraphServiceClient CreateGraphServiceClient() { ...}
puplic SendMyEmail() {....}
}

Signalr .net core 2.2 - mesages going to non connected groups

I am using .net core 2.2 with SignalR version 1.1.0. When I test the app, messages are being received by member who are NOT in the group. My groups are being dynamically created at run time based on relevant criteria, as in : var TheHub = CurrUser.Hubname; I cannot work out why group members who are NOT in the group are also receiving the messages. I am sending to GROUP and not ALL.
Please see code. Any help greatly appreciated, I am ready to pull my hair out.
My hub class
public class Chathub : Microsoft.AspNetCore.SignalR.Hub
{
public override async Task OnConnectedAsync()
{
var TheHub = CurrUser.Hubname;
await Groups.AddToGroupAsync(Context.ConnectionId, TheHub.ToString());
await base.OnConnectedAsync();
}
public Task SendMessageGroup(string user, string message)
{
var TheHub = CurrUser.Hubname;
return Clients.Group(TheHub.ToString()).SendAsync("ReceiveMessage", user, message);
}
}
My Javascript
"use strict";
document.getElementById("sendgroupButton").addEventListener("click", function (event) {
var user = document.getElementById("userInput").value;
var message = document.getElementById("messageInput").value;
connection.invoke("SendMessageGroup", user, message).catch(function (err) {
return console.error(err.toString());
});
event.preventDefault();
playAudio();
});
var connection = new signalR.HubConnectionBuilder().withUrl("/chatHub").build();
document.getElementById("sendgroupButton").disabled = true;
connection.on("ReceiveMessage", function (user, message) {
var msg = message.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
var encodedMsg = user + " says " + msg;
var li = document.createElement("li");
li.textContent = encodedMsg;
document.getElementById("messagesList").appendChild(li);
});
connection.start().then(function () {
document.getElementById("sendgroupButton").disabled = false;
}).catch(function (err) {
return console.error(err.toString());
});
This is how I get the current value for curruser.hubname, please see below.
#inject SignInManager<ApplicationUser> SignInManager
#inject UserManager<ApplicationUser> UserManager
#if (SignInManager.IsSignedIn(User))
{
CurrUser.CurrentUsertId = UserManager.GetUserId(User);
var ctx = new WebookContext();
var LoggedInGuestHouseName = (from Ghouse in ctx.Guesthouse
where Ghouse.UserId == CurrUser.CurrentUsertId
select Ghouse).SingleOrDefault();
//check to see if guesthouse details have been completed, if not skip this next line of code.
if( LoggedInGuestHouseName != null)
{
CurrUser.GuestHouseName = LoggedInGuestHouseName.GuestHouseName;
// add the hub to current user
CurrUser.HubId = (int) LoggedInGuestHouseName.HubId;
var Ghname = LoggedInGuestHouseName.GuestHouseName;
var GhUserEmailaddress = LoggedInGuestHouseName.Emailaddress;
var GhHuId = LoggedInGuestHouseName.HubId;
CurrUser.GuestHouseName = Ghname;
CurrUser.GuestHouseEmailaddress = GhUserEmailaddress;
var q = (from gh in ctx.Hub
where gh.HubId == GhHuId
select gh).SingleOrDefault();
var myhubname = q.HubName;
CurrUser.Hubname = myhubname;
};
}
Looks like SignalR core is not for the feint hearted. Until a authoritative book comes out, one is really walking blind. I have researched this topic blue, but alas have now given up on SignalR for now.

IdentityServer 4 - Custom IExtensionGrantValidator always return invalid_grant

My app requirements is to authenticate using client credentials AND another code (hash).
I followed this link to create and use custom IExtensionGrantValidator.
I manged to invoke the custom IExtensionGrantValidator with approved grant, but client always gets invalid_grant error.
For some reason the set operation ofd Result (property of ExtensionGrantValidationContext) always fails (overriding the Error value returns the overrided value to client).
This is CustomGrantValidator Code:
public class CustomGrantValidator : IExtensionGrantValidator
{
public string GrantType => "grant-name";
public Task ValidateAsync(ExtensionGrantValidationContext context)
{
var hash = context.Request.Raw["hash"]; //extract hash from request
var result = string.IsNullOrEmpty(hash) ?
new GrantValidationResult(TokenRequestErrors.InvalidRequest) :
new GrantValidationResult(hash, GrantType);
context.Result = result
}
}
Startup.cs contains this line:
services.AddTransient<IExtensionGrantValidator, CustomGrantValidator>();
And finally client's code:
var httpClient = new HttpClient() { BaseAddress = new Uri("http://localhost:5000") };
var disco = await httpClient.GetDiscoveryDocumentAsync("http://localhost:5000");
var cReq = await httpClient.RequestTokenAsync(new TokenRequest
{
GrantType = "grant-name",
Address = disco.TokenEndpoint,
ClientId = clientId,// client Id taken from appsetting.json
ClientSecret = clientSecret, //client secret taken from appsetting.json
Parameters = new Dictionary<string, string> { { "hash", hash } }
});
if (cReq.IsError)
//always getting 'invalid_grant' error
throw InvalidOperationException($"{cReq.Error}: {cReq.ErrorDescription}");
The below codes works on my environment :
public async Task ValidateAsync(ExtensionGrantValidationContext context)
{
var hash = context.Request.Raw["hash"]; //extract hash from request
var result = string.IsNullOrEmpty(hash) ?
new GrantValidationResult(TokenRequestErrors.InvalidRequest) :
new GrantValidationResult(hash, GrantType);
context.Result = result;
return;
}
Don't forget to register the client to allow the custom grant :
return new List<Client>
{
new Client
{
ClientId = "client",
// no interactive user, use the clientid/secret for authentication
AllowedGrantTypes = { "grant-name" },
// secret for authentication
ClientSecrets =
{
new Secret("secret".Sha256())
},
// scopes that client has access to
AllowedScopes = { "api1" }
}
};
I got the same issue and found the answer from #Sarah Lissachell, turn out that I need to implement the IProfileService. This interface has a method called IsActiveAsync. If you don't implement this method, the answer of ValidateAsync will always be false.
public class IdentityProfileService : IProfileService
{
//This method comes second
public async Task GetProfileDataAsync(ProfileDataRequestContext context)
{
//IsActiveAsync turns out to be true
//Here you add the claims that you want in the access token
var claims = new List<Claim>();
claims.Add(new Claim("ThisIsNotAGoodClaim", "MyCrapClaim"));
context.IssuedClaims = claims;
}
//This method comes first
public async Task IsActiveAsync(IsActiveContext context)
{
bool isActive = false;
/*
Implement some code to determine that the user is actually active
and set isActive to true
*/
context.IsActive = isActive;
}
}
Then you have to add this implementation in your startup page.
public void ConfigureServices(IServiceCollection services)
{
// Some other code
services.AddIdentityServer()
.AddDeveloperSigningCredential()
.AddAspNetIdentity<Users>()
.AddInMemoryApiResources(config.GetApiResources())
.AddExtensionGrantValidator<CustomGrantValidator>()
.AddProfileService<IdentityProfileService>();
// More code
}

LinkedIn full profile details using DotNetOpenAuth in MVC4

My MVC4 application allows login using LinkedIn account. I want to pull all details that are avaible from linkedIn of the logged in User. Currently i have done the following.
In My AuthConfig.cs,
Dictionary<string, object> linkedInExtraData = new Dictionary<string, object>();
linkedInExtraData.Add("Icon", "../Images/linkedIn.png");
OAuthWebSecurity.RegisterClient(
client: new App_Start.LinkedInCustomClient("xxxxxxxxxxxx", "yyyyyyyyyyyyyyy"),
displayName: "LinkedIn",
extraData: linkedInExtraData);
In linkedInCustomClient.cs , from LinkedIn Developer Kit
public class LinkedInCustomClient : OAuthClient
{
private static XDocument LoadXDocumentFromStream(Stream stream)
{
var settings = new XmlReaderSettings
{
MaxCharactersInDocument = 65536L
};
return XDocument.Load(XmlReader.Create(stream, settings));
}
/// Describes the OAuth service provider endpoints for LinkedIn.
private static readonly ServiceProviderDescription LinkedInServiceDescription =
new ServiceProviderDescription
{
AccessTokenEndpoint =
new MessageReceivingEndpoint("https://api.linkedin.com/uas/oauth/accessToken",
HttpDeliveryMethods.PostRequest),
RequestTokenEndpoint =
new MessageReceivingEndpoint("https://api.linkedin.com/uas/oauth/requestToken?scope=r_fullprofile",
HttpDeliveryMethods.PostRequest),
UserAuthorizationEndpoint =
new MessageReceivingEndpoint("https://www.linkedin.com/uas/oauth/authorize",
HttpDeliveryMethods.PostRequest),
TamperProtectionElements =
new ITamperProtectionChannelBindingElement[] { new HmacSha1SigningBindingElement() },
ProtocolVersion = ProtocolVersion.V10a
};
public LinkedInCustomClient(string consumerKey, string consumerSecret) :
base("linkedIn", LinkedInServiceDescription, consumerKey, consumerSecret) { }
/// Check if authentication succeeded after user is redirected back from the service provider.
/// The response token returned from service provider authentication result.
[SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes",
Justification = "We don't care if the request fails.")]
protected override AuthenticationResult VerifyAuthenticationCore(AuthorizedTokenResponse response)
{
// See here for Field Selectors API http://developer.linkedin.com/docs/DOC-1014
const string profileRequestUrl =
"https://api.linkedin.com/v1/people/~:(id,first-name,last-name,interests,headline,industry,summary,email-address,location:(name),picture-url,positions,associations,languages,honors,educations,date-of-birth,primary-twitter-account,three-current-positions,three-past-positions,group-memberships,specialties,skills)";
string accessToken = response.AccessToken;
string tokenSecret = (response as ITokenSecretContainingMessage).TokenSecret;
string Verifier = response.ExtraData.Values.First();
var profileEndpoint =
new MessageReceivingEndpoint(profileRequestUrl, HttpDeliveryMethods.GetRequest);
HttpWebRequest request =
WebWorker.PrepareAuthorizedRequest(profileEndpoint, accessToken);
try
{
using (WebResponse profileResponse = request.GetResponse())
{
using (Stream responseStream = profileResponse.GetResponseStream())
{
XDocument document = LoadXDocumentFromStream(responseStream);
return new AuthenticationResult(
isSuccessful: true,
provider: ProviderName,
providerUserId: userId,
userName: userName,
extraData: extraData);
}
}
}
catch (Exception exception)
{
return new AuthenticationResult(exception);
}
}
}
In my controller,
AuthenticationResult result = OAuthWebSecurity.VerifyAuthentication(Url.Action("ExternalLoginCallback", new { ReturnUrl = returnUrl }));
if (!result.IsSuccessful)
{
return RedirectToAction("ExternalLoginFailure");
}
I need to get the following details in my controller as authentication result.
(id,first-name,last-name,interests,headline,industry,summary,email-address,location:(name),picture-url,positions,associations,languages,honors,educations,date-of-birth,primary-twitter-account,three-current-positions,three-past-positions,group-memberships,specialties,skills)
The response of your request from LinkedIn will be a xml file. The format and fields are mentioned in LinkedIn Profile Fields
For getting email field, you need to modify your request token url as
RequestTokenEndpoint = new MessageReceivingEndpoint("https://api.linkedin.com/uas/oauth/requestToken?scope=r_fullprofile+r_emailaddress",
HttpDeliveryMethods.PostRequest),
You can get the fields as required in the following code
XDocument document = LoadXDocumentFromStream(responseStream);
Eg : For getting the first name field from the xml file,
var firstName = document.Root.Element("first-name").Value;
Fields like languages, positions, skills etc will be returned as structured objects as part of the profile.
Eg : Language field.
var Lang = document.Root.Element("languages");
var languages = new List<string>();
if (Lang != null)
{
foreach (var l in Lang.Elements())
{
if (l.Element("language") != null && l.Element("language").Element("name") != null)
{
languages.Add(l.Element("language").Element("name").Value);
}
}
}
Then you can add fields to "extraData" which can be accessed in the controller.
extraData.Add("firstName", firstName);
extraData.Add("languages", lang);