You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

431 lines
17 KiB

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Keycloak.Net;
using Keycloak.Net.Models.Clients;
using Keycloak.Net.Models.ClientScopes;
using Keycloak.Net.Models.ProtocolMappers;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Volo.Abp.Data;
using Volo.Abp.DependencyInjection;
namespace EShopOnAbp.DbMigrator;
public class KeyCloakDataSeeder : IDataSeedContributor, ITransientDependency
{
private readonly KeycloakClient _keycloakClient;
private readonly KeycloakClientOptions _keycloakOptions;
private readonly ILogger<KeyCloakDataSeeder> _logger;
private readonly IConfiguration _configuration;
public KeyCloakDataSeeder(IOptions<KeycloakClientOptions> keycloakClientOptions, ILogger<KeyCloakDataSeeder> logger,
IConfiguration configuration)
{
_logger = logger;
_configuration = configuration;
_keycloakOptions = keycloakClientOptions.Value;
_keycloakClient = new KeycloakClient(
_keycloakOptions.Url,
_keycloakOptions.AdminUserName,
_keycloakOptions.AdminPassword
);
}
public async Task SeedAsync(DataSeedContext context)
{
await UpdateRealmSettingsAsync();
await UpdateAdminUserAsync();
await CreateRoleMapperAsync(); // roles scope
await CreateClientScopesAsync();
await CreateClientsAsync();
}
private async Task UpdateRealmSettingsAsync()
{
var masterRealm = await _keycloakClient.GetRealmAsync(_keycloakOptions.RealmName);
if (masterRealm.AccessTokenLifespan != 30 * 60)
{
masterRealm.AccessTokenLifespan = 30 * 60;
await _keycloakClient.UpdateRealmAsync(_keycloakOptions.RealmName, masterRealm);
}
}
private async Task CreateRoleMapperAsync()
{
var roleScope = (await _keycloakClient.GetClientScopesAsync(_keycloakOptions.RealmName))
.FirstOrDefault(q => q.Name == "roles");
if (roleScope == null)
return;
if (!roleScope.ProtocolMappers.Any(q => q.Name == "roles"))
{
await _keycloakClient.CreateProtocolMapperAsync(_keycloakOptions.RealmName, roleScope.Id,
new ProtocolMapper()
{
Name = "roles",
Protocol = "openid-connect",
_ProtocolMapper = "oidc-usermodel-realm-role-mapper",
Config = new Dictionary<string, string>()
{
{ "access.token.claim", "true" },
{ "id.token.claim", "true" },
{ "claim.name", "role" },
{ "multivalued", "true" },
{ "userinfo.token.claim", "true" },
}
});
}
}
private async Task CreateClientScopesAsync()
{
await CreateScopeAsync("AdministrationService");
await CreateScopeAsync("IdentityService");
await CreateScopeAsync("BasketService");
await CreateScopeAsync("CatalogService");
await CreateScopeAsync("OrderingService");
await CreateScopeAsync("PaymentService");
await CreateScopeAsync("CmskitService");
}
private async Task CreateScopeAsync(string scopeName)
{
var scope = (await _keycloakClient.GetClientScopesAsync(_keycloakOptions.RealmName))
.FirstOrDefault(q => q.Name == scopeName);
if (scope == null)
{
scope = new ClientScope
{
Name = scopeName,
Description = scopeName + " scope",
Protocol = "openid-connect",
Attributes = new Attributes
{
ConsentScreenText = scopeName,
DisplayOnConsentScreen = "true",
IncludeInTokenScope = "true"
},
ProtocolMappers = new List<ProtocolMapper>()
{
new ProtocolMapper()
{
Name = scopeName,
Protocol = "openid-connect",
_ProtocolMapper = "oidc-audience-mapper",
Config =
new
Dictionary<string, string>
{
{ "id.token.claim", "false" },
{ "access.token.claim", "true" },
{ "included.custom.audience", scopeName }
}
}
}
};
await _keycloakClient.CreateClientScopeAsync(_keycloakOptions.RealmName, scope);
}
}
private async Task CreateClientsAsync()
{
await CreatePublicWebClientAsync();
await CreateSwaggerClientAsync();
await CreateWebClientAsync();
// await CreateCmskitClientAsync();
// await CreateAdministrationClientAsync();
}
private async Task CreateAdministrationClientAsync()
{
var administrationClient =
(await _keycloakClient.GetClientsAsync(_keycloakOptions.RealmName,
clientId: "EShopOnAbp_AdministrationService"))
.FirstOrDefault();
if (administrationClient == null)
{
administrationClient = new Client()
{
ClientId = "EShopOnAbp_AdministrationService",
Name = "Administration service client",
Protocol = "openid-connect",
PublicClient = false,
ImplicitFlowEnabled = false,
AuthorizationServicesEnabled = false,
StandardFlowEnabled = false,
DirectAccessGrantsEnabled = false,
ServiceAccountsEnabled = true,
Secret = "1q2w3e*"
};
administrationClient.Attributes = new Dictionary<string, object>()
{
{ "oauth2.device.authorization.grant.enabled", false },
{ "oidc.ciba.grant.enabled", false }
};
await _keycloakClient.CreateClientAsync(_keycloakOptions.RealmName, administrationClient);
await AddOptionalClientScopesAsync(
"EShopOnAbp_AdministrationService",
new List<string>
{
"IdentityService"
}
);
var insertedClient =
(await _keycloakClient.GetClientsAsync(_keycloakOptions.RealmName, clientId: "EShopOnAbp_AdministrationService"))
.First();
var clientIdProtocolMapper = insertedClient.ProtocolMappers.First(q => q.Name == "Client ID");
clientIdProtocolMapper.Config["claim.name"] = "client_id";
var result = await _keycloakClient.UpdateClientAsync(_keycloakOptions.RealmName, insertedClient.Id,
insertedClient);
}
}
private async Task CreateCmskitClientAsync()
{
var cmsKitClient =
(await _keycloakClient.GetClientsAsync(_keycloakOptions.RealmName, clientId: "EShopOnAbp_CmskitService"))
.FirstOrDefault();
if (cmsKitClient == null)
{
cmsKitClient = new Client()
{
ClientId = "EShopOnAbp_CmskitService",
Name = "Cmskit microservice client",
Protocol = "openid-connect",
PublicClient = false,
ImplicitFlowEnabled = false,
AuthorizationServicesEnabled = false,
StandardFlowEnabled = false,
DirectAccessGrantsEnabled = false,
ServiceAccountsEnabled = true,
Secret = "1q2w3e*"
};
cmsKitClient.Attributes = new Dictionary<string, object>()
{
{ "oauth2.device.authorization.grant.enabled", false },
{ "oidc.ciba.grant.enabled", false },
{ "client_credentials.use_refresh_token", false }
};
await _keycloakClient.CreateClientAsync(_keycloakOptions.RealmName, cmsKitClient);
await AddOptionalClientScopesAsync(
"EShopOnAbp_CmskitService",
new List<string>
{
"IdentityService"
}
);
var insertedClient =
(await _keycloakClient.GetClientsAsync(_keycloakOptions.RealmName, clientId: "EShopOnAbp_CmskitService"))
.First();
var clientIdProtocolMapper = insertedClient.ProtocolMappers.First(q => q.Name == "Client ID");
clientIdProtocolMapper.Config["claim.name"] = "client_id";
var result = await _keycloakClient.UpdateClientAsync(_keycloakOptions.RealmName, insertedClient.Id,
insertedClient);
}
}
private async Task CreateWebClientAsync()
{
var webClient = (await _keycloakClient.GetClientsAsync(_keycloakOptions.RealmName, clientId: "Web"))
.FirstOrDefault();
if (webClient == null)
{
var webRootUrl = _configuration[$"Clients:Web:RootUrl"];
webClient = new Client
{
ClientId = "Web",
Name = "Angular Back-Office Web Application",
Protocol = "openid-connect",
Enabled = true,
BaseUrl = webRootUrl,
RedirectUris = new List<string>
{
$"{webRootUrl.TrimEnd('/')}"
},
FrontChannelLogout = true,
PublicClient = true
};
webClient.Attributes = new Dictionary<string, object>
{
{ "post.logout.redirect.uris", $"{webRootUrl.TrimEnd('/')}" }
};
await _keycloakClient.CreateClientAsync(_keycloakOptions.RealmName, webClient);
await AddOptionalClientScopesAsync(
"Web",
new List<string>
{
"AdministrationService", "IdentityService", "BasketService", "CatalogService",
"OrderingService", "PaymentService", "CmskitService"
}
);
}
}
private async Task CreateSwaggerClientAsync()
{
var swaggerClient =
(await _keycloakClient.GetClientsAsync(_keycloakOptions.RealmName, clientId: "SwaggerClient"))
.FirstOrDefault();
if (swaggerClient == null)
{
var webGatewaySwaggerRootUrl = _configuration[$"Clients:WebGateway:RootUrl"].TrimEnd('/');
var publicWebGatewayRootUrl = _configuration[$"Clients:PublicWebGateway:RootUrl"].TrimEnd('/');
var accountServiceRootUrl = _configuration[$"Clients:AccountService:RootUrl"].TrimEnd('/');
var identityServiceRootUrl = _configuration[$"Clients:IdentityService:RootUrl"].TrimEnd('/');
var administrationServiceRootUrl = _configuration[$"Clients:AdministrationService:RootUrl"].TrimEnd('/');
var catalogServiceRootUrl = _configuration[$"Clients:CatalogService:RootUrl"].TrimEnd('/');
var basketServiceRootUrl = _configuration[$"Clients:BasketService:RootUrl"].TrimEnd('/');
var orderingServiceRootUrl = _configuration[$"Clients:OrderingService:RootUrl"].TrimEnd('/');
var paymentServiceRootUrl = _configuration[$"Clients:PaymentService:RootUrl"].TrimEnd('/');
var cmskitServiceRootUrl = _configuration[$"Clients:CmskitService:RootUrl"].TrimEnd('/');
swaggerClient = new Client
{
ClientId = "SwaggerClient",
Name = "Swagger Client Application",
Protocol = "openid-connect",
Enabled = true,
RedirectUris = new List<string>
{
$"{webGatewaySwaggerRootUrl}/swagger/oauth2-redirect.html", // WebGateway redirect uri
$"{publicWebGatewayRootUrl}/swagger/oauth2-redirect.html", // PublicWebGateway redirect uri
$"{accountServiceRootUrl}/swagger/oauth2-redirect.html", // AccountService redirect uri
$"{identityServiceRootUrl}/swagger/oauth2-redirect.html", // IdentityService redirect uri
$"{administrationServiceRootUrl}/swagger/oauth2-redirect.html", // AdministrationService redirect uri
$"{catalogServiceRootUrl}/swagger/oauth2-redirect.html", // CatalogService redirect uri
$"{basketServiceRootUrl}/swagger/oauth2-redirect.html", // BasketService redirect uri
$"{orderingServiceRootUrl}/swagger/oauth2-redirect.html", // OrderingService redirect uri
$"{paymentServiceRootUrl}/swagger/oauth2-redirect.html", // PaymentService redirect uri
$"{cmskitServiceRootUrl}/swagger/oauth2-redirect.html" // CmskitService redirect uri
},
FrontChannelLogout = true,
PublicClient = true
};
await _keycloakClient.CreateClientAsync(_keycloakOptions.RealmName, swaggerClient);
await AddOptionalClientScopesAsync(
"SwaggerClient",
new List<string>
{
"AdministrationService", "IdentityService", "BasketService", "CatalogService",
"OrderingService", "PaymentService", "CmskitService"
}
);
}
}
private async Task CreatePublicWebClientAsync()
{
var publicWebClient = (await _keycloakClient.GetClientsAsync(_keycloakOptions.RealmName, clientId: "PublicWeb"))
.FirstOrDefault();
if (publicWebClient == null)
{
var publicWebRootUrl = _configuration[$"Clients:PublicWeb:RootUrl"];
publicWebClient = new Client
{
ClientId = "PublicWeb",
Name = "Public Web Application",
Protocol = "openid-connect",
Enabled = true,
BaseUrl = publicWebRootUrl,
RedirectUris = new List<string>
{
$"{publicWebRootUrl.TrimEnd('/')}/signin-oidc"
},
FrontChannelLogout = true,
PublicClient = true,
ImplicitFlowEnabled = true // for hybrid flow
};
publicWebClient.Attributes = new Dictionary<string, object>
{
{ "post.logout.redirect.uris", $"{publicWebRootUrl.TrimEnd('/')}/signout-callback-oidc" }
};
await _keycloakClient.CreateClientAsync(_keycloakOptions.RealmName, publicWebClient);
await AddOptionalClientScopesAsync(
"PublicWeb",
new List<string>
{
"AdministrationService", "IdentityService", "BasketService", "CatalogService",
"OrderingService", "PaymentService", "CmskitService"
}
);
}
}
private async Task AddOptionalClientScopesAsync(string clientName, List<string> scopes)
{
var client = (await _keycloakClient.GetClientsAsync(_keycloakOptions.RealmName, clientId: clientName))
.FirstOrDefault();
if (client == null)
{
_logger.LogError($"Couldn't find {clientName}! Could not seed optional scopes!");
return;
}
var clientOptionalScopes =
(await _keycloakClient.GetOptionalClientScopesAsync(_keycloakOptions.RealmName, client.Id)).ToList();
var clientScopes = (await _keycloakClient.GetClientScopesAsync(_keycloakOptions.RealmName)).ToList();
foreach (var scope in scopes)
{
if (!clientOptionalScopes.Any(q => q.Name == scope))
{
var serviceScope = clientScopes.First(q => q.Name == scope);
_logger.LogInformation($"Seeding {scope} scope to {clientName}.");
await _keycloakClient.UpdateOptionalClientScopeAsync(_keycloakOptions.RealmName, client.Id,
serviceScope.Id);
}
}
}
private async Task UpdateAdminUserAsync()
{
var users = await _keycloakClient.GetUsersAsync(_keycloakOptions.RealmName, username: "admin");
var adminUser = users.FirstOrDefault();
if (adminUser == null)
{
throw new Exception(
"Keycloak admin user is not provided, check if KEYCLOAK_ADMIN environment variable is passed properly.");
}
if (string.IsNullOrEmpty(adminUser.Email))
{
adminUser.Email = "admin@abp.io";
adminUser.FirstName = "admin";
adminUser.EmailVerified = true;
_logger.LogInformation("Updating admin user with email and first name...");
await _keycloakClient.UpdateUserAsync(_keycloakOptions.RealmName, adminUser.Id, adminUser);
}
}
}