How to remove a users manager in AzureAD using Microsoft.Azure.ActiveDirectory.GraphClient - c#

I'm using the Microsoft.Azure.ActiveDirectory.GraphClient (Version 2.1.0) to write an app for Azure AD user management. I'm able to set the Manager of a user but have no idea how to clear the field.
Unfortunately the sample project provided on GitHub do not contain this function either.

I managed to clear the "manager" field using the code below. It is not using the Microsoft.Azure.ActiveDirectory.GraphClient library but gets the job done.
var token = <get your adal token here>
var httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", token);
var url = "https://graph.windows.net/<tenant domain>/users/<userid>/$links/manager?api-version=1.6"
var resp = httpClient.DeleteAsync(url).Result;
if (!resp.IsSuccessStatusCode)
{
// log / throw exception etc.
}

You need to perform a DELETE HTTP request to https://graph.microsoft.com/v1.0/users/<user_email>/manager/$ref (make sure to replace the <user_email> in the URL.
A successful call will receive 204 response code and empty string as the response body.
This method is currently missing from the Microsoft Graph API docs but should be added in the future. (see here)
Also you should start using Microsoft Graph (graph.microsoft.com) instead of Azure AD Graph (graph.windows.net) as the latter is becoming obsolete. (See here)

//Assign and remove user's manager
// User.Manager = newUser as DirectoryObject;
User.Manager = null;

Related

How to fix issue calling Amazon SP-API, which always returns Unauthorized, even with valid Token and Signature

I went through the guide of for getting setup to call the new SP-API (https://github.com/amzn/selling-partner-api-docs/blob/main/guides/developer-guide/SellingPartnerApiDeveloperGuide.md), and during the process checked off all of the api areas to grant access to (i.e. Orders, Inventory, etc). I am using the C# library provided by Amazon (https://github.com/amzn/selling-partner-api-models/tree/main/clients/sellingpartner-api-aa-csharp). I successfully get an access token and successfully sign the request, but always get the following error:
Access to requested resource is denied. / Unauthorized, with no details.
I am trying to perform a simple get to the /orders/v0/orders endpoint. What am I doing wrong?
Below is my code:
private const string MARKETPLACE_ID = "ATVPDKIKX0DER";
var resource = $"/orders/v0/orders";
var client = new RestClient("https://sellingpartnerapi-na.amazon.com");
IRestRequest restRequest = new RestRequest(resource, Method.GET);
restRequest.AddParameter("MarketPlaceIds", MARKETPLACE_ID, ParameterType.QueryString);
restRequest.AddParameter("CreatedAfter", DateTime.UtcNow.AddDays(-5), ParameterType.QueryString);
var lwaAuthorizationCredentials = new LWAAuthorizationCredentials
{
ClientId = AMAZON_LWA_CLIENT_ID,
ClientSecret = AMAZON_LWA_CLIENT_SECRET,
RefreshToken = AMAZON_LWA_REFRESH_TOKEN,
Endpoint = new Uri("https://api.amazon.com/auth/o2/token")
};
restRequest = new LWAAuthorizationSigner(lwaAuthorizationCredentials).Sign(restRequest);
var awsAuthenticationCredentials = new AWSAuthenticationCredentials
{
AccessKeyId = AMAZON_ACCESS_KEY_ID,
SecretKey = AMAZON_ACCESS_SECRET,
Region = "us-east-1"
};
restRequest = new AWSSigV4Signer(awsAuthenticationCredentials).Sign(restRequest, client.BaseUrl.Host);
var response = client.Execute(restRequest);
If you followed the SP-API guide, then you created a Role (which is the IAM ARN your app is registered with) and a User which has permissions to assume that role to make API calls.
However, one thing the guide is not clear about is that you can't make API calls using that user's credentials directly. You must first call the STS API's AssumeRole method with your User's credentials (AMAZON_ACCESS_KEY_ID/AMAZON_ACCESS_SECRET), and it will return temporary credentials authorized against the Role. You use those temporary credentials when signing requests.
AssumeRole will also return a session token which you must include with your API calls in a header called X-Amz-Security-Token. For a brief description of X-Amz-Security-Token see https://docs.aws.amazon.com/STS/latest/APIReference/CommonParameters.html
You also get this error if your sp app is under review, drove me nuts!
If you using c# take look to
https://github.com/abuzuhri/Amazon-SP-API-CSharp
AmazonConnection amazonConnection = new AmazonConnection(new AmazonCredential()
{
AccessKey = "AKIAXXXXXXXXXXXXXXX",
SecretKey = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
RoleArn = "arn:aws:iam::XXXXXXXXXXXXX:role/XXXXXXXXXXXX",
ClientId = "amzn1.application-XXX-client.XXXXXXXXXXXXXXXXXXXXXXXXXXXX",
ClientSecret = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
RefreshToken= "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
});
var orders= amazonConnection.Orders.ListOrders();
In our situation, we had to explicitly add an IAM policy to the user we defined as making the API call. Please see the link below and confirm that the user you have calling the API has the policy assigned to them:
https://github.com/amzn/selling-partner-api-docs/blob/main/guides/developer-guide/SellingPartnerApiDeveloperGuide.md#step-3-create-an-iam-policy
Somehow we went through the step-by-step setup twice, and adding this explicit policy was missed. Initially I believe it was added 'inline' as instructed, but that does not seem to work.
I dont think is a duplicated question, buy the solution may apply: https://stackoverflow.com/a/66860192/1034622

Unable to locate the refresh token with Microsoft Graph

I was looking here about refresh tokens.
I have this code to get a access token:
if(bPromptUser)
{
_AuthResult = await PublicClientApp.AcquireTokenAsync(_scopes); //Opens Microsoft Login Screen
using (RegistryKey key = Registry.CurrentUser.CreateSubKey(keyName))
{
key.OpenSubKey(keyName, true);
key.SetValue("Status", _AuthResult.AccessToken);
key.SetValue("Expire", _AuthResult.ExpiresOn.ToLocalTime().ToString());
key.Close();
token = _AuthResult.AccessToken;
}
// Append the access token to the request.
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", token);
}
But my _AuthResult doesn't actually have a RefreskToken in the list. Is this because I am using v1 of Microsoft Graph?
Update
According to the documentation the scope suggested in the answer is on by default?
I believe when using MSAL (and the v2 auth endpoint) that you don't get a refresh token by default. To get the refresh token you need to request the offline_access scope as well as the other scopes. Please see https://developer.microsoft.com/en-us/graph/docs/concepts/permissions_reference#openid-permissions for more details.
Hope this helps,
Microsoft provide sample code for TokenCacheHelper.
Add that to your project and provide an instance of it. Then, set the path. Like this:
TokenCacheHelper.CacheFilePath = Program.Options.TokenCachePath;
PublicClientApp = new PublicClientApplication(_AppID, "https://login.microsoftonline.com/common", TokenCacheHelper.GetUserCache());
That is all you need to do. The cache file contains all the token details, including the refresh token.
More details are in the conversation here. In part:
As far as helping you to implement the token cache, to store the content of the token cache,
you need to:
Copy the TokenCacheHelper from here to your project.
If you really want to save the content of the cache to the registry,
change the implementation of:
AfterAccessNotification to write to the
registry instead of a file this line
BeforeAccessNotification to read
fromthe registry instead of a file this line
Construct the
PublicClientApplication your as shown here (passing the cache that you
get by calling TokenCacheHelper.GetUserCache():
https://github.com/Azure-Samples/active-directory-dotnet-desktop-msgraph-v2/blob/master/active-directory-wpf-msgraph-v2/App.xaml.cs#L19:
clientApp = new PublicClientApplication(ClientId, "https://login.microsoftonline.com/common", TokenCacheHelper.GetUserCache());
For me, my problem was using an older version of the Microsoft.Identity.Client nuget package. Upgrading from 4.35.1 to 4.40.0 fixed the token error.

Use google credentials to login into UWP C# app

I'm trying to make a login for a UWP app that I'm developing for a client that has a #<theircompay>.com email that uses G Suite. It doesn't have to access any user data, they just want it as an authentication so that only people that have a company email can access the app.
It would be great if they could login from within the app without having to use a web browser, and even better if it could remember them so they wouldn't have to login every single time.
I've been looking at OAuth 2.0 and several other solutions google has but can't really understand which one to use and much less how.
I looked into this answer but it doesn't seem like a good idea to ship your certificate file with your app.
So basically if this can be done, what (if any) certificates or credentials do I need to get from Google, and how would I handle them and the login through my C# code?
Edit
The app is 100% client side, no server backend
Taking a look at Google's GitHub it seems that .Net API is still not ready for UWP (however if you traverse the issues you will find that they are working on it, so it's probably a matter of time when official version is ready and this answer would be obsolete).
As I think getting simple accessToken (optionaly refresing it) to basic profile info should be sufficient for this case. Basing on available samples from Google I've build a small project (source at GitHub), that can help you.
So first of all you have to define your app at Google's developer console and obtain ClientID and ClientSecret. Once you have this you can get to coding. To obtain accessToken I will use a WebAuthenticationBroker:
string authString = "https://accounts.google.com/o/oauth2/auth?client_id=" + ClientID;
authString += "&scope=profile";
authString += $"&redirect_uri={RedirectURI}";
authString += $"&state={state}";
authString += $"&code_challenge={code_challenge}";
authString += $"&code_challenge_method={code_challenge_method}";
authString += "&response_type=code";
var receivedData = await WebAuthenticationBroker.AuthenticateAsync(WebAuthenticationOptions.UseTitle, new Uri(authString), new Uri(ApprovalEndpoint));
switch (receivedData.ResponseStatus)
{
case WebAuthenticationStatus.Success:
await GetAccessToken(receivedData.ResponseData.Substring(receivedData.ResponseData.IndexOf(' ') + 1), state, code_verifier);
return true;
case WebAuthenticationStatus.ErrorHttp:
Debug.WriteLine($"HTTP error: {receivedData.ResponseErrorDetail}");
return false;
case WebAuthenticationStatus.UserCancel:
default:
return false;
}
If everything goes all right and user puts correct credentials, you will have to ask Google for tokens (I assume that you only want the user to put credentials once). For this purpose you have the method GetAccessToken:
// Parses URI params into a dictionary - ref: http://stackoverflow.com/a/11957114/72176
Dictionary<string, string> queryStringParams = data.Split('&').ToDictionary(c => c.Split('=')[0], c => Uri.UnescapeDataString(c.Split('=')[1]));
StringContent content = new StringContent($"code={queryStringParams["code"]}&client_secret={ClientSecret}&redirect_uri={Uri.EscapeDataString(RedirectURI)}&client_id={ClientID}&code_verifier={codeVerifier}&grant_type=authorization_code",
Encoding.UTF8, "application/x-www-form-urlencoded");
HttpResponseMessage response = await httpClient.PostAsync(TokenEndpoint, content);
string responseString = await response.Content.ReadAsStringAsync();
if (!response.IsSuccessStatusCode)
{
Debug.WriteLine("Authorization code exchange failed.");
return;
}
JsonObject tokens = JsonObject.Parse(responseString);
accessToken = tokens.GetNamedString("access_token");
foreach (var item in vault.RetrieveAll().Where((x) => x.Resource == TokenTypes.AccessToken.ToString() || x.Resource == TokenTypes.RefreshToken.ToString())) vault.Remove(item);
vault.Add(new PasswordCredential(TokenTypes.AccessToken.ToString(), "MyApp", accessToken));
vault.Add(new PasswordCredential(TokenTypes.RefreshToken.ToString(), "MyApp", tokens.GetNamedString("refresh_token")));
TokenLastAccess = DateTimeOffset.UtcNow;
Once you have the tokens (I'm saving them in PasswordVault for safety), you can later then use them to authenticate without asking the user for his credentials. Note that accessToken has limited lifetime, therefore you use refreshToken to obtain a new one:
if (DateTimeOffset.UtcNow < TokenLastAccess.AddSeconds(3600))
{
// is authorized - no need to Sign In
return true;
}
else
{
string token = GetTokenFromVault(TokenTypes.RefreshToken);
if (!string.IsNullOrWhiteSpace(token))
{
StringContent content = new StringContent($"client_secret={ClientSecret}&refresh_token={token}&client_id={ClientID}&grant_type=refresh_token",
Encoding.UTF8, "application/x-www-form-urlencoded");
HttpResponseMessage response = await httpClient.PostAsync(TokenEndpoint, content);
string responseString = await response.Content.ReadAsStringAsync();
if (response.IsSuccessStatusCode)
{
JsonObject tokens = JsonObject.Parse(responseString);
accessToken = tokens.GetNamedString("access_token");
foreach (var item in vault.RetrieveAll().Where((x) => x.Resource == TokenTypes.AccessToken.ToString())) vault.Remove(item);
vault.Add(new PasswordCredential(TokenTypes.AccessToken.ToString(), "MyApp", accessToken));
TokenLastAccess = DateTimeOffset.UtcNow;
return true;
}
}
}
The code above is only a sample (with some shortcuts) and as mentioned above - a working version with some more error handling you will find at my GitHub. Please also note, that I haven't spend much time on this and it will surely need some more work to handle all the cases and possible problems. Though hopefully will help you to start.
Answer from Roamsz is great but didnt work for me because I found some conflicts or at least with the latest build 17134 as target, it doesn't work. Here are the problem, in his Github sample, he is using returnurl as urn:ietf:wg:oauth:2.0:oob . this is the type of url, you can't use with web application type when you create new "Create OAuth client ID" in the google or firebase console. you must use "Ios" as shown below. because web application requires http or https urls as return url.
from google doc
According to his sample he is using Client secret to obtain access token, this is not possible if you create Ios as type. because Android and Ios arent using client secret. It is perfectly described over here
client_secret The client secret obtained from the API Console. This
value is not needed for clients registered as Android, iOS, or Chrome
applications.
So you must use type as Ios, No Client Secret needed and return url is urn:ietf:wg:oauth:2.0:oob or urn:ietf:wg:oauth:2.0:oob:auto difference is that auto closes browser and returns back to the app. other one, code needs to be copied manually. I prefer to use urn:ietf:wg:oauth:2.0:oob:auto
Regarding code: please follow his github code. Just remove the Client Secret from the Access Token Request.
EDIT: it looks like I was right that even offical sample is not working after UWP version 15063, somebody created an issue on their github
https://github.com/Microsoft/Windows-universal-samples/issues/642
I'm using pretty straightforward code with Google.Apis.Oauth2.v2 Nuget package. Note, that I'm using v.1.25.0.859 of that package. I tried to update to the lastest version (1.37.0.1404), but this surprisingly doesn't work with UWP. At the same time v. 1.25.0.859 works just fine.
So, unless there's a better option, I would recommend to use a bit old, but working version of Nuget package.
This is my code:
credential = await GoogleWebAuthorizationBroker.AuthorizeAsync(
new Uri("ms-appx:///Assets/User/Auth/google_client_secrets.json"),
new[] { "profile", "email" },
"me",
CancellationToken.None);
await GoogleWebAuthorizationBroker.ReauthorizeAsync(credential, CancellationToken.None);
Then you can retrieve access token from: credential.Token.AccessToken.

OneDrive API with C#, get authentication code programmatic

I have to write an application, no matter what language (c#, Java, shell, python ...) that can connect to OneDrive and then uploads file.
Following the OneDrive API I found that i need in one step to go to the browser (manually and to post a url that combines client_id and client_security to get an authentication code so i can connect my client with it to get the access token. (oAuth2 protocol)
I need to get the access_token pragmatically, i don't need any manual step to be involved.
I tried in c# to use the WebBrowser component to navigate to the url and to get the access token, I found that the browser stays in the same url and not getting the final url that includes the auth_code!
My code looks like:
// Initialize a new Client (without an Access/Refresh tokens
var client = new Client(options);
// Get the OAuth Request Url
var authRequestUrl = client.GetAuthorizationRequestUrl(new[] { Scope.Basic, Scope.Signin, Scope.SkyDrive, Scope.SkyDriveUpdate });
// TODO: Navigate to authRequestUrl using the browser, and retrieve the Authorization Code from the response
WebBrowser wb = new WebBrowser();
wb.AllowNavigation = true;
wb.ScrollBarsEnabled = false;
wb.ScriptErrorsSuppressed = true;
wb.Navigate(authRequestUrl);
Console.WriteLine(wb.Version);
while (wb.ReadyState != WebBrowserReadyState.Complete)
{
Application.DoEvents();
}
wb.Document.InvokeScript("evt_Login_onload(event)");
Uri myUrl = wb.Url;
Anyone can help with fixing this, or maybe suggest other ideas please?
Thanks in Advance!
It looks like you're creating a Windows desktop app using C#. There's actually an example at https://msdn.microsoft.com/en-us/library/hh826529.aspx for using the WebBrowser class to get the authorization code, then the token, then make an API. In short, you'll first need to send a request to the following URL with your client_id and scopes.
https://login.live.com/oauth20_authorize.srf?client_id=YOUR_CLIENT_ID&scope=YOUR_SCOPE_STRING&response_type=code&redirect_uri=https://login.live.com/oauth20_desktop.srf
In the response, you'll get the authorization code which you'll need to use to send another request to with your client_id, client_secret, authorization code like the following.
https://login.live.com/oauth20_token.srf?client_id=YOUR_CLIENT_ID&client_secret=YOUR_CLIENT_SECRET&redirect_uri=https://login.live.com/oauth20_desktop.srf&code=AUTHORIZATION_CODE&grant_type=authorization_code
When you finally receive the access token, you can make requests to the API using your access token similar to the following.
"https://apis.live.net/v5.0/me?access_token=ACCESS_TOKEN". The "me" can be changed to any other folder or directory.
I hope that helps.
dont u think the scope u provided are wrong, they should be wl.basic, wl.signin, and if ur using new onedrive api then it should be onedrive.readonly or onedrive.readwrite
if ur using liveconnect api for the purpose of using onedrive then scope should be wl.skydrive or wl.contacts_skydrive or wl.skydrive_update
depending upon ur uses (refer https://msdn.microsoft.com/en-us/library/hh243646.aspx)
and can u more elaborate how ur trying to get the access_token, from above it is quite confusing to me
Have you solved you issue?
Have you tried to use the LiveSDK to authenticate?
Have a look at my question there, it might help you :
Onedrive API vs LiveSDK
I have used the following code, after installing both the LiveSDK and the OneDrive SDK, and this does not require any login after the first authorization. However it "may" have to be a RT app (windows store or windows phone store)
var authClient = new LiveAuthClient();
var authResult = await authClient.LoginAsync(new string[] {
"wl.signin", "onedrive.readwrite", "onedrive.appfolder"});
if (authResult.Session == null)
throw new InvalidOperationException("You need to sign in and give consent to the app.");
var Connection = new ODConnection("https://api.onedrive.com/v1.0",
new MicrosoftAccountAuthenticationInfo() { TokenType = "Bearer",
AccessToken = odArgs.Session.AccessToken });
Toan-Nguyen's answer almost helps me. On the step 2 (when I should send a request with authorization code) I get the response with error "Public clients can't send client secret". This answer said it's neccessary to remove the attribute client_secret from url.

Why the same Access Token works for Media but doesn't work for Feed?

I'm using InstaSharp to get all medias from my account. This code works without any error:
var user = new InstaSharp.Endpoints.Media.Authenticated(config, authInfo);
var media = user.Popular();
Now if I do the same with Users:
var user = new InstaSharp.Endpoints.Users.Authenticated(config, authInfo);
var feed = user.Feed("self");
This returns:
OAuthParameterException: The access_token provided is invalid.
Why the same access token works in one place but doesn't work in another?
Note: I'm passing all scopes (basic, comments, likes, relationships) while getting the access token.I have also tried with default (basic) and it didn't work either.
Note2: Since I'm trying this in a ConsoleApplication, the way I'm getting the access token is, generate the link, copy/paste to the browser, confirm the authorization and get it from url. I'm not sure it makes any difference but it's worth noting...
I figure out the problem. It seems that the generated link was requesting code, not access_token.So that's why it was invalid.
And I realized that I was using older version of InstaSharp even though I downloaded it from it's website. First, I installed the latest version via NuGet. Then, I get the access token and the media like this:
var config = new InstagramConfig(clientId, clientSecret);
var AuthLink = OAuth.AuthLink(config.OAuthUri + "/authorize",
config.ClientId,
config.RedirectUri,
scopes,
OAuth.ResponseType.Code);
var authInfo = new OAuth(config);
// get the code from the link then pass it to RequestToken
var response = await authInfo.RequestToken(code);
var user = new Users(config, response);
var media = await user.RecentSelf(count: 30);
And all worked very well.

Categories