From 762a6196d1d335fc1dc61621b6d8654443252f99 Mon Sep 17 00:00:00 2001 From: mmusaev <9410421+mmusaev@users.noreply.github.com> Date: Fri, 4 Aug 2023 16:25:59 -0500 Subject: [PATCH 1/4] 1. Use wellknown standard secrets.json file for storing a private key for a local development. 2. Client api application can use appSettings.xxxxx.json and appSettings.yyyyy.json to accomodate various deployment environments, which OKTA's default implementation doesn't accomodate. 3. Finally this extensbility prevents null reference exceptions thrown if client app opts to provide a configuration file path. There are a few things to consider to optimize the code in GetConfigurationOrDefault method: 4. Avoid calling configBuilder.Build() multiple times. Instead, call it once and store the result in a variable, then use that variable to retrieve the sections you need. This will reduce the number of times the configuration is built and improve performance. 5. Consider using AddJsonStream() instead of AddJsonFile() to read the appsettings.json file. This will avoid the overhead of opening and reading the file from disk. 6. Consider removing unnecessary configuration sources, such as AddYamlFile() and AddEnvironmentVariables() - not all applications use them. 7. Implemented the first optimization in the original code section of the GetConfigurationOrDefault method. --- src/Okta.Sdk/Client/Configuration.cs | 67 +++++++++++++++++++--------- 1 file changed, 47 insertions(+), 20 deletions(-) diff --git a/src/Okta.Sdk/Client/Configuration.cs b/src/Okta.Sdk/Client/Configuration.cs index efd667105..12188cfe2 100644 --- a/src/Okta.Sdk/Client/Configuration.cs +++ b/src/Okta.Sdk/Client/Configuration.cs @@ -787,6 +787,12 @@ protected void AddApiKeyPrefix(string key, string value) #endregion Methods #region Static Members + + /// + /// Allows a client application/api to set a configuration file path (i.e. secrets.json or appSettings.xxxxxx.json) + /// + public static Func GetConfigFilePath { get; set; } + /// /// Merge configurations. /// @@ -832,34 +838,55 @@ public static IReadableConfiguration MergeConfigurations(IReadableConfiguration }; return config; } - + public static Configuration GetConfigurationOrDefault(Configuration configuration = null) { - string configurationFileRoot = Directory.GetCurrentDirectory(); + var compiledConfig = new Configuration(); + var configBuilder = new ConfigurationBuilder(); - var homeOktaYamlLocation = HomePath.Resolve("~", ".okta", "okta.yaml"); + if (GetConfigFilePath != null) + { + string applicationAppSettingsLocation = GetConfigFilePath(); - var applicationAppSettingsLocation = Path.Combine(configurationFileRoot ?? string.Empty, "appsettings.json"); - var applicationOktaYamlLocation = Path.Combine(configurationFileRoot ?? string.Empty, "okta.yaml"); + if (!File.Exists(applicationAppSettingsLocation)) + throw new FileNotFoundException($"Could not find configuration file at {applicationAppSettingsLocation}"); - var configBuilder = new ConfigurationBuilder() - .AddYamlFile(homeOktaYamlLocation, optional: true) - .AddJsonFile(applicationAppSettingsLocation, optional: true) - .AddYamlFile(applicationOktaYamlLocation, optional: true) - .AddEnvironmentVariables("okta", "_", root: "okta") - .AddEnvironmentVariables("okta_testing", "_", root: "okta") - .AddObject(configuration, root: "okta:client") - .AddObject(configuration, root: "okta:testing") - .AddObject(configuration); + var builtConfig = configBuilder.AddJsonFile(applicationAppSettingsLocation) + .AddObject(configuration) + .Build(); - var compiledConfig = new Configuration(); - configBuilder.Build().GetSection("okta").GetSection("client").Bind(compiledConfig); - configBuilder.Build().GetSection("okta").GetSection("testing").Bind(compiledConfig); - configBuilder.Build().Bind(compiledConfig); + builtConfig.GetSection("okta").GetSection("client").Bind(compiledConfig); - return compiledConfig; + return compiledConfig; + } + else + { + string configurationFileRoot = Directory.GetCurrentDirectory(); + + var homeOktaYamlLocation = HomePath.Resolve("~", ".okta", "okta.yaml"); + + var applicationAppSettingsLocation = Path.Combine(configurationFileRoot ?? string.Empty, "appsettings.json"); + var applicationOktaYamlLocation = Path.Combine(configurationFileRoot ?? string.Empty, "okta.yaml"); + + configBuilder.AddYamlFile(homeOktaYamlLocation, optional: true) + .AddJsonFile(applicationAppSettingsLocation, optional: true) + .AddYamlFile(applicationOktaYamlLocation, optional: true) + .AddEnvironmentVariables("okta", "_", root: "okta") + .AddEnvironmentVariables("okta_testing", "_", root: "okta") + .AddObject(configuration, root: "okta:client") + .AddObject(configuration, root: "okta:testing") + .AddObject(configuration); + + var config = configBuilder.Build(); + config.GetSection("okta").GetSection("client").Bind(compiledConfig); + config.GetSection("okta").GetSection("testing").Bind(compiledConfig); + config.Bind(compiledConfig); + + + return compiledConfig; + } } - + #endregion Static Members } From c7ee137ed38ffceb37c4675ddb7ecf77e99a0910 Mon Sep 17 00:00:00 2001 From: mmusaev <9410421+mmusaev@users.noreply.github.com> Date: Tue, 3 Oct 2023 17:34:45 -0500 Subject: [PATCH 2/4] Update README.md docs: update the configuration reference section in the readme with a minimal code sample of how to use my proposed feature --- README.md | 161 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 161 insertions(+) diff --git a/README.md b/README.md index e950a7404..31b12ea13 100644 --- a/README.md +++ b/README.md @@ -193,6 +193,167 @@ var oauthAppsApi = new ApplicationApi(configuration); It is possible to use an access token you retrieved outside of the SDK for authentication. For that, set `Configuration.AuthorizationMode` configuration property to `AuthorizationMode.BearerToken` and `Configuration.AccessToken` to the token string. +## Using secret.json for local or appsettings.Development.json or appsettings.Stagging.json etc., Program.cs example for ASPNET API Core. +``` csharp +using Microsoft.Extensions.Configuration.UserSecrets; +using Okta.Sdk.Api; +using Okta.Sdk.Client; +using System.Reflection; + +var builder = WebApplication.CreateBuilder(args); + +// Add services to the container. +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddControllers(); + +builder.Services.AddSwaggerGen(); + +builder.Services.AddScoped(); +builder.Services.AddScoped(); + +var app = builder.Build(); + +SetConfigurationFilePathForOktaApiServices(); + +// Configure the HTTP request pipeline. +if (!app.Environment.IsProduction()) +{ + app.UseSwagger(); + app.UseSwaggerUI(); +} +app.UseRouting(); +app.UseHttpsRedirection(); +app.MapControllers(); + +app.Run(); + +void SetConfigurationFilePathForOktaApiServices() +{ + var environmentName = app.Environment.EnvironmentName; + + if (string.Equals(environmentName, "Local", StringComparison.OrdinalIgnoreCase)) + { + var secretsId = Assembly.GetExecutingAssembly().GetCustomAttribute()?.UserSecretsId; + var configFilePath = PathHelper.GetSecretsPathFromSecretsId(secretsId); + + SetFilePath(configFilePath); + } + else + { + var configurationFileRoot = app.Environment.ContentRootPath; + var applicationAppSettingsLocation = Path.Combine(configurationFileRoot ?? string.Empty, string.IsNullOrEmpty(environmentName) ? "appsettings.json" : $"appsettings.Development.json"); + + SetFilePath(applicationAppSettingsLocation); + } +} + +void SetFilePath(string filePath) +{ + System.Diagnostics.Debug.Assert(!string.IsNullOrEmpty(filePath), $"{nameof(filePath)} missing parameter."); + + if (File.Exists(filePath)) + { + Configuration.GetConfigFilePath = () => filePath; + } +} +``` + +## Consume IUserApi, IGroupApi in OktaContoller.cs +``` csharp +using Microsoft.AspNetCore.Mvc; +using Okta.Sdk.Api; +using Okta.Sdk.Model; + +namespace OktaUserManagement; + +public class OktaContoller : Controller +{ + private readonly IUserApi _userApi; + private readonly IGroupApi _groupApi; + + public OktaContoller(IUserApi userApi, IGroupApi groupApi) + { + _userApi = userApi; + _groupApi = groupApi; + } + + [HttpGet("GetUsers")] + public async Task> GetUsers() + { + var users = await _userApi.ListUsers(limit: 2).ToListAsync(); + return users; + } + + [HttpGet("GetGroups")] + public async Task> GetGroups() + { + var groups = await _groupApi.ListGroups().ToListAsync(); + return groups; + } +} +``` + +## Set okta settings in secrets.json for local development, appsettings.Stagging.json, appsetttings.XXXXX.json, appsetttings.YYYYY.json settings files for deployment environments +```json +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*", + + "okta": { + "client": { + "connectionTimeout": 30000, + "oktaDomain": "{{yourOktaDomain}}", + "proxy": null, + "port": null, + "host": null, + "username": null, + "password": null, + "authorizationMode": "PrivateKey", + "clientId": "{{clientId}}", + "Scopes": { + "scopeId1": "okta.users.read", + "scopeId2": "okta.users.manage", + "scopeId3": "okta.groups.read", + "scopeId4": "okta.groups.manage" + }, + "PrivateKey": { + "d": "{{d}}", + "p": "{{p}}", + "q": "{{q}}", + "dp": "{{dp}}", + "dq": "{{dq}}", + "qi": "{{qi}}", + "kty": "RSA", + "e": "AQAB", + "kid": "{{kid}}", + "n": "{{n}}" + } + } + } +} +``` +## ASPNETCORE_ENVIRONMENT is set to local for a developer machine environment in launchSettings.json +```json +{ + "profiles": { + "OktaUserManagement": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "https://localhost:7127", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Local" + } + } + } +} +``` ## Usage guide These examples will help you understand how to use this library. You can also browse the full [API reference documentation][dotnetdocs]. From 8dff071ee696654cc080621e0592fa1abba0066d Mon Sep 17 00:00:00 2001 From: mmusaev <9410421+mmusaev@users.noreply.github.com> Date: Tue, 3 Oct 2023 17:50:26 -0500 Subject: [PATCH 3/4] Update README.md docs: use environmentName for json file --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 31b12ea13..4b65e1fd4 100644 --- a/README.md +++ b/README.md @@ -241,7 +241,7 @@ void SetConfigurationFilePathForOktaApiServices() else { var configurationFileRoot = app.Environment.ContentRootPath; - var applicationAppSettingsLocation = Path.Combine(configurationFileRoot ?? string.Empty, string.IsNullOrEmpty(environmentName) ? "appsettings.json" : $"appsettings.Development.json"); + var applicationAppSettingsLocation = Path.Combine(configurationFileRoot ?? string.Empty, string.IsNullOrEmpty(environmentName) ? "appsettings.json" : $"appsettings.{environmentName}.json"); SetFilePath(applicationAppSettingsLocation); } From d0b91fd6672635ca1612f31b8704e112b0583fde Mon Sep 17 00:00:00 2001 From: mmusaev <9410421+mmusaev@users.noreply.github.com> Date: Fri, 6 Oct 2023 17:23:25 -0500 Subject: [PATCH 4/4] Update README.md fix: correct misspelling --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 4b65e1fd4..2e0328b56 100644 --- a/README.md +++ b/README.md @@ -258,7 +258,7 @@ void SetFilePath(string filePath) } ``` -## Consume IUserApi, IGroupApi in OktaContoller.cs +## Consume IUserApi, IGroupApi in OktaController.cs ``` csharp using Microsoft.AspNetCore.Mvc; using Okta.Sdk.Api; @@ -266,12 +266,12 @@ using Okta.Sdk.Model; namespace OktaUserManagement; -public class OktaContoller : Controller +public class OktaController : Controller { private readonly IUserApi _userApi; private readonly IGroupApi _groupApi; - public OktaContoller(IUserApi userApi, IGroupApi groupApi) + public OktaController(IUserApi userApi, IGroupApi groupApi) { _userApi = userApi; _groupApi = groupApi;