Add/remove users to/from AAD group in batches - c#

What is the code to add users to AAD group or remove users from AAD group in batches in C#? (first find batch size and then add or remove users). Any sample code would be great.
UPDATE:
I added the following code:
private HttpRequestMessage MakeRequest(AzureADUser user, Guid targetGroup)
{
return new HttpRequestMessage(HttpMethod.Patch, $"https://graph.microsoft.com/v1.0/groups/{targetGroup}")
{
Content = new StringContent(MakeAddRequestBody(user), System.Text.Encoding.UTF8, "application/json"),
};
}
private static string MakeAddRequestBody(AzureADUser user)
{
JObject body = new JObject
{
["members#odata.bind"] = JArray.FromObject($"https://graph.microsoft.com/v1.0/users/{user.ObjectId}")
};
return body.ToString(Newtonsoft.Json.Formatting.None);
}
public async Task AddUsersToGroup1(IEnumerable<AzureADUser> users, AzureADGroup targetGroup)
{
try
{
var batches = GetBatchRequest(users, targetGroup.ObjectId);
foreach (var batchRequestContent in batches)
{
var response = await _graphServiceClient
.Batch
.Request()
.WithMaxRetry(10)
.PostAsync(batchRequestContent);
var responses = await response.GetResponsesAsync();
}
}
catch (Exception ex)
{
}
}
On running this I get the following exception: Object serialized to String. JArray instance expected. What am I missing? Also, once I get the responses, I need to check if all of the response returned an 'OK' response or not similar to:
return responses.Any(x => x == ResponseCode.Error) ? ResponseCode.Error : ResponseCode.Ok;
How would I do that?

Add users into AAD Group in batch:
GraphServiceClient graphClient = new GraphServiceClient(authProvider);
var additionalData = new Dictionary<string, object>()
{
{"members#odata.bind", new List<string>()}
};
(additionalData["members#odata.bind"] as List<string>).Add("https://graph.microsoft.com/v1.0/users/{id}"");
(additionalData["members#odata.bind"] as List<string>).Add("https://graph.microsoft.com/v1.0/users/{id}"");
var group = new Group
{
AdditionalData = additionalData
};
await graphClient.Groups["{group-id}"]
.Request()
.UpdateAsync(group);
There is not an endpoint which we can use to remove users from AAD Group in batch. But there is a batch endpoint which combines multiple requests in one HTTP call. It seems to have a limitation of 20. So we can't delete too many users in one call.
Here is an example, remove users from AAD Group in batch (Reference here):
GraphServiceClient graphClient = new GraphServiceClient(authProvider);
var removeUserRequest1 = graphClient.Groups["{group-id}"].Members["{id}"].Reference.Request().GetHttpRequestMessage();
var removeUserRequest2 = graphClient.Groups["{group-id}"].Members["{id}"].Reference.Request().GetHttpRequestMessage();
removeUserRequest1.Method = HttpMethod.Delete;
removeUserRequest2.Method = HttpMethod.Delete;
var batchRequestContent = new BatchRequestContent();
batchRequestContent.AddBatchRequestStep(removeUserRequest1);
batchRequestContent.AddBatchRequestStep(removeUserRequest2);
await graphClient.Batch.Request().PostAsync(batchRequestContent);

Related

How to retrieve all the groups that a user has from Azure AD in C# ASP.NET Core 6.0?

I'm trying to get all the groups that an user has but I can't achieve that. Here's what I've been trying:
public async Task<string> traerGrupos(string userID)
{
string currentUser = "null";
try
{
var tenant = "mytenant";
var clientID = "myclientid";
var secret = "mysecretkey";
var clientSecretCred = new ClientSecretCredential(tenant, clientID, secret);
GraphServiceClient graphClient = new GraphServiceClient(clientSecretCred);
var usr = graphClient.Users[userID.ToString()].Request()
.Select(x => x.DisplayName).GetAsync().Result;
currentUser = usr.DisplayName;
return currentUser;
}
catch (Exception ex)
{
return currentUser = ex.Message;
}
}
But I cannot see an option to get the groups. Besides, I get this error:
Code: Authorization_RequestDenied Message: Insufficient privileges to complete the operation.
Inner error: AdditionalData: date: 2022-12-06T19:54:23...
but my app has every permission that it requires.
How could I solve this? Thank you very much!
Firstly, your requirement can be done by this api.
And I noticed that you are using Graph SDK, so your code should look like this:
var memberOf = await graphClient.Users["{user-id}"].MemberOf
.Request()
.GetAsync();
Then I noticed you are using client credential flow by this code new ClientSecretCredential, so I agree with Charles Han that you should set the scope as https://graph.microsoft.com/.default.
Then the whole progress is:
Adding correct API permission in Azure AD application for Application type Directory.Read.All, Directory.ReadWrite.All
Your code should look like this:
using Microsoft.Graph;
using Azure.Identity;
var scopes = new[] { "https://graph.microsoft.com/.default" };
var tenantId = "tenant_name.onmicrosoft.com";
var clientId = "aad_app_id";
var clientSecret = "client_secret";
var clientSecretCredential = new ClientSecretCredential(
tenantId, clientId, clientSecret);
var graphClient = new GraphServiceClient(clientSecretCredential, scopes);
var memberOf = await graphClient.Users["{user-id}"].MemberOf.Request().GetAsync();
If you have the scope set up correctly in the app registration, try to add the scope in your GraphServiceClient constructor,
string[] scopes = new string[] { "https://graph.microsoft.com/.default" };
GraphServiceClient graphClient = new GraphServiceClient(clientSecretCred, scope);
Given that you have the correct credentials/rights for the graph API as Charles Han says.
Remember that you can try the explorer the Graph API and read more in the docs about transitiveMemberOf
I would do/have done something like this
...
//{userPrincipalName} = email or id {GUID} of user
var usersTentativeGroups = new List<ADTentativeGroup>();
await SetAccessTokenInHeader();
var url = $"https://graph.microsoft.com/v1.0/users/{userPrincipalName}/transitiveMemberOf";
var jsonResp = await _client.GetAsync(url);
var result = JsonConvert.DeserializeObject<ADGroupRoot>(await jsonResp.Content.ReadAsStringAsync());
AddResultIfNotNull(usersTentativeGroups, result);
while (!string.IsNullOrEmpty(result?.NextLink))
{
await SetAccessTokenInHeader();
jsonResp = await _client.GetAsync(result.NextLink);
result = JsonConvert.DeserializeObject<ADGroupRoot>(await jsonResp.Content.ReadAsStringAsync());
AddResultIfNotNull(usersTentativeGroups, result);
}

msgraph: how to create a batch request for subscriptions using GraphServiceClient?

I'm trying to create a batch request that will create multiple graph notification subscriptions in a single request.
I've been reading this article:
https://learn.microsoft.com/en-us/graph/sdks/batch-requests?tabs=csharp
Problem
I'm getting the following error message but I don't know how to address it:
(local variable) Task<Subscription> userRequest
Argument 1: cannot convert from 'System.Threading.Tasks.Task<Microsoft.Graph.Subscription>' to 'Microsoft.Graph.BatchRequestStep'
Code
var batchRequestContent = new BatchRequestContent();
GraphServiceClient graphClient = await GenerateGraphAuthToken(this.amParms);
foreach (string userId in this.UserstoSubscribe)
{
var subscription = new Subscription
{
ChangeType = "updated",
NotificationUrl= notificationURL,
Resource = $"users/{userId}/drive/root",
ExpirationDateTime = DateTimeOffset.Parse(subscriptionDate),
ClientState = "secretClientValue",
LatestSupportedTlsVersion = "v1_2"
};
var userRequest = graphClient.Subscriptions
.Request()
.AddAsync(subscription);
var userRequestId = batchRequestContent.AddBatchRequestStep(userRequest);
}
var returnedResponse = await graphClient.Batch.Request().PostAsync(batchRequestContent);
Any tips would be appreciated. Sorry. I'm just new to .NET and to MS Graph.
AddBatchRequestStep method expects as a parameter either BatchRequestStep or IBaseRequest or HttpRequestMessage.
In your case you have a POST request, so you must get the HttpRequestMessage and convert to a POST.
var subscription = new Subscription
{
ChangeType = "updated",
NotificationUrl= notificationURL,
Resource = $"users/{userId}/drive/root",
ExpirationDateTime = DateTimeOffset.Parse(subscriptionDate),
ClientState = "secretClientValue",
LatestSupportedTlsVersion = "v1_2"
};
// create a json content from the subscription
var jsonSubscription = graphClient.HttpProvider.Serializer.SerializeAsJsonContent(subscription);
// create a HttpRequestMessage, specify the method and add the json content
var userRequest = graphClient.Subscriptions.Request().GetHttpRequestMessage();
userRequest.Method = HttpMethod.Post;
userRequest.Content = jsonSubscription;
// add userRequest to a batch request content
var userRequestId = batchRequestContent.AddBatchRequestStep(userRequest);

Alexa.NET cannot create a reminder : Invalid Bearer Token

I want to create push notifications to my Alexa Devide. Due the push notification program is closed I am trying to create reminders. The final idea is to create an Azure Function with this code and being called when a TFS Build faild.
I'm using Alexa.NET and Alexa.NET.Reminders from a console application, already have and Alexa Skill with all the permissions granted, in the Alexa portal and in the mobile app.
Everything seems to work nice until I try to read the reminders in my account, when get an exception "Invalid Bearer Token"
this is the code:
[Fact]
public async Task SendNotificationTest()
{
var clientId = "xxxx";
var clientSecret = "yyyy";
var alexaClient = clientId;
var alexaSecret = clientSecret;
var accessToken = new Alexa.NET.AccessTokenClient(Alexa.NET.AccessTokenClient.ApiDomainBaseAddress);
var token = await accessToken.Send(alexaClient, alexaSecret);
var reminder = new Reminder
{
RequestTime = DateTime.UtcNow,
Trigger = new RelativeTrigger(12 * 60 * 60),
AlertInformation = new AlertInformation(new[] { new SpokenContent("test", "en-GB") }),
PushNotification = PushNotification.Disabled
};
var total = JsonConvert.SerializeObject(reminder);
var client = new RemindersClient("https://api.eu.amazonalexa.com", token.Token);
var alertList = await client.Get();
foreach (var alertInformation in alertList.Alerts)
{
Console.WriteLine(alertInformation.ToString());
}
try
{
var response = await client.Create(reminder);
}
catch (Exception ex)
{
var x = ex.Message;
}
}
Are there any examples to get the access token?
Am I missing a step in the process?
Thanks in advance.
N.B. The reminders client requires that you have a skill with reminders persmission enabled, and the user must have given your skill reminders permission (even if its your development account)
Creating a reminder
using Alexa.NET.Response
using Alexa.NET.Reminders
....
var reminder = new Reminder
{
RequestTime = DateTime.UtcNow,
Trigger = new RelativeTrigger(12 * 60 * 60),
AlertInformation = new AlertInformation(new[] { new SpokenContent("it's a test", "en-GB") }),
PushNotification = PushNotification.Disabled
};
var client = new RemindersClient(skillRequest);
var alertDetail = await client.Create(reminder);
Console.WriteLine(alertDetail.AlertToken);
Retrieving Current Reminders
// Single reminders can be retrieved with client.Get(alertToken)
var alertList = await client.Get();
foreach(var alertInformation in alertList.Alerts)
{
//Your logic here
}
Deleting a Reminder
await client.Delete(alertToken);

Azure Management Libraries Sdk for .NET list() function not working for various interfaces

I'm using the Azure Management Libraries (specifically fluent) to create web request towards their api to get a list of my databases under my subscription. I'm able to get an instance of the sqlserver using fluent but am unable to get a list of all databases under a specific server.
Define and delete work fine it is just the list() function.
I've tried using it for sqlserver.firewallrules and the list function doesn't work there as well.
Here is some code:
The log at some point just pauses then writes "has exited with code 0"
public async Task<List<String>> getSqlDatabaseList()
{
System.Diagnostics.Debug.WriteLine("Starting to get database list");
List<string> dbNameList = new List<string>();
//the var azure is defined earlier in the project and is authenticated.
var sqlServer = await azure.SqlServers.GetByResourceGroupAsync("<resource group name>", "<server Name>");
//The code below successfully writes the server name
System.Diagnostics.Debug.WriteLine(sqlServer.Name);
//The code below here is where everyting stop and "has exited with code 0" happens after a few seconds of delay
var dbList = sqlServer.Databases.List();
//Never reaches this line
System.Diagnostics.Debug.WriteLine("This line never is written");
foreach (ISqlDatabase db in dbList)
{
dbNameList.Add(db.Name);
}
return dbNameList;
}
Clarification:
I'm using ASP.NET MVC
Here is how my controller method accesses the class method. Resource Manager is the name of the class that implements getSQlDatabaseList();
// GET: Home
public async Task<ActionResult> Index()
{
ResourceManager rm = new ResourceManager();
List<string> test = await rm.getSqlDatabaseList();
//Never Gets to this line of code and never calls the for each or anything after
foreach (var item in test)
{
System.Diagnostics.Debug.WriteLine(item);
}
System.Diagnostics.Debug.WriteLine("Is past for each");
//AzureManager azm = await AzureManager.createAzureManager();
//await azm.getResourceGroupList();
return View(new UserLogin());
}
According to your code and description, I guess the reason why your code couldn't create the table is about your async getSqlDatabaseList.
I guess you call this method in console main method or something else.
If your main method is executed completely, your async method getSqlDatabaseList isn't execute the completely and return the list of the string. It will end all async method.
I suggest you could add await or result key keyword when calling the getSqlDatabaseList method to wait the thread execute the method completely.
More details, you could refer to below test demo.
static void Main(string[] args)
{
//use result to wait the mehtod executed completely
List<String> test = getSqlDatabaseList().Result;
foreach (var item in test)
{
Console.WriteLine(item);
}
Console.Read();
}
public static async Task<List<String>> getSqlDatabaseList()
{
//System.Diagnostics.Debug.WriteLine("Starting to get database list");
List<string> dbNameList = new List<string>();
var credentials = SdkContext.AzureCredentialsFactory.FromFile(#"D:\Auth.txt");
var azure = Azure
.Configure()
.WithLogLevel(HttpLoggingDelegatingHandler.Level.Basic)
.Authenticate(credentials)
.WithDefaultSubscription();
var sqlServer = await azure.SqlServers.GetByResourceGroupAsync("groupname", "brandotest");
var dbList = sqlServer.Databases.List();
foreach (ISqlDatabase db in dbList)
{
dbNameList.Add(db.Name);
}
return dbNameList;
}
Update:
According to your description, I have created a test MVC application. As you say I have reproduce your issue.
I think there are something wrong with the azure management fluent SDK.
Here is a workaround, I suggest you could directly send rest api to get the database.
More details, you could refer to below codes:
Send the request to below url:
https://management.azure.com/subscriptions/{subscriptionsid}/resourceGroups/{resourceGroupsname}/providers/Microsoft.Sql/servers/{servername}/databases?api-version={apiversion}
public static List<String> getSqlDatabaseList()
{
//System.Diagnostics.Debug.WriteLine("Starting to get database list");
List<string> dbNameList = new List<string>();
string tenantId = "yourtenantid";
string clientId = "yourclientId";
string clientSecret = "clientSecret";
string subscriptionid = "subscriptionid";
string resourcegroup = "resourcegroupname";
string sqlservername = "brandotest";
string version = "2014-04-01";
string authContextURL = "https://login.windows.net/" + tenantId;
var authenticationContext = new AuthenticationContext(authContextURL);
var credential = new ClientCredential(clientId, clientSecret);
var result = authenticationContext.AcquireToken(resource: "https://management.azure.com/", clientCredential: credential);
if (result == null)
{
throw new InvalidOperationException("Failed to obtain the JWT token");
}
string token = result.AccessToken;
HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(string.Format("https://management.azure.com/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Sql/servers/{2}/databases?api-version={3}", subscriptionid, resourcegroup, sqlservername, version));
request.Method = "GET";
request.Headers["Authorization"] = "Bearer " + token;
request.ContentType = "application/json";
var httpResponse = (HttpWebResponse)request.GetResponse();
using (var streamReader = new StreamReader(httpResponse.GetResponseStream()))
{
string jsonResponse = streamReader.ReadToEnd();
dynamic json = JsonConvert.DeserializeObject(jsonResponse);
dynamic resultList = json.value.Children();
foreach (var item in resultList)
{
dbNameList.Add(((Newtonsoft.Json.Linq.JValue)item.name).Value.ToString());
}
}
return dbNameList;
}
Result:
Another workaround.
I suggest you could use thread.join to wait the list method execute completely.
Code:
public static async Task<List<String>> getSqlDatabaseList()
{
//System.Diagnostics.Debug.WriteLine("Starting to get database list");
List<string> dbNameList = new List<string>();
var credentials = SdkContext.AzureCredentialsFactory.FromFile(#"D:\Auth.txt");
var azure = Azure
.Configure()
.WithLogLevel(HttpLoggingDelegatingHandler.Level.Basic)
.Authenticate(credentials)
.WithDefaultSubscription();
var sqlServer = await azure.SqlServers.GetByResourceGroupAsync("brandosecondtest", "brandotest");
IReadOnlyList<ISqlDatabase> dbList = null;
Thread thread = new Thread(() => { dbList = sqlServer.Databases.List(); });
thread.Start();
//wait the thread
thread.Join();
foreach (ISqlDatabase db in dbList)
{
dbNameList.Add(db.Name);
}
return dbNameList;
}
Result:

How can I tell if a webproperty is deleted using Google AnalyticsService?

I am using the Google Analytics Api to get web property information from my Analytics account.
When I log into analaytics though, I only have one website, but through the api I get several (old and deleted sites)
My code is like this:
var provider = new WebServerClient(GoogleAuthenticationServer.Description)
{
ClientIdentifier = _appId,
ClientSecret = _appSecret
};
var auth = new OAuth2Authenticator<WebServerClient>(provider, x => new AuthorizationState { AccessToken = token });
var analyticsService = new AnalyticsService(auth);
var accounts = analyticsService.Management.Accounts.List().Fetch();
foreach (var account in accounts.Items)
{
var webProperties = analyticsService.Management.Webproperties.List(account.Id).Fetch();
// todo: determine if web property is still in use?
}
From code how can I tell which ones are still active?
So after a bit more digging.
It seems there is no flag or anything like that indicating it has been removed, but if you keep digging into the result set you will notice that at the profile level, a profile that doesn't have child items seems to be a deleted one.
Which makes sense I guess there wouldn't be a profile associated with those that have been removed.
var provider = new WebServerClient(GoogleAuthenticationServer.Description)
{
ClientIdentifier = _appId,
ClientSecret = _appSecret
};
var auth = new OAuth2Authenticator<WebServerClient>(provider, x => new AuthorizationState { AccessToken = token });
var analyticsService = new AnalyticsService(auth);
var accounts = analyticsService.Management.Accounts.List().Fetch();
var result = new List<Profile>();
foreach (var account in accounts.Items)
{
var webProperties = analyticsService.Management.Webproperties.List(account.Id).Fetch();
foreach (var webProperty in webProperties.Items)
{
var profiles = analyticsService.Management.Profiles.List(account.Id, webProperty.Id).Fetch();
if (profiles.Items != null && profiles.Items.Any())
{
// these are the ones we want
result.AddRange(profiles.Items);
}
}
}
}

Categories