180 lines
7.6 KiB
C#
180 lines
7.6 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;
|
||
|
|
||
|
public async Task<string> GetTokenAsync()
|
||
|
{
|
||
|
var tokenEndpoint = $"{this.settings.Endpoint}/realms/master/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,
|
||
|
});
|
||
|
|
||
|
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 ret = JsonConvert.DeserializeObject<List<KeycloakUser>>(json);
|
||
|
return ret.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 ret = JsonConvert.DeserializeObject<KeycloakUser>(json);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
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 ret = JsonConvert.DeserializeObject<List<KeycloakUser>>(json);
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
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 ret = await GetUserByEmailAsync(realm, email);
|
||
|
return ret;
|
||
|
}
|
||
|
}
|