diff --git a/README.md b/README.md index 921018d..6727a1b 100644 --- a/README.md +++ b/README.md @@ -3,62 +3,188 @@ page_type: sample languages: - csharp products: +- azure-active-directory +- azure-active-directory-b2c - dotnet -description: "Add 150 character max description" -urlFragment: "update-this-to-unique-url-stub" +- dotnet-core +- ms-graph +description: ".NET Core console application using Microsoft Graph for Azure AD B2C user account management." +urlFragment: "manage-b2c-users-dotnet-core-ms-graph" --- -# Official Microsoft Sample +# Azure AD B2C user account management with .NET Core and Microsoft Graph - - -Give a short description for your sample here. What does it do and why is it important? +The code in this sample backs the [Manage Azure AD B2C user accounts with Microsoft Graph](https://docs.microsoft.com/azure/active-directory-b2c/manage-user-accounts-graph-api) article on docs.microsoft.com. ## Contents -Outline the file contents of the repository. It helps users navigate the codebase, build configuration and any related assets. - -| File/folder | Description | -|-------------------|--------------------------------------------| -| `src` | Sample source code. | -| `.gitignore` | Define what to ignore at commit time. | -| `CHANGELOG.md` | List of changes to the sample. | -| `CONTRIBUTING.md` | Guidelines for contributing to the sample. | -| `README.md` | This README file. | -| `LICENSE` | The license for the sample. | +| File/folder | Description | +|:---------------------|:--------------------------------------------------------------| +| `./data` | Example user data in JSON format. | +| `./src` | Sample source code (*.proj, *.cs, etc.). | +| `.gitignore` | Defines the Visual Studio resources to ignore at commit time. | +| `CODE_OF_CONDUCT.md` | Information about the Microsoft Open Source Code of Conduct. | +| `LICENSE` | The license for the sample. | +| `README.md` | This README file. | +| `SECURITY.md` | Guidelines for reporting security issues found in the sample. | ## Prerequisites -Outline the required components and tools that a user might need to have on their machine in order to run the sample. This can be anything from frameworks, SDKs, OS versions or IDE releases. +* [Visual Studio](https://visualstudio.microsoft.com/) or [Visual Studio Code](https://code.visualstudio.com/) for debugging or file editing +* [.NET Core SDK](https://dotnet.microsoft.com/) 3.1+ +* [Azure AD B2C tenant](https://docs.microsoft.com/azure/active-directory-b2c/tutorial-create-tenant) with one or more user accounts in the directory +* [Management app registered](https://docs.microsoft.com/azure/active-directory-b2c/microsoft-graph-get-started) in your B2C tenant ## Setup -Explain how to prepare the sample once the user clones or downloads the repository. The section should outline every step necessary to install dependencies and set up any settings (for example, API keys and output folders). +1. Clone the repo or download and extract the [ZIP archive](https://github.com/Azure-Samples/ms-identity-dotnetcore-b2c-account-management/archive/master.zip) +1. Modify `./src/appsettings.json` with values appropriate for your environment: + - Azure AD B2C **tenant ID** + - Registered application's **Application (client) ID** + - Registered application's **Client secret** +1. Build the application with `dotnet build`: + + ```console + azureuser@machine:~/ms-identity-dotnetcore-b2c-account-management$ cd src + azureuser@machine:~/ms-identity-dotnetcore-b2c-account-management/src$ dotnet build + Microsoft (R) Build Engine version 16.4.0+e901037fe for .NET Core + Copyright (C) Microsoft Corporation. All rights reserved. + + Restore completed in 431.62 ms for /home/azureuser/ms-identity-dotnetcore-b2c-account-management/src/b2c-ms-graph.csproj. + b2c-ms-graph -> /home/azureuser/ms-identity-dotnetcore-b2c-account-management/src/bin/Debug/netcoreapp3.0/b2c-ms-graph.dll + + Build succeeded. + 0 Warning(s) + 0 Error(s) + + Time Elapsed 00:00:02.62 + ``` ## Running the sample -Outline step-by-step instructions to execute the sample and see its output. Include steps for executing the sample from the IDE, starting specific services in the Azure portal or anything related to the overall launch of the code. +Execute the sample with `dotnet b2c-ms-graph.dll`, select the operation you'd like to perform, then press ENTER. + +For example, get a user by object ID (command `2`), then exit the application with `exit`: + +```console +azureuser@machine:~/ms-identity-dotnetcore-b2c-account-management/src$ dotnet bin/Debug/netcoreapp3.0/b2c-ms-graph.dll + +Command Description +==================== +[1] Get all users (one page) +[2] Get user by object ID +[3] Get user by sign-in name +[4] Delete user by object ID +[5] Update user password +[6] Create users (bulk import) +[help] Show available commands +[exit] Exit the program +------------------------- +Enter command, then press ENTER: 2 +Enter user object ID: 064deeb8-0000-0000-0000-bf4084c9325b +Looking for user with object ID '064deeb8-0000-0000-0000-bf4084c9325b'... +{"displayName":"Autumn Hutchinson","identities":[{"signInType":"emailAddress","issuer":"contosob2c.onmicrosoft.com","issuerAssignedId":"autumn@fabrikam.com","@odata.type":"microsoft.graph.objectIdentity"},{"signInType":"userPrincipalName","issuer":"contosob2c.onmicrosoft.com","issuerAssignedId":"064deeb8-0000-0000-0000-bf4084c9325b@contosob2c.onmicrosoft.com","@odata.type":"microsoft.graph.objectIdentity"}],"id":"064deeb8-0000-0000-0000-bf4084c9325b","@odata.type":"microsoft.graph.user","@odata.context":"https://graph.microsoft.com/beta/$metadata#users(displayName,id,identities)/$entity","responseHeaders":{"Date":["Fri, 14 Feb 2020 18:52:56 GMT"],"Cache-Control":["no-cache"],"Transfer-Encoding":["chunked"],"Strict-Transport-Security":["max-age=31536000"],"request-id":["23165c3f-0000-0000-0000-f7fc59669c24"],"client-request-id":["23165c3f-0000-0000-0000-f7fc59669c24"],"x-ms-ags-diagnostic":["{\"ServerInfo\":{\"DataCenter\":\"WEST US 2\",\"Slice\":\"E\",\"Ring\":\"1\",\"ScaleUnit\":\"000\",\"RoleInstance\":\"MW1PEPF00001671\"}}"],"OData-Version":["4.0"]},"statusCode":"OK"} +Enter command, then press ENTER: exit +azureuser@machine:~/ms-identity-dotnetcore-b2c-account-management/src$ +``` ## Key concepts -Provide users with more context on the tools and services used in the sample. Explain some of the code that is being used and how services interact with each other. +The application uses the [OAuth 2.0 client credentials grant](https://docs.microsoft.com/azure/active-directory/develop/v2-oauth2-client-creds-grant-flow) flow to get an access token for calling the Microsoft Graph API. In the client credentials grant flow, the application non-interactively authenticates as itself, as opposed to requiring a user to sign in interactively. + +The following libraries are used in this sample: + +| Library documentation | NuGet | API reference | Source code | +| ------- | ------------- | ------------- | ------ | +| [Microsoft Authentication Library for .NET (MSAL.NET)][msal-doc] | [Microsoft.Identity.Client][msal-pkg] | [Reference][msal-ref] | [GitHub][msal-src] | +| [Microsoft Graph Client Library for .NET][graph-doc] | [Microsoft.Graph.Auth][graph-auth-pkg] | [Reference][graph-auth-ref] | [GitHub][graph-auth-src] | +| [Microsoft Graph Client Beta Library for .NET][graph-doc] | [Microsoft.Graph.Beta][graph-beta-pkg] | [Reference][graph-auth-ref] | [GitHub][graph-beta-src] | +| [.NET Extensions][config-doc] | [Microsoft.Extensions.Configuration][config-pkg] | [Reference][config-ref] | [GitHub][config-src] | + +The Microsoft Graph Client Library for .NET is a wrapper for MSAL.NET, providing helper classes for authenticating with and calling the Microsoft Graph API. + +### Creating the GraphServiceClient + +After parsing the values in `appsettings.json`, a [GraphServiceClient][GraphServiceClient] (the primary utility for working with Graph resources) is instantiated with following object instantiation flow: + +[ConfidentialClientApplication][ConfidentialClientApplication] :arrow_right: [ClientCredentialProvider][ClientCredentialProvider] :arrow_right: [GraphServiceClient][GraphServiceClient] + +From [`Program.cs`](./src/Program.cs): + +```csharp +// Read application settings from appsettings.json (tenant ID, app ID, client secret, etc.) +AppSettings config = AppSettingsFile.ReadFromJsonFile(); + +// Initialize the client credential auth provider +IConfidentialClientApplication confidentialClientApplication = ConfidentialClientApplicationBuilder + .Create(config.AppId) + .WithTenantId(config.TenantId) + .WithClientSecret(config.AppSecret) + .Build(); +ClientCredentialProvider authProvider = new ClientCredentialProvider(confidentialClientApplication); + +// Set up the Microsoft Graph service client with client credentials +GraphServiceClient graphClient = new GraphServiceClient(authProvider); +``` + +### Graph operations with GraphServiceClient + +The initialized *GraphServiceClient* can then be used to perform any operation for which it's been granted permissions by its [app registration](https://docs.microsoft.com/azure/active-directory-b2c/microsoft-graph-get-started). + +For example, getting a list of the user accounts in the tenant (from [`UserService.cs`](./src/Services/UserService.cs)): + +```csharp +public static async Task ListUsers(AppSettings config, GraphServiceClient graphClient) +{ + Console.WriteLine("Getting list of users..."); + + // Get all users (one page) + var result = await graphClient.Users + .Request() + .Select(e => new + { + e.DisplayName, + e.Id, + e.Identities + }) + .GetAsync(); + + foreach (var user in result.CurrentPage) + { + Console.WriteLine(JsonConvert.SerializeObject(user)); + } +} +``` ## Contributing -This project welcomes contributions and suggestions. Most contributions require you to agree to a -Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us -the rights to use your contribution. For details, visit https://cla.opensource.microsoft.com. +This project welcomes contributions and suggestions. Most contributions require you to agree to a Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us the rights to use your contribution. For details, visit https://cla.opensource.microsoft.com. + +When you submit a pull request, a CLA bot will automatically determine whether you need to provide a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions provided by the bot. You will only need to do this once across all repos using our CLA. + +This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. + + +[msal-doc]: https://docs.microsoft.com/azure/active-directory/develop/msal-overview +[msal-pkg]: https://www.nuget.org/packages/Microsoft.Identity.Client/ +[msal-ref]: https://docs.microsoft.com/dotnet/api/microsoft.identity.client?view=azure-dotnet +[msal-src]: https://github.com/AzureAD/microsoft-authentication-library-for-dotnet + +[config-doc]: https://docs.microsoft.com/aspnet/core/fundamentals/configuration +[config-pkg]: https://www.nuget.org/packages/Microsoft.Extensions.Configuration/ +[config-ref]: https://docs.microsoft.com/dotnet/api/microsoft.extensions.configuration +[config-src]: https://github.com/dotnet/extensions -When you submit a pull request, a CLA bot will automatically determine whether you need to provide -a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions -provided by the bot. You will only need to do this once across all repos using our CLA. +[graph-doc]: https://docs.microsoft.com/graph/ +[graph-auth-pkg]: https://www.nuget.org/packages/Microsoft.Graph.Auth/ +[graph-beta-pkg]: https://www.nuget.org/packages/Microsoft.Graph.Beta/ +[graph-auth-ref]: https://github.com/microsoftgraph/msgraph-sdk-dotnet/blob/dev/docs/overview.md + +[graph-auth-src]: https://github.com/microsoftgraph/msgraph-sdk-dotnet +[graph-beta-src]: https://github.com/microsoftgraph/msgraph-beta-sdk-dotnet -This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). -For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or -contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. +[ConfidentialClientApplication]: https://docs.microsoft.com/dotnet/api/microsoft.identity.client.iconfidentialclientapplication +[ClientCredentialProvider]: https://github.com/microsoftgraph/msgraph-sdk-dotnet-auth#b-client-credential-provider +[GraphServiceClient]: https://github.com/microsoftgraph/msgraph-sdk-dotnet/blob/dev/docs/overview.md#graphserviceclient \ No newline at end of file diff --git a/data/users.json b/data/users.json new file mode 100644 index 0000000..489018f --- /dev/null +++ b/data/users.json @@ -0,0 +1,53 @@ +{ + "users": [ + { + + "displayName": "[TEST] Bridgette Harmon (Local account)", + "givenName": "Bridgette", + "surname": "Harmon", + "identities": [ + { + "signInType": "emailAddress", + "issuerAssignedId": "bridgette@wingtiptoys.com" + } + ], + "password": "Pass!w0rd" + }, + { + + "displayName": "[TEST] Curt Foret (Social)", + "givenName": "Curt", + "surname": "Foret", + "identities": [ + { + "signInType": "federated", + "issuer": "facebook.com", + "issuerAssignedId": "0987654321" + }], + "otherMails": ["curt@fabrikam.com"] + }, + { + + "displayName": "[TEST] Edith Porter (Local and social)", + "givenName": "Edith", + "surname": "Porter", + "identities": [ + { + "signInType": "federated", + "issuer": "google.com", + "issuerAssignedId": "1234567890" + }, + { + "signInType": "federated", + "issuer": "github.com", + "issuerAssignedId": "abcdef" + }, + { + "signInType": "emailAddress", + "issuerAssignedId": "edith@wingtiptoys.com" + } + ], + "password": "1234" + } + ] +} \ No newline at end of file diff --git a/src/Models/AppSettings.cs b/src/Models/AppSettings.cs new file mode 100644 index 0000000..091cfc6 --- /dev/null +++ b/src/Models/AppSettings.cs @@ -0,0 +1,42 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.Extensions.Configuration; +using Newtonsoft.Json; +using System.IO; + +namespace b2c_ms_graph +{ + public class AppSettingsFile + { + public AppSettings AppSettings { get; set; } + + public static AppSettings ReadFromJsonFile() + { + IConfigurationRoot Configuration; + + var builder = new ConfigurationBuilder() + .SetBasePath(Directory.GetCurrentDirectory()) + .AddJsonFile("appsettings.json"); + + Configuration = builder.Build(); + return Configuration.Get().AppSettings; + } + } + + public class AppSettings + { + [JsonProperty(PropertyName = "TenantId")] + public string TenantId { get; set; } + + [JsonProperty(PropertyName = "AppId")] + public string AppId { get; set; } + + [JsonProperty(PropertyName = "ClientSecret")] + public string ClientSecret { get; set; } + + [JsonProperty(PropertyName = "UsersFileName")] + public string UsersFileName { get; set; } + + } +} diff --git a/src/Models/UserModel.cs b/src/Models/UserModel.cs new file mode 100644 index 0000000..6f96876 --- /dev/null +++ b/src/Models/UserModel.cs @@ -0,0 +1,40 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.Graph; +using Newtonsoft.Json; + +namespace b2c_ms_graph +{ + public class UserModel : User + { + [JsonProperty(PropertyName = "password", NullValueHandling = NullValueHandling.Ignore)] + public string Password { get; set; } + + public void SetB2CProfile(string TenantName) + { + this.PasswordProfile = new PasswordProfile + { + ForceChangePasswordNextSignIn = false, + Password = this.Password, + ODataType = null + }; + this.PasswordPolicies = "DisablePasswordExpiration,DisableStrongPassword"; + this.Password = null; + this.ODataType = null; + + foreach (var item in this.Identities) + { + if (item.SignInType == "emailAddress" || item.SignInType == "userName") + { + item.Issuer = TenantName; + } + } + } + + public override string ToString() + { + return JsonConvert.SerializeObject(this); + } + } +} \ No newline at end of file diff --git a/src/Models/UsersModel.cs b/src/Models/UsersModel.cs new file mode 100644 index 0000000..dc2d6ec --- /dev/null +++ b/src/Models/UsersModel.cs @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Newtonsoft.Json; + +namespace b2c_ms_graph +{ + public class UsersModel + { + public UserModel[] Users { get; set; } + + public static UsersModel Parse(string JSON) + { + return JsonConvert.DeserializeObject(JSON, typeof(UsersModel)) as UsersModel; + } + } +} \ No newline at end of file diff --git a/src/Program.cs b/src/Program.cs new file mode 100644 index 0000000..afe6b6a --- /dev/null +++ b/src/Program.cs @@ -0,0 +1,130 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Threading.Tasks; +using Microsoft.Graph; +using Microsoft.Graph.Auth; +using Microsoft.Identity.Client; + +namespace b2c_ms_graph +{ + class Program + { + static void Main(string[] args) + { + try + { + RunAsync().GetAwaiter().GetResult(); + } + catch (Exception ex) + { + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine(ex.Message); + Console.ResetColor(); + } + } + + private static async Task RunAsync() + { + // Read application settings from appsettings.json (tenant ID, app ID, client secret, etc.) + AppSettings config = AppSettingsFile.ReadFromJsonFile(); + + // Initialize the client credential auth provider + IConfidentialClientApplication confidentialClientApplication = ConfidentialClientApplicationBuilder + .Create(config.AppId) + .WithTenantId(config.TenantId) + .WithClientSecret(config.ClientSecret) + .Build(); + ClientCredentialProvider authProvider = new ClientCredentialProvider(confidentialClientApplication); + + // Set up the Microsoft Graph service client with client credentials + GraphServiceClient graphClient = new GraphServiceClient(authProvider); + + Program.PrintCommands(); + + try + { + while (true) + { + Console.Write("Enter command, then press ENTER: "); + string decision = Console.ReadLine(); + switch (decision.ToLower()) + { + case "1": + await UserService.ListUsers(graphClient); ; + break; + case "2": + await UserService.GetUserById(graphClient); ; + break; + case "3": + await UserService.GetUserBySignInName(config, graphClient); ; + break; + case "4": + await UserService.DeleteUserById(graphClient); + break; + case "5": + await UserService.SetPasswordByUserId(graphClient); + break; + case "6": + await UserService.BulkCreate(config, graphClient); + break; + case "help": + Program.PrintCommands(); + break; + case "exit": + return; + default: + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine("Invalid command. Enter 'help' to show a list of commands."); + Console.ResetColor(); + break; + } + + Console.ResetColor(); + } + } + catch (Exception ex) + { + Console.ForegroundColor = ConsoleColor.Red; + + var innerException = ex.InnerException; + if (innerException != null) + { + while (innerException != null) + { + Console.WriteLine(innerException.Message); + innerException = innerException.InnerException; + } + } + else + { + Console.WriteLine(ex.Message); + } + } + finally + { + Console.ResetColor(); + } + + Console.ReadLine(); + } + + private static void PrintCommands() + { + Console.ResetColor(); + Console.WriteLine(); + Console.WriteLine("Command Description"); + Console.WriteLine("===================="); + Console.WriteLine("[1] Get all users (one page)"); + Console.WriteLine("[2] Get user by object ID"); + Console.WriteLine("[3] Get user by sign-in name"); + Console.WriteLine("[4] Delete user by object ID"); + Console.WriteLine("[5] Update user password"); + Console.WriteLine("[6] Create users (bulk import)"); + Console.WriteLine("[help] Show available commands"); + Console.WriteLine("[exit] Exit the program"); + Console.WriteLine("-------------------------"); + } + } +} diff --git a/src/Services/UserService.cs b/src/Services/UserService.cs new file mode 100644 index 0000000..9008433 --- /dev/null +++ b/src/Services/UserService.cs @@ -0,0 +1,206 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.IO; +using System.Threading.Tasks; +using Microsoft.Graph; +using Newtonsoft.Json; + +namespace b2c_ms_graph +{ + class UserService + { + public static async Task ListUsers(GraphServiceClient graphClient) + { + Console.WriteLine("Getting list of users..."); + + // Get all users (one page) + var result = await graphClient.Users + .Request() + .Select(e => new + { + e.DisplayName, + e.Id, + e.Identities + }) + .GetAsync(); + + foreach (var user in result.CurrentPage) + { + Console.WriteLine(JsonConvert.SerializeObject(user)); + } + } + + public static async Task GetUserById(GraphServiceClient graphClient) + { + Console.Write("Enter user object ID: "); + string userId = Console.ReadLine(); + + Console.WriteLine($"Looking for user with object ID '{userId}'..."); + + try + { + // Get user by object ID + var result = await graphClient.Users[userId] + .Request() + .Select(e => new + { + e.DisplayName, + e.Id, + e.Identities + }) + .GetAsync(); + + if (result != null) + { + Console.WriteLine(JsonConvert.SerializeObject(result)); + } + } + catch (Exception ex) + { + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine(ex.Message); + Console.ResetColor(); + } + } + + public static async Task GetUserBySignInName(AppSettings config, GraphServiceClient graphClient) + { + Console.Write("Enter user sign-in name (username or email address): "); + string userId = Console.ReadLine(); + + Console.WriteLine($"Looking for user with sign-in name '{userId}'..."); + + try + { + // Get user by sign-in name + var result = await graphClient.Users + .Request() + .Filter($"identities/any(c:c/issuerAssignedId eq '{userId}' and c/issuer eq '{config.TenantId}')") + .Select(e => new + { + e.DisplayName, + e.Id, + e.Identities + }) + .GetAsync(); + + if (result != null) + { + Console.WriteLine(JsonConvert.SerializeObject(result)); + } + } + catch (Exception ex) + { + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine(ex.Message); + Console.ResetColor(); + } + } + + public static async Task DeleteUserById(GraphServiceClient graphClient) + { + Console.Write("Enter user object ID: "); + string userId = Console.ReadLine(); + + Console.WriteLine($"Looking for user with object ID '{userId}'..."); + + try + { + // Delete user by object ID + await graphClient.Users[userId] + .Request() + .DeleteAsync(); + + Console.WriteLine($"User with object ID '{userId}' successfully deleted."); + } + catch (Exception ex) + { + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine(ex.Message); + Console.ResetColor(); + } + } + + public static async Task SetPasswordByUserId(GraphServiceClient graphClient) + { + Console.Write("Enter user object ID: "); + string userId = Console.ReadLine(); + + Console.Write("Enter new password: "); + string password = Console.ReadLine(); + + Console.WriteLine($"Looking for user with object ID '{userId}'..."); + + var user = new User + { + PasswordPolicies = "DisablePasswordExpiration,DisableStrongPassword", + PasswordProfile = new PasswordProfile + { + ForceChangePasswordNextSignIn = false, + Password = password, + } + }; + + try + { + // Update user by object ID + await graphClient.Users[userId] + .Request() + .UpdateAsync(user); + + Console.WriteLine($"User with object ID '{userId}' successfully updated."); + } + catch (Exception ex) + { + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine(ex.Message); + Console.ResetColor(); + } + } + + public static async Task BulkCreate(AppSettings config, GraphServiceClient graphClient) + { + // Get the users to import + string appDirectoryPath = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location); + string dataFilePath = Path.Combine(appDirectoryPath, config.UsersFileName); + + // Verify and notify on file existence + if (!System.IO.File.Exists(dataFilePath)) + { + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine($"File '{dataFilePath}' not found."); + Console.ResetColor(); + Console.ReadLine(); + return; + } + + Console.WriteLine("Starting bulk create operation..."); + + // Read the data file and convert to object + UsersModel users = UsersModel.Parse(System.IO.File.ReadAllText(dataFilePath)); + + foreach (var user in users.Users) + { + user.SetB2CProfile(config.TenantId); + + try + { + // Create the user account in the directory + User user1 = await graphClient.Users + .Request() + .AddAsync(user); + + Console.WriteLine($"User '{user.DisplayName}' successfully created."); + } + catch (Exception ex) + { + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine(ex.Message); + Console.ResetColor(); + } + } + } + } +} \ No newline at end of file diff --git a/src/appsettings.json b/src/appsettings.json new file mode 100644 index 0000000..87f98f2 --- /dev/null +++ b/src/appsettings.json @@ -0,0 +1,8 @@ +{ + "appSettings": { + "TenantId": "your-b2c-tenant.onmicrosoft.com", + "AppId": "Application (client) ID", + "ClientSecret": "Client secret", + "UsersFileName": "users.json" + } +} diff --git a/src/b2c-ms-graph.csproj b/src/b2c-ms-graph.csproj new file mode 100644 index 0000000..2c0274a --- /dev/null +++ b/src/b2c-ms-graph.csproj @@ -0,0 +1,30 @@ + + + + Exe + netcoreapp3.0 + b2c_ms_graph + + + + + + + + + + + + + + + Always + + + + + Always + + + +