182 lines
7.7 KiB
C#
182 lines
7.7 KiB
C#
using System.Text;
|
|
using IdentityModel.Client;
|
|
using Microsoft.Extensions.Configuration;
|
|
using Newtonsoft.Json;
|
|
|
|
namespace CH.KeycloakApi;
|
|
|
|
public class KeycloakService
|
|
{
|
|
private readonly KeycloakSettings settings;
|
|
|
|
public KeycloakService(IConfiguration configuration)
|
|
{
|
|
this.settings = new KeycloakSettings();
|
|
configuration.Bind("Keycloak", settings);
|
|
}
|
|
|
|
public KeycloakSettings Settings => settings;
|
|
|
|
// use token manager instead
|
|
public async Task<string> GetTokenAsync(CancellationToken cancellationToken = default)
|
|
{
|
|
var tokenEndpoint = $"{this.settings.Endpoint}/realms/${settings.ApiRealm}/protocol/openid-connect/token";
|
|
var client = new HttpClient();
|
|
var response = await client.RequestTokenAsync(new TokenRequest
|
|
{
|
|
Address = tokenEndpoint,
|
|
GrantType = "client_credentials",
|
|
ClientId = this.settings.ClientId,
|
|
ClientSecret = this.settings.ClientSecret,
|
|
}, cancellationToken);
|
|
|
|
return response.AccessToken;
|
|
}
|
|
|
|
public async Task<KeycloakUser?> GetUserByEmailAsync(string realm, string email)
|
|
{
|
|
var httpClient = new HttpClient();
|
|
var url = $"{this.settings.Endpoint}/admin/realms/{realm}/users?email={email}";
|
|
httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("bearer", await GetTokenAsync());
|
|
var response = await httpClient.GetAsync(url);
|
|
response.EnsureSuccessStatusCode();
|
|
var json = await response.Content.ReadAsStringAsync();
|
|
var keycloakUsers = JsonConvert.DeserializeObject<List<KeycloakUser>>(json);
|
|
return keycloakUsers?.FirstOrDefault();
|
|
}
|
|
|
|
public async Task<KeycloakUser?> GetUserByIdAsync(string realm, string id)
|
|
{
|
|
var httpClient = new HttpClient();
|
|
httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("bearer", await GetTokenAsync());
|
|
var url = $"{this.settings.Endpoint}/admin/realms/{realm}/users/{id}";
|
|
var response = await httpClient.GetAsync(url);
|
|
response.EnsureSuccessStatusCode();
|
|
var json = await response.Content.ReadAsStringAsync();
|
|
var keycloakUser = JsonConvert.DeserializeObject<KeycloakUser>(json);
|
|
return keycloakUser;
|
|
}
|
|
|
|
public async Task SendChangePasswordEmailAsync(string realm, string id)
|
|
{
|
|
//PUT /{realm}/users/{id}/execute-actions-email
|
|
var url = $"{this.settings.Endpoint}/admin/realms/{realm}/users/{id}/execute-actions-email";
|
|
var httpClient = new HttpClient();
|
|
httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("bearer", await GetTokenAsync());
|
|
var putJson = JsonConvert.SerializeObject(new string[] {
|
|
"UPDATE_PASSWORD"
|
|
});
|
|
var response = await httpClient.PutAsync(url, new StringContent(putJson, Encoding.UTF8, "application/json"));
|
|
response.EnsureSuccessStatusCode();
|
|
}
|
|
|
|
public async Task<List<KeycloakUser>?> GetUsersAsync(string realm, string? search = null, int max = 100)
|
|
{
|
|
var httpClient = new HttpClient();
|
|
httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("bearer", await GetTokenAsync());
|
|
var url = $"{this.settings.Endpoint}/admin/realms/{realm}/users?max={max}";
|
|
if (!string.IsNullOrWhiteSpace(search))
|
|
url += $"&search={search}";
|
|
|
|
var response = await httpClient.GetAsync(url);
|
|
response.EnsureSuccessStatusCode();
|
|
|
|
var json = await response.Content.ReadAsStringAsync();
|
|
var keycloakUsers = JsonConvert.DeserializeObject<List<KeycloakUser>>(json);
|
|
return keycloakUsers;
|
|
}
|
|
|
|
public async Task ChangePasswordAsync(string realm, string id, string newPassword, bool temporary)
|
|
{
|
|
// auth/admin/realms/{realm}/users/{id}/reset-password
|
|
/////{ "type": "password", "temporary": false, "value": "my-new-password" }
|
|
var url = $"{this.settings.Endpoint}/admin/realms/{realm}/users/{id}/reset-password";
|
|
var httpClient = new HttpClient();
|
|
httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("bearer", await GetTokenAsync());
|
|
var putJson = JsonConvert.SerializeObject(new {
|
|
type = "password",
|
|
temporary,
|
|
value = newPassword
|
|
});
|
|
var response = await httpClient.PutAsync(url, new StringContent(putJson, Encoding.UTF8, "application/json"));
|
|
response.EnsureSuccessStatusCode();
|
|
}
|
|
|
|
|
|
|
|
public async Task UpdateUserByIdAsync(string realm, string id, string email, string firstName, string lastName, bool enabled)
|
|
{
|
|
var user = await GetUserByIdAsync(realm, id);
|
|
if (user == null)
|
|
throw new Exception($"no user {email} from on realm {realm}");
|
|
|
|
user.Email = email;
|
|
user.FirstName = firstName;
|
|
user.LastName = lastName;
|
|
user.Username = email;
|
|
user.Enabled = enabled;
|
|
|
|
var url = $"{this.settings.Endpoint}/admin/realms/{realm}/users/{id}";
|
|
|
|
var httpClient = new HttpClient();
|
|
httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("bearer", await GetTokenAsync());
|
|
var putJson = JsonConvert.SerializeObject(user);
|
|
var response = await httpClient.PutAsync(url, new StringContent(putJson, Encoding.UTF8, "application/json"));
|
|
response.EnsureSuccessStatusCode();
|
|
}
|
|
|
|
public async Task<bool> EmailExistsAsync(string realm, string email)
|
|
{
|
|
return await this.GetUserByEmailAsync(realm, email) != null;
|
|
}
|
|
|
|
public async Task<KeycloakUser?> CreateOrUpdateAsync(string realm, string email, string firstName, string lastName, bool enabled)
|
|
{
|
|
var existingUser = await GetUserByEmailAsync(realm, email);
|
|
if (existingUser != null)
|
|
{
|
|
await UpdateUserByIdAsync(realm, existingUser.Id, email, firstName, lastName, enabled);
|
|
return await GetUserByEmailAsync(realm, email);
|
|
}
|
|
|
|
return await CreateUserAsync(realm, email, firstName, lastName, enabled);
|
|
}
|
|
|
|
public async Task<KeycloakUser?> CreateUserAsync(string realm, string email, string firstName, string lastName, bool enabled)
|
|
{
|
|
long epochTicks = new DateTime(1970, 1, 1).Ticks;
|
|
long unixTime = ((DateTime.UtcNow.Ticks - epochTicks) / TimeSpan.TicksPerSecond);
|
|
|
|
var user = new KeycloakUser()
|
|
{
|
|
CreatedTimestamp = unixTime,
|
|
Username = email,
|
|
FirstName = firstName,
|
|
LastName = lastName,
|
|
Enabled = enabled,
|
|
Totp = false,
|
|
EmailVerified = false,
|
|
RequiredActions = new List<String>(),
|
|
Attributes = null,
|
|
Email = email,
|
|
NotBefore = 0,
|
|
Access = new Dictionary<string, object>
|
|
{
|
|
{ "manageGroupMembership", true },
|
|
{ "view", true },
|
|
{ "mapRoles", true },
|
|
{ "impersonate", true },
|
|
{ "manage", true }
|
|
}
|
|
};
|
|
|
|
var httpClient = new HttpClient();
|
|
httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("bearer", await GetTokenAsync());
|
|
var url = $"{this.settings.Endpoint}/admin/realms/{realm}/users";
|
|
var postJson = JsonConvert.SerializeObject(user);
|
|
var response = await httpClient.PostAsync(url, new StringContent(postJson, Encoding.UTF8, "application/json"));
|
|
response.EnsureSuccessStatusCode();
|
|
var keycloakUser = await GetUserByEmailAsync(realm, email);
|
|
return keycloakUser;
|
|
}
|
|
} |