So, here is the problem. I searched all over the MSDN and here on Stack but there isn't one definitive answer how to get (or is it even possible as of today?) to access Shared calendars in Office365.
I followed this tutorial and here is offending method:
public async Task<ActionResult> Index()
{
List<MyCalendar> myCalendars = new List<MyCalendar>();
var signInUserId = ClaimsPrincipal.Current.FindFirst(ClaimTypes.NameIdentifier).Value;
var userObjectId = ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier").Value;
AuthenticationContext authContext = new AuthenticationContext(SettingsHelper.Authority, new ADALTokenCache(signInUserId));
try
{
DiscoveryClient discClient = new DiscoveryClient(SettingsHelper.DiscoveryServiceEndpointUri,
async () =>
{
var authResult = await authContext.AcquireTokenSilentAsync(SettingsHelper.DiscoveryServiceResourceId, new ClientCredential(SettingsHelper.ClientId, SettingsHelper.AppKey), new UserIdentifier(userObjectId, UserIdentifierType.UniqueId));
return authResult.AccessToken;
});
var discoveryCapabilitiesResult = await discClient.DiscoverCapabilitiesAsync();
var dcr = await discClient.DiscoverCapabilityAsync("Calendar");
OutlookServicesClient exClient = new OutlookServicesClient(dcr.ServiceEndpointUri,
async () =>
{
var authResult = await authContext.AcquireTokenSilentAsync(dcr.ServiceResourceId, new ClientCredential(SettingsHelper.ClientId, SettingsHelper.AppKey), new UserIdentifier(userObjectId, UserIdentifierType.UniqueId));
return authResult.AccessToken;
});
//var calendarsResult = await exClient.Me.Calendars.ExecuteAsync();
var calendarsResult = await exClient.Me.Calendars.ExecuteAsync();
do
{
var calendars = calendarsResult.CurrentPage;
foreach (var c in calendars)
{
myCalendars.Add(new MyCalendar { DisplayName = c.Name });
}
calendarsResult = await calendarsResult.GetNextPageAsync();
} while (calendarsResult != null);
}
catch (AdalException exception)
{
//handle token acquisition failure
if (exception.ErrorCode == AdalError.FailedToAcquireTokenSilently)
{
authContext.TokenCache.Clear();
//handle token acquisition failure
}
}
return View(myCalendars);
}
This function will return only Calendar under "My Calendars" but not the others (see the picture)
I get only the first one - that is mine calendar. Second one under "My Calendars" is shared with me (I'm not the author, somebody else is and I have r/w on it) and one under "Shared Calendars" is company-wide one (on this one I also have r/w permissions).
Is there a way to get all of them? On portal.azure.com my app is added and I set permissions for both mine and shared calendars:
I have no idea what to do. Contacts are working properly but I cannot find a way to get any shared calendar.
Based on the test, the Microsoft Graph could get the all calendars. Here is the rest for your reference:
Shared calendar from chenchenLi to NanYu:
Query the calendars from NanYu:
More detail about the Microsoft Graph REST to get the calendars, you can refer the link below:
List calendars
Related
I am trying to create Online Meeting using microsoft graph api without login into AzureActiveDirectory with asp.net web application.For this my app has below permissions which are required as per documentation https://learn.microsoft.com/en-us/graph/api/application-post-onlinemeetings?view=graph-rest-1.0&tabs=csharp with client credential auth flow https://learn.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-client-creds-grant-flow without immediate interaction with a user.I am able to retrive access token successfully as per client-creds-grant-flow.
I tried Micosoft.Graph and Micosoft.Graph.Beta still getting 404 error.
Create online meeting code
var graphClient = GetAuthenticatedClientCredential();
var onlineMeeting = new OnlineMeeting
{
StartDateTime = DateTimeOffset.Parse("2020-10-01T10:30:34.2444915+00:00"),
EndDateTime = DateTimeOffset.Parse("2020-10-01T11:00:34.2464912+00:00"),
Subject = "Create Online Meeting-Without user login to Office 365"
};
return await graphClient.Me.OnlineMeetings
.Request()
.AddAsync(onlineMeeting);
Access Token code
public static async Task<string> GetUserAccessTokenAsyncByCc()
{
IConfidentialClientApplication cca = ConfidentialClientApplicationBuilder.Create(appId)
.WithTenantId(appTenantId)
.WithClientSecret(appSecret)
.Build();
string[] scopes1 = new string[] { "https://graph.microsoft.com/.default" };
//string[] scopes1 = new string[] { "https://graph.microsoft.com/OnlineMeetings.ReadWrite.All" };
// string[] scopes1 = new string[] { "https://graph.microsoft.com/beta/OnlineMeetings.Read.All" };
//string[] scopes1 = new string[] { "https://graph.microsoft.com/beta/.default" };
var result = await cca.AcquireTokenForClient(scopes1).ExecuteAsync();
return result.AccessToken;
}
and Auth Provider code
public static GraphServiceClient GetAuthenticatedClientCredential()
{
DelegateAuthenticationProvider provider = new DelegateAuthenticationProvider(
async (requestMessage) =>
{
string accessToken = await GetUserAccessTokenAsyncByCc();
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", accessToken);
});
GraphServiceClient graphClient = new GraphServiceClient(provider);
return graphClient;
}
app permission image
below are the necessary app permission
You can only use delegated permissions to create an onlineMeeting, so you must log in as a user, and you cannot use the client credential flow. You need to use the auth code flow to obtain the token.
I have the exact same issue as in this question:
How do I set return_uri for GoogleWebAuthorizationBroker.AuthorizeAsync?
However, that question was answered 3 years ago and the answer provided isn't working for me; I see no way to actually set the redirect uri. So here's the issue:
static async Task<UserCredential> GetCredential()
{
var clientSecretPath = HttpRuntime.AppDomainAppPath + "client_secret.json";
var credPath = HttpRuntime.AppDomainAppPath + "credentials/GoogleAnalyticsApiConsole/";
UserCredential credential;
using (var stream = new FileStream(clientSecretPath,
FileMode.Open, FileAccess.Read))
{
var secrets = GoogleClientSecrets.Load(stream).Secrets;
credential = await GoogleWebAuthorizationBroker.AuthorizeAsync(
secrets,
new[] {AnalyticsReportingService.Scope.Analytics},
"analytics#mysite.com",
CancellationToken.None,
new FileDataStore(credPath, true));
return credential;
}
}
This is returning the following error:
failed to launch browser with https //accounts.google.com/o/oauth2/v2/auth
It's trying to launch the oauth2 page with redirect_uri = "http://localhost:/authorize"; when I try to directly view the url it's trying to launch, the page says "The redirect URI in the request: http://localhost:XXXXX/authorize/ did not match a registered redirect URI"
I tried just adding localhost:XXXXX to the authorized urls in the Google API Console, but the next time I ran it the port was different, like localhost:XXXYY. My client_secret.json file has all of the authorized redirect urls listed, but aren't being used. How do I set the redirect uri and fix this issue?
I am not sure if you are trying to build a locally installed application or a asp.net web application, but based on the question you have pointed to, i assume its a web application and here's how i solved it.
First of all, GoogleWebAuthorizationBroker's default implementation is for locally installed application. You can find its implementation in this link
.As a result your code might work fine in your local machine but when hosted in webserver it might load forever.
So you need to implement your own AuthorizationCodeFlow for Web applications as mentioned in the google's documentation.
And this is how i implemented it in my ASP.NET Core MVC web application
public async Task<IActionResult> ConfigureGA(CancellationToken cancellationToken)
{
GoogleAnalyticsModel model = new GoogleAnalyticsModel();
var state = UriHelper.GetDisplayUrl(Request);
var result = await GetCredential(state, cancellationToken);
if (result.Credential != null)
{
using (var svc = new AnalyticsService(
new BaseClientService.Initializer
{
HttpClientInitializer = result.Credential,
ApplicationName = "Your App Name"
}))
{
ManagementResource.AccountSummariesResource.ListRequest list = svc.Management.AccountSummaries.List();
list.MaxResults = 1000;
AccountSummaries feed = await list.ExecuteAsync();
model.UserAccounts = feed.Items.ToList();
}
return View(model);
}
else
{
return new RedirectResult(result.RedirectUri);
}
}
private async Task<AuthorizationCodeWebApp.AuthResult> GetCredential(string state, CancellationToken cancellationToken)
{
var userId = userManager.GetUserAsync(User).Result.Id;
var redirectUri = Request.Scheme + "://" + Request.Host.ToUriComponent() + "/authcallback/";
using (var stream = new FileStream("client_secret.json",
FileMode.Open, FileAccess.Read))
{
IAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow(new GoogleAuthorizationCodeFlow.Initializer
{
ClientSecrets = GoogleClientSecrets.Load(stream).Secrets,
Scopes = new[] { AnalyticsService.Scope.AnalyticsReadonly, AnalyticsReportingService.Scope.AnalyticsReadonly },
DataStore = datastore
});
return await new AuthorizationCodeWebApp(flow, redirectUri, state)
.AuthorizeAsync(userId, cancellationToken);
}
}
I am passing the state of the application while requesting oauth credential(i.e., GetCredential() method).
In this method, create your own IAuthorizationCodeFlow and pass your flow, redirect_uri(which you must also set in your Google Developer Console) and the state of your application to AuthorizationCodeWebApp
Next you must implement the authcallback controller to handle the oauth code. This code is similar to the google-dotnet-client library found here but i have adopted the same code because i am working with Microsoft.AspNetCore.Mvc
public class AuthCallbackController : Controller
{
private readonly UserManager<ApplicationUser> userManager;
private readonly IGoogleAnalyticsDataStore datastore;
public AuthCallbackController(UserManager<ApplicationUser> userManager, IGoogleAnalyticsDataStore datastore)
{
this.userManager = userManager;
this.datastore = datastore;
}
protected virtual ActionResult OnTokenError(TokenErrorResponse errorResponse)
{
throw new TokenResponseException(errorResponse);
}
public async virtual Task<ActionResult> Index(AuthorizationCodeResponseUrl authorizationCode,
CancellationToken taskCancellationToken)
{
using (var stream = new FileStream("client_secret.json",
FileMode.Open, FileAccess.Read))
{
IAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow(
new GoogleAuthorizationCodeFlow.Initializer
{
ClientSecrets = GoogleClientSecrets.Load(stream).Secrets,
DataStore = datastore,
Scopes = new[] { AnalyticsService.Scope.AnalyticsReadonly, AnalyticsReportingService.Scope.AnalyticsReadonly }
});
if (string.IsNullOrEmpty(authorizationCode.Code))
{
var errorResponse = new TokenErrorResponse(authorizationCode);
return OnTokenError(errorResponse);
}
string userId = userManager.GetUserAsync(User).Result.Id;
var returnUrl = UriHelper.GetDisplayUrl(Request);
var token = await flow.ExchangeCodeForTokenAsync(userId, authorizationCode.Code, returnUrl.Substring(0, returnUrl.IndexOf("?")),
taskCancellationToken).ConfigureAwait(false);
// Extract the right state.
var oauthState = await AuthWebUtility.ExtracRedirectFromState(datastore, userId,
authorizationCode.State).ConfigureAwait(false);
return new RedirectResult(oauthState);
}
}
}
Hope this answers your question and a little beyond the scope of the question.
I had the same problem today.
It turned out that there was no "default" Program set for handling http/https protocol.
var outlookServicesClient = await AuthenticationHelper.EnsureOutlookServicesClientCreatedAsync("Calendar");
internal static async Task<OutlookServicesClient> EnsureOutlookServicesClientCreatedAsync(string capabilityName)
{
var signInUserId = ClaimsPrincipal.Current.FindFirst(ClaimTypes.NameIdentifier).Value;
var userObjectId = ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier").Value;
AuthenticationContext authContext = new AuthenticationContext(SettingsHelper.Authority, new ADALTokenCache(signInUserId));
try
{
DiscoveryClient discClient = new DiscoveryClient(SettingsHelper.DiscoveryServiceEndpointUri,
async () =>
{
var authResult = await authContext.AcquireTokenSilentAsync(SettingsHelper.DiscoveryServiceResourceId, new ClientCredential(SettingsHelper.ClientId, SettingsHelper.ClientSecret),
new UserIdentifier(userObjectId, UserIdentifierType.UniqueId));
return authResult.AccessToken;
});
var dcr = await discClient.DiscoverCapabilityAsync(capabilityName);
return new OutlookServicesClient(dcr.ServiceEndpointUri,
async () =>
{
var authResult = await authContext.AcquireTokenSilentAsync(dcr.ServiceResourceId,
new ClientCredential(SettingsHelper.ClientId, SettingsHelper.ClientSecret),
new UserIdentifier(userObjectId, UserIdentifierType.UniqueId));
return authResult.AccessToken;
});
}
catch (AdalException exception)
{
//Handle token acquisition failure
if (exception.ErrorCode == AdalError.FailedToAcquireTokenSilently)
{
authContext.TokenCache.Clear();
throw exception;
}
return null;
}
public ADALTokenCache(string user)
{
// associate the cache to the current user of the web app
User = user;
this.AfterAccess = AfterAccessNotification;
this.BeforeAccess = BeforeAccessNotification;
this.BeforeWrite = BeforeWriteNotification;
// look up the entry in the DB
Cache = db.UserTokenCacheList.FirstOrDefault(c => c.webUserUniqueId == User);
// place the entry in memory
this.Deserialize((Cache == null) ? null : Cache.cacheBits);
}
i am using this code for ADAL authentication. This is working fine in my local IIS server. When i hosted the same on AZURE VM then getting an error like
"Failed to acquire token silently. Call method AcquireToken". Can anybody help me on resolving this error??
Settings Helper code as follows. In public ADALTokenCache(string user) we are getting userid finely but getting an empty cache... What will be the reason??
AuthenticationContext authContext = new AuthenticationContext(SettingsHelper.Authority, new ADALTokenCache(signInUserId));
try
{
DiscoveryClient discClient = new DiscoveryClient(SettingsHelper.DiscoveryServiceEndpointUri,
async () =>
{
var authResult = await authContext.AcquireTokenSilentAsync(SettingsHelper.DiscoveryServiceResourceId,
new ClientCredential(SettingsHelper.ClientId,
SettingsHelper.ClientSecret),
new UserIdentifier(userObjectId,
UserIdentifierType.UniqueId));
return authResult.AccessToken;
});
Make sure that your authority does not contain "common". Also, please turn on the diagnostics as explained in http://www.cloudidentity.com/blog/2015/08/07/adal-diagnostics/ and take a look at the trace. Very often this is due to a mismatch in the cache - acquiretokensilent only works with cached tokens, and if you didn't seed the cache/you are not working against the cache instance you selected earlier/you pass a different user identifier/you pass common as authority you'll get a cache miss.
I assume you were using the O365-ASPNETMVC-Start project on Github.
What's the "ida:TenantId" setting in your web.config file on Azure VM?
I can get the same error "Failed to acquire token silently. Call method AcquireToken" if setting the "ida:TenantId" to "common". For this scenerio, you need to set "ida:TenantId" to actual tenant id. For example, "e07xxxx0e-fxx2-441f-ad9a-9dxxa59xxx52" (guid).
I am currently building an Office365 Website which manages your files within onedrive and your sharepoint files. Currently though I am having an issue in that if the sharepoint isn't a root address one (i.e. https://mysharepoint.sharepoint.com) but is a seperate one (i.e. https://intergendev1.sharepoint.com/anothersite or https://intergendev1.sharepoint.com/sites/moarsite) I cannot acquire the token silently, in other words I cannot use my token from the Office API to acquire a token for the sharepoint site. My theory is that I am using the incorrect ResourceId or ServiceEndpointUri, currently I just use https://intergendev1.sharepoint.com/anothersite for both, and I am un-able to aquire the token, but it DOES work for https://intergendev1.sharepoint.com/.
Thanks for the help in advance. If it helps, here is my AquireToken code:
internal static async Task<SharepointApiClient> GetExtenalSharepoint(Uri serviceEndpointUri,
string serviceResourceId)
{
var signInUserId = ClaimsPrincipal.Current.FindFirst(ClaimTypes.NameIdentifier).Value;
var userObjectId = ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier").Value;
AuthenticationContext authContext = new AuthenticationContext(SettingsHelper.AzureADAuthority, new AdTokenCache.AdTokenCache(signInUserId));
try
{
return new SharepointApiClient(serviceEndpointUri,
async () =>
{
var authResult = await authContext.AcquireTokenSilentAsync(serviceResourceId,
new ClientCredential(SettingsHelper.ClientId,
SettingsHelper.AppKey),
new UserIdentifier(userObjectId,
UserIdentifierType.UniqueId));
return authResult.AccessToken;
})
{
ResourceId = serviceResourceId
};
}
catch (AdalException exception)
{
//Partially handle token acquisition failure here and bubble it up to the controller
if (exception.ErrorCode == AdalError.FailedToAcquireTokenSilently)
{
authContext.TokenCache.Clear();
throw exception;
}
return null;
}
}
Perhaps I am the only one that thinks Google's API documentation is awful but I've spent more time on this simple task than I wanted.
Currently my project is using a GDATA implementation to connect with the Google Calendar API v2. I followed this guide: http://www.codeproject.com/Articles/565032/Google-Calendar-Integration-in-ASP-NET-Create-ed
But I noticed that Google is deprecating version 2 of their API this fall. I am trying to figure out how I can connect to their version 3 API which appears to be using OAuth2.
After reading their documentation and searching the internet >:( - The problem I keep running into is EVERY sample, tutorial or youtube video I've come across that shows how to implement this involve the Google consent screen where the user clicks "Accept".
I've tried doing the following but honestly not sure if it's even the right direction?
// Register the authenticator. The Client ID and secret have to be copied from the API Access
// tab on the Google APIs Console.
var provider = new NativeApplicationClient(GoogleAuthenticationServer.Description);
provider.ClientIdentifier = "MY_CLIENT_ID";
provider.ClientSecret = "MY_CLIENT_SECRET";
// Create the service. This will automatically call the previously registered authenticator.
var service = new CalendarService();
My application doesn't need the user's account/consent (OAuth), I need to connect like I am currently in my code-behind.
So the question is how do I "upgrade" my current implementation to v3? Do I use OAuth, Service Account? I've found plenty of examples showing the v3 usages for how to retrieve events and insert them... but they all authenticate with a user consent screen on the front end.
Here is my current GData implementation...
public class GoogleGateway : IGoogleGateway
{
private readonly IRepository<UserSetting> _settingsRepository;
private Service _googleService;
private CalendarService _googleCalendar;
private Uri _calendarUri;
public GoogleGateway(IRepository<UserSetting> settingsRepository)
{
_settingsRepository = settingsRepository;
}
public IEnumerable<EventEntry> GetAllEvents(DateTime? startDate)
{
if (!Connect()) return new List<EventEntry>();
// Create the query object:
EventQuery query = new EventQuery();
query.Uri = _calendarUri;
if (startDate != null)
query.StartTime = startDate.Value;
// Tell the service to query:
EventFeed calFeed = _googleCalendar.Query(query);
return calFeed.Entries.Cast<EventEntry>();
}
public bool Connect()
{
var calSettings = _settingsRepository.Get().Where(x => x.Setting == "Calendar");
if (calSettings.Any())
{
var username = calSettings.First(x => x.Meta == "GoogleUsername").Value;
var password = calSettings.First(x => x.Meta == "GooglePassword").Value;
var calendarUri = new Uri(calSettings.First(x => x.Meta == "CalendarFeed").Value);
var applicationName = calSettings.First(x => x.Meta == "ApplicationName").Value;
_calendarUri = calendarUri;
//FeedQuery feedQuery = new FeedQuery();
_googleService = new Service("cl", applicationName);
_googleCalendar = new CalendarService(applicationName);
// Set your credentials:
_googleService.setUserCredentials(username, password);
_googleCalendar.setUserCredentials(username, password);
return true;
}
return false;
}
public void AddEvent(string title, string contents, string location, DateTime startTime, DateTime endTime)
{
if (!Connect()) return;
EventEntry.EVENT_CATEGORY = new AtomCategory("Appointments");
EventEntry entry = new EventEntry
{
Title = { Text = title },
Content = { Content = contents },
};
// Set the title and content of the entry.
// Set a location for the event.
Where eventLocation = new Where();
eventLocation.ValueString = location;
entry.Locations.Add(eventLocation);
When eventTime = new When(startTime, endTime);
entry.Times.Add(eventTime);
Uri postUri = new Uri("http://www.google.com/calendar/feeds/default/private/full");
// Send the request and receive the response:
AtomEntry insertedEntry = _googleCalendar.Insert(postUri, entry);
}
public void DeleteEvent(string eventId)
{
if (!Connect()) return;
var events = GetAllEvents(null);
var appointment = events.First(x => x.EventId == eventId);
_googleService.Delete(appointment);
}
}
I'm growing desperate at this point, any help would be very appreciated. Include your twitter handle in your answer and I'll buy you a coffee!
UPDATED
I currently have the following, but I is still not authenticating... :(
static CalendarService BuildService()
{
String serviceAccountEmail = "xxxxxxxxxxxxx-31xxxxxxxxxxxxxxxxxxx#developer.gserviceaccount.com";
var certPath = HttpContext.Current.Server.MapPath("/xxxxxxxxxxxx.p12");
var certificate = new X509Certificate2(certPath, "notasecret", X509KeyStorageFlags.Exportable);
ServiceAccountCredential credential = new ServiceAccountCredential(
new ServiceAccountCredential.Initializer(serviceAccountEmail)
{
Scopes = new[] { CalendarService.Scope.Calendar }
}.FromCertificate(certificate));
// Create the service.
var service = new CalendarService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential, <<<<<< DOES NOT RESOLVE!
ApplicationName = "MyApplication",
});
var test = service.Calendars.Get("xxxxxxxxxxxxxxxxxx#group.calendar.google.com");
return service;
}
The problem is that you are storing credentials in plaintext. In Oauth2 the users won't give you their credentials (thus access to everything) but instead they enable your app to access the data of a specific type / scope.
It's not clear from your description whether you only ever access one calendar fully in your control or you have multiple users. In the first case the answer would be use service account (https://developers.google.com/accounts/docs/OAuth2ServiceAccount). In the second case if you are a calendar app with many users, you will need to go down the user consent road and you should read on :)
For offline access you can specify that the access_type should be offline when retrieving the credentials for the first time. Together with the access token you'll also get a refresh token, which you can use to re-authenticate at any later time without any more user clicks (https://developers.google.com/accounts/docs/OAuth2WebServer#refresh). However, at least one consent screen it is.