It looks like the Application object in Microsoft.Azure.ActiveDirectory.GraphClient allows a Webapplication to be created. I cannot see how I can use this to create a new Native application.
thanks
Updates:
TaskCompletionSource<string> tcs = new TaskCompletionSource<string>();
tcs.SetResult(accessToken);
var graphClient = new ActiveDirectoryClient(
new Uri($"{GraphApiBaseUrl}{tenantId}"),
async () => { return await tcs.Task; });
var password = Guid.NewGuid().ToString("N");
var cred = new PasswordCredential()
{
StartDate = DateTime.UtcNow,
EndDate = DateTime.UtcNow.AddYears(1),
Value = password
};
var app = await GetApplicationByUrlAsync(accessToken, tenantId, appName, identifierUrl);
if(app == null)
{
app = new Application()
{
DisplayName = appName,
Homepage = homePageUrl,
IdentifierUris = new List<string>() { identifierUrl },
LogoutUrl = logoutUrl,
ReplyUrls = new List<string>() { replyUrl },
PasswordCredentials = new List<PasswordCredential>() { cred },
};
await graphClient.Applications.AddApplicationAsync(app);
}
The fact that an app is a native client application is identified by the PublicClient boolean property on the Application object. (See client types from the OAuth 2.0 spec.)
So, you could register a native client app with the following code:
var app = new Application()
{
DisplayName = "My native client app",
ReplyUrls = new List<string>() { "urn:ietf:wg:oauth:2.0:oob" },
PublicClient = true
};
await graphClient.Applications.AddApplicationAsync(app);
Console.WriteLine("App created. AppId: {0}, ObjectId: {1}", app.AppId, app.ObjectId);
Note that the native client app does not have password credentials or key credentials (or any other secret).
Details about these and other properties of Application objects are available in the API reference documentation: https://msdn.microsoft.com/en-us/library/azure/ad/graph/api/entity-and-complex-type-reference#application-entity
Related
I'm trying to create a meeting as a application, add attendees, determine time availability.
This is what I have so far :
Auth
private async Task<ClientCredentialProvider> GetToken()
{
var confidentialClientApplication = ConfidentialClientApplicationBuilder
.Create(_microsoftAppId)
.WithTenantId(_microsoftTenantId)
.WithClientSecret(_microsoftAppPassword)
.Build();
ClientCredentialProvider authProvider = new ClientCredentialProvider(confidentialClientApplication);
return authProvider;
}
Meeting request
public async Task GetMeetingtime()
{
var authProvider = await GetToken();
GraphServiceClient graphClient = new GraphServiceClient(authProvider);
var attendees = new List<AttendeeBase>()
{
new AttendeeBase
{
Type = AttendeeType.Required,
EmailAddress = new EmailAddress
{
Name = "john doe",
Address = "john.doe#onmicrosoft.com"
}
},
new AttendeeBase
{
Type = AttendeeType.Required,
EmailAddress = new EmailAddress
{
Name = "john toe",
Address = "john.toe#onmicrosoft.com"
}
}
};
var locationConstraint = new LocationConstraint
{
IsRequired = false,
SuggestLocation = false,
Locations = new List<LocationConstraintItem>()
{
new LocationConstraintItem
{
ResolveAvailability = false,
DisplayName = "Conf room Hood"
}
}
};
var timeConstraint = new TimeConstraint
{
ActivityDomain = ActivityDomain.Work,
TimeSlots = new List<TimeSlot>()
{
new TimeSlot
{
Start = new DateTimeTimeZone
{
DateTime = "2020-12-10T09:00:00",
TimeZone = "Pacific Standard Time"
},
End = new DateTimeTimeZone
{
DateTime = "2020-12-10T17:00:00",
TimeZone = "Pacific Standard Time"
}
}
}
};
var isOrganizerOptional = false;
var meetingDuration = new Duration("PT1H");
var returnSuggestionReasons = true;
var minimumAttendeePercentage = (double)100;
await graphClient
.Me
.FindMeetingTimes(attendees, locationConstraint, timeConstraint, meetingDuration, null, isOrganizerOptional, returnSuggestionReasons, minimumAttendeePercentage)
.Request()
.Header("Prefer", "outlook.timezone=\"Pacific Standard Time\"")
.PostAsync();
}
This is the error I get:
Current authenticated context is not valid for this request. This occurs when a request is made to an endpoint that requires user sign-in. For example, /me requires a signed-in user. Acquire a token on behalf of a user to make requests to these endpoints. Use the OAuth 2.0 authorization code flow for mobile and native apps and the OAuth 2.0 implicit flow for single-page web apps.
How can I solve this?
According to docs, the findMeetingTimes API does not support application-only access: https://learn.microsoft.com/en-us/graph/api/user-findmeetingtimes?view=graph-rest-1.0&tabs=http.
It can only be called in the context of a signed-in user.
Now, you could try to use .Users["user-id"] instead of .Me just in case the docs are wrong about this.
"me" only makes sense when calling the API on behalf of a user, which you are not.
Calendar events can be created as an application that has write access to the users' calendars.
When calling GitHttpClient.CreateAnnotatedTagAsync, I keep getting "VS30063: You are not authorized to access https://dev.azure.com" even though my credentials are valid.
This is my code:
var vssConnection = new VssConnection(new Uri("ORG_URI"), new VssBasicCredential(string.Empty, "PAT"));
var gitClient = vssConnection.GetClient<GitHttpClient>();
var tag = new GitAnnotatedTag
{
Name = "tagname",
Message = "A new tag",
TaggedBy = new GitUserDate
{
Name = "Name",
Email = "Email",
Date = DateTime.Now,
ImageUrl = null
},
ObjectId = "SHA",
TaggedObject = new GitObject
{
ObjectId = "SHA",
ObjectType = GitObjectType.Commit
},
Url = string.Empty
};
var sourceRepo = await gitClient.GetRepositoryAsync("PROJECT", repoName);
// This call works
var tags = await gitClient.GetTagRefsAsync(sourceRepo.Id);
// The tag is printed to the console
Console.WriteLine(tags.First().Name);
// This throws "VS30063: You are not authorized to access https://dev.azure.com"
await gitClient.CreateAnnotatedTagAsync(tag, "PROJECT", sourceRepo.Id);
There is an issue with your PAT token. I just created a tag using your code and PAT with FULL Access:
Can you create a new token and try again?
I'm using PeopleService in order to create contacts straight into my G Suit Account. I followed the security steps about getting the key for a type of Service Account. My Application will create contacts so that there's no need to request a specific user permission. It has its own key and credentials.
My code seems to work except because the CreateContactRequest provides me ResourceName with value "people/c171255166120767303". And every time I request I got a different resourceName like this "people/c9013378989213012841".
The problem is, where the hell that goes? At my G Suite account, I can’t see the created contact anywhere.
But the resulting Person object seems to be ok.
How can I check if this works? Where the contact was created?
The code is as bellow:
private static string _clientId = "1........1";
private static string _clienteScret = "i******************_";
private static string _serviceAccountId = "aaaa#bbbb.iam.gserviceaccount.com";
public static void Cadastrar(Models.SignupRequest message)
{
var chave =
#"D:\********.p12";
var certificate = new X509Certificate2(chave, "notasecret", X509KeyStorageFlags.Exportable);
var credential = new ServiceAccountCredential(
new ServiceAccountCredential.Initializer(_serviceAccountId)
{
Scopes = new[] {
PeopleService.Scope.Contacts,
PeopleService.Scope.ContactsReadonly,
"https://www.google.com/m8/feeds"
}
}.FromCertificate(certificate));
var service = new PeopleService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = "Client for www",
});
var contactToCreate = new Person();
var names = new List<Name> {new Name() {GivenName = message.name, FamilyName = "Doe"}};
contactToCreate.Names = names;
contactToCreate.EmailAddresses = new List<EmailAddress>
{
new EmailAddress()
{
DisplayName = message.name,
Value = message.email
}
};
contactToCreate.Organizations = new List<Organization>
{
new Organization()
{
Current = true,
Name = message.nome_fantasia,
}
};
contactToCreate.Biographies = new List<Biography>
{
new Biography()
{
Value = message.ToString()
}
};
contactToCreate.PhoneNumbers = new List<PhoneNumber>
{
new PhoneNumber()
{
Type = "mobile",
Value = message.celular
},
new PhoneNumber()
{
Type = "work",
Value = message.telefone
}
};
var request = new Google.Apis.PeopleService.v1.PeopleResource.CreateContactRequest(service, contactToCreate);
var createdContact = request.Execute();
}
If you want to create contact for user with 'email#yourdomain.com' through a service account you need:
Use "setServiceAccountUser(userEmail)" to impersonate the user
Enable impersonation on your G-Suite for People API service. For this point:
See section "Delegating domain wide access" here
Get client-id from your credential account
Use "https://www.googleapis.com/auth/contacts" as scope.
I followed this instruction (Using Azure GraphClient API how can you create a new Native Application?) and I could create a native application by Azure Graph Client. However, I don't know how to grant permissions by Graph API.
This is my code:
var app = new Application()
{
DisplayName = "appNativeName",
Homepage = "https://stackoverflow.com",
RequiredResourceAccess = new List<RequiredResourceAccess>()
{
new RequiredResourceAccess {
ResourceAppId = "00000002-0000-0000-c000-000000000000", //Azure AD
ResourceAccess = new List<ResourceAccess>
{
new ResourceAccess {Id = Guid.Parse("my guid id"), Type = "Scope" }
}
}
},
PublicClient = true //native app
};
await azureADContext.Applications.AddApplicationAsync(app);
var obj = app.ObjectId;
var appId = app.AppId;
I really appreciate your help.
I'm trying to use Google Calendar API v3, but i have problems while running the codes, it always gives me that error :
An exception of type 'System.AggregateException' occurred in mscorlib.ni.dll but was not handled in user code
Additional information: One or more errors occurred.
I don't know why it does, also It should work as well. Here is a screenshot for it :
Also my codes are :
UserCredential credential;
credential = GoogleWebAuthorizationBroker.AuthorizeAsync(
new Uri("ms-appx:///Assets/client_secrets.json"),
Scopes,
"user",
CancellationToken.None).Result;
// Create Google Calendar API service.
var service = new CalendarService(new BaseClientService.Initializer()
{
HttpClientInitializer = credential,
ApplicationName = ApplicationName,
});
var calendarService = new CalendarService(new BaseClientService.Initializer
{
HttpClientInitializer = credential,
ApplicationName = "Windows 10 Calendar sample"
});
var calendarListResource = await calendarService.CalendarList.List().ExecuteAsync();
If you can at least help with calling it through REST API, that would be great too, but you must consider that it's UWP, so it has another way to get it work as well.
As i already tried through REST API, but i always get "Request error code 400".
Thanks for your attention.
The Google API Client Library for .NET does not support UWP by now. So we can't use Google.Apis.Calendar.v3 Client Library in UWP apps now. For more info, please see the similar question: Universal Windows Platform App with google calendar.
To use Google Calendar API in UWP, we can call it through REST API. To use the REST API, we need to authorize requests first. For how to authorize requests, please see Authorizing Requests to the Google Calendar API and Using OAuth 2.0 for Mobile and Desktop Applications.
After we have the access token, we can call Calendar API like following:
var clientId = "{Your Client Id}";
var redirectURI = "pw.oauth2:/oauth2redirect";
var scope = "https://www.googleapis.com/auth/calendar.readonly";
var SpotifyUrl = $"https://accounts.google.com/o/oauth2/auth?client_id={clientId}&redirect_uri={Uri.EscapeDataString(redirectURI)}&response_type=code&scope={Uri.EscapeDataString(scope)}";
var StartUri = new Uri(SpotifyUrl);
var EndUri = new Uri(redirectURI);
// Get Authorization code
WebAuthenticationResult WebAuthenticationResult = await WebAuthenticationBroker.AuthenticateAsync(WebAuthenticationOptions.None, StartUri, EndUri);
if (WebAuthenticationResult.ResponseStatus == WebAuthenticationStatus.Success)
{
var decoder = new WwwFormUrlDecoder(new Uri(WebAuthenticationResult.ResponseData).Query);
if (decoder[0].Name != "code")
{
System.Diagnostics.Debug.WriteLine($"OAuth authorization error: {decoder.GetFirstValueByName("error")}.");
return;
}
var autorizationCode = decoder.GetFirstValueByName("code");
//Get Access Token
var pairs = new Dictionary<string, string>();
pairs.Add("code", autorizationCode);
pairs.Add("client_id", clientId);
pairs.Add("redirect_uri", redirectURI);
pairs.Add("grant_type", "authorization_code");
var formContent = new Windows.Web.Http.HttpFormUrlEncodedContent(pairs);
var client = new Windows.Web.Http.HttpClient();
var httpResponseMessage = await client.PostAsync(new Uri("https://www.googleapis.com/oauth2/v4/token"), formContent);
if (!httpResponseMessage.IsSuccessStatusCode)
{
System.Diagnostics.Debug.WriteLine($"OAuth authorization error: {httpResponseMessage.StatusCode}.");
return;
}
string jsonString = await httpResponseMessage.Content.ReadAsStringAsync();
var jsonObject = Windows.Data.Json.JsonObject.Parse(jsonString);
var accessToken = jsonObject["access_token"].GetString();
//Call Google Calendar API
using (var httpRequest = new Windows.Web.Http.HttpRequestMessage())
{
string calendarAPI = "https://www.googleapis.com/calendar/v3/users/me/calendarList";
httpRequest.Method = Windows.Web.Http.HttpMethod.Get;
httpRequest.RequestUri = new Uri(calendarAPI);
httpRequest.Headers.Authorization = new Windows.Web.Http.Headers.HttpCredentialsHeaderValue("Bearer", accessToken);
var response = await client.SendRequestAsync(httpRequest);
if (response.IsSuccessStatusCode)
{
var listString = await response.Content.ReadAsStringAsync();
//TODO
}
}
}
I have the Google .NET Client working in my UWP app. The trick is that you have to put it in a .NET Standard 2.0 Class Library, expose the API services you need, and then reference that library from your UWP app.
Also, you have to handle the getting the auth token yourself. It's not that much work and the Drive APIs and Calendar APIs work just fine (the only ones I've tried). You can see that I pass in a simple class that contains the auth token and other auth details to a method called Initialize.
Here is the single class I used in the .NET Standard 2.0 class library:
namespace GoogleProxy
{
public class GoogleService
{
public CalendarService calendarService { get; private set; }
public DriveService driveService { get; private set; }
public GoogleService()
{
}
public void Initialize(AuthResult authResult)
{
var credential = GetCredentialForApi(authResult);
var baseInitializer = new BaseClientService.Initializer { HttpClientInitializer = credential, ApplicationName = "{your app name here}" };
calendarService = new Google.Apis.Calendar.v3.CalendarService(baseInitializer);
driveService = new Google.Apis.Drive.v3.DriveService(baseInitializer);
}
private UserCredential GetCredentialForApi(AuthResult authResult)
{
var initializer = new GoogleAuthorizationCodeFlow.Initializer
{
ClientSecrets = new ClientSecrets
{
ClientId = "{your app client id here}",
ClientSecret = "",
},
Scopes = new string[] { "openid", "email", "profile", "https://www.googleapis.com/auth/calendar.readonly", "https://www.googleapis.com/auth/calendar.events.readonly", "https://www.googleapis.com/auth/drive.readonly" },
};
var flow = new GoogleAuthorizationCodeFlow(initializer);
var token = new TokenResponse()
{
AccessToken = authResult.AccessToken,
RefreshToken = authResult.RefreshToken,
ExpiresInSeconds = authResult.ExpirationInSeconds,
IdToken = authResult.IdToken,
IssuedUtc = authResult.IssueDateTime,
Scope = "openid email profile https://www.googleapis.com/auth/calendar.readonly https://www.googleapis.com/auth/calendar.events.readonly https://www.googleapis.com/auth/drive.readonly",
TokenType = "bearer" };
return new UserCredential(flow, authResult.Id, token);
}
}
}
In order to get the Auth token from google, you have to use custom schemes. Register your app as an 'iOS' app on the google services console and put in a URI scheme (something unique). Then add this scheme to your UWP manifest under Declarations->Protocol. Handle it in your App.xaml.cs:
protected override void OnActivated(IActivatedEventArgs args)
{
base.OnActivated(args);
if (args.Kind == ActivationKind.Protocol)
{
ProtocolActivatedEventArgs protocolArgs = (ProtocolActivatedEventArgs)args;
Uri uri = protocolArgs.Uri;
Debug.WriteLine("Authorization Response: " + uri.AbsoluteUri);
locator.AccountsService.GoogleExternalAuthWait.Set(uri.Query);
}
}
That GoogleExternalAuthWait comes from some magical code I found about how to create an asynchronous ManualResetEvent. https://blogs.msdn.microsoft.com/pfxteam/2012/02/11/building-async-coordination-primitives-part-1-asyncmanualresetevent/ It looks like this (I only converted it to generic).
public class AsyncManualResetEvent<T>
{
private volatile TaskCompletionSource<T> m_tcs = new TaskCompletionSource<T>();
public Task<T> WaitAsync() { return m_tcs.Task; }
public void Set(T TResult) { m_tcs.TrySetResult(TResult); }
public bool IsReset => !m_tcs.Task.IsCompleted;
public void Reset()
{
while (true)
{
var tcs = m_tcs;
if (!tcs.Task.IsCompleted ||
Interlocked.CompareExchange(ref m_tcs, new TaskCompletionSource<T>(), tcs) == tcs)
return;
}
}
}
This is how you start the Google Authorization. What happens is it launches an external browser to begin the google signing process and then wait (that's what the AsyncManualResetEvent does). When you're done, Google will launch a URI using your custom scheme. You should get a message dialog saying the browser is trying to open an app... click ok and the AsyncManualResetEvent continues and finishes the auth process. You'll need to make a class that contains all the auth info to pass to your class library.
private async Task<AuthResult> AuthenticateGoogleAsync()
{
try
{
var stateGuid = Guid.NewGuid().ToString();
var expiration = DateTimeOffset.Now;
var url = $"{GoogleAuthorizationEndpoint}?client_id={WebUtility.UrlEncode(GoogleAccountClientId)}&redirect_uri={WebUtility.UrlEncode(GoogleRedirectURI)}&state={stateGuid}&scope={WebUtility.UrlEncode(GoogleScopes)}&display=popup&response_type=code";
var success = Windows.System.Launcher.LaunchUriAsync(new Uri(url));
GoogleExternalAuthWait = new AsyncManualResetEvent<string>();
var query = await GoogleExternalAuthWait.WaitAsync();
var dictionary = query.Substring(1).Split('&').ToDictionary(x => x.Split('=')[0], x => Uri.UnescapeDataString(x.Split('=')[1]));
if (dictionary.ContainsKey("error"))
{
return null;
}
if (!dictionary.ContainsKey("code") || !dictionary.ContainsKey("state"))
{
return null;
}
if (dictionary["state"] != stateGuid)
return null;
string tokenRequestBody = $"code={dictionary["code"]}&redirect_uri={Uri.EscapeDataString(GoogleRedirectURI)}&client_id={GoogleAccountClientId}&access_type=offline&scope=&grant_type=authorization_code";
StringContent content = new StringContent(tokenRequestBody, Encoding.UTF8, "application/x-www-form-urlencoded");
// Performs the authorization code exchange.
using (HttpClientHandler handler = new HttpClientHandler())
{
handler.AllowAutoRedirect = true;
using (HttpClient client = new HttpClient(handler))
{
HttpResponseMessage response = await client.PostAsync(GoogleTokenEndpoint, content);
if (response.IsSuccessStatusCode)
{
var stringResponse = await response.Content.ReadAsStringAsync();
var json = JObject.Parse(stringResponse);
var id = DecodeIdFromJWT((string)json["id_token"]);
var oauthToken = new AuthResult()
{
Provider = AccountType.Google,
AccessToken = (string)json["access_token"],
Expiration = DateTimeOffset.Now + TimeSpan.FromSeconds(int.Parse((string)json["expires_in"])),
Id = id,
IdToken = (string)json["id_token"],
ExpirationInSeconds = long.Parse((string)json["expires_in"]),
IssueDateTime = DateTime.Now,
RefreshToken = (string)json["refresh_token"]
};
return oauthToken;
}
else
{
return null;
}
}
}
}
catch (Exception ex)
{
Debug.WriteLine(ex.Message);
return null;
}
}