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.
Ombi/src/Ombi/Controllers/V1/IdentityController.cs

1085 lines
40 KiB

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading.Tasks;
using AutoMapper;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Ombi.Api.Plex;
using Ombi.Api.TheMovieDb.Models;
using Ombi.Attributes;
using Ombi.Core.Authentication;
using Ombi.Core.Engine;
using Ombi.Core.Engine.Interfaces;
using Ombi.Core.Helpers;
using Ombi.Core.Models.UI;
using Ombi.Core.Services;
using Ombi.Core.Settings;
using Ombi.Core.Settings.Models.External;
using Ombi.Helpers;
using Ombi.Models;
using Ombi.Models.Identity;
using Ombi.Notifications;
using Ombi.Notifications.Models;
using Ombi.Schedule.Jobs.Ombi;
using Ombi.Settings.Settings.Models;
using Ombi.Settings.Settings.Models.Notifications;
using Ombi.Store.Entities;
using Ombi.Store.Entities.Requests;
using Ombi.Store.Repository;
using Ombi.Store.Repository.Requests;
using IdentityResult = Microsoft.AspNetCore.Identity.IdentityResult;
using OmbiIdentityResult = Ombi.Models.Identity.IdentityResult;
namespace Ombi.Controllers.V1
{
/// <inheritdoc />
/// <summary>
/// The Identity Controller, the API for everything Identity/User related
/// </summary>
[ApiV1]
[Produces("application/json")]
[ApiController]
public class IdentityController : BaseController
{
public IdentityController(OmbiUserManager user,
RoleManager<IdentityRole> rm,
IEmailProvider prov,
ISettingsService<EmailNotificationSettings> s,
ISettingsService<CustomizationSettings> c,
ISettingsService<OmbiSettings> ombiSettings,
IWelcomeEmail welcome,
ILogger<IdentityController> l,
IPlexApi plexApi,
ISettingsService<PlexSettings> settings,
ISettingsService<UserManagementSettings> umSettings,
IRepository<UserNotificationPreferences> notificationPreferences,
IRepository<UserQualityProfiles> userProfiles,
IUserDeletionEngine deletionEngine,
IRequestLimitService requestLimitService,
ICacheService cacheService)
{
UserManager = user;
RoleManager = rm;
EmailProvider = prov;
EmailSettings = s;
CustomizationSettings = c;
WelcomeEmail = welcome;
_log = l;
_plexApi = plexApi;
_plexSettings = settings;
OmbiSettings = ombiSettings;
_userManagementSettings = umSettings;
_userNotificationPreferences = notificationPreferences;
_userQualityProfiles = userProfiles;
_deletionEngine = deletionEngine;
_requestLimitService = requestLimitService;
_cacheService = cacheService;
}
private OmbiUserManager UserManager { get; }
private readonly IUserDeletionEngine _deletionEngine;
private readonly IRequestLimitService _requestLimitService;
private readonly ICacheService _cacheService;
private RoleManager<IdentityRole> RoleManager { get; }
private IEmailProvider EmailProvider { get; }
private ISettingsService<EmailNotificationSettings> EmailSettings { get; }
private ISettingsService<CustomizationSettings> CustomizationSettings { get; }
private readonly ISettingsService<UserManagementSettings> _userManagementSettings;
private ISettingsService<OmbiSettings> OmbiSettings { get; }
private IWelcomeEmail WelcomeEmail { get; }
private readonly ILogger<IdentityController> _log;
private readonly IPlexApi _plexApi;
private readonly ISettingsService<PlexSettings> _plexSettings;
private readonly IRepository<UserNotificationPreferences> _userNotificationPreferences;
private readonly IRepository<UserQualityProfiles> _userQualityProfiles;
/// <summary>
/// This is what the Wizard will call when creating the user for the very first time.
/// This should never be called after this.
/// The reason why we return false if users exists is that this method doesn't have any
/// authorization and could be called from anywhere.
/// </summary>
/// <remarks>We have [AllowAnonymous] since when going through the wizard we do not have a JWT Token yet</remarks>
/// <param name="user"></param>
/// <returns></returns>
[HttpPost("Wizard")]
[ApiExplorerSettings(IgnoreApi = true)]
[AllowAnonymous]
public async Task<SaveWizardResult> CreateWizardUser([FromBody] CreateUserWizardModel user)
{
var users = UserManager.Users;
// There could be a SINGLE plex user as you can create that in the wizard flow, but there should not be anything else
var plexUsersCount = await users.CountAsync(x => x.UserType == UserType.PlexUser);
if (plexUsersCount > 1 || users.Any(x => x.UserType == UserType.LocalUser))
{
// No one should be calling this. Only the wizard
return new SaveWizardResult { Result = false, Errors = new List<string> { "Looks like there is an existing user!" } };
}
if (user.UsePlexAdminAccount)
{
var settings = await _plexSettings.GetSettingsAsync();
var authToken = settings.Servers.FirstOrDefault()?.PlexAuthToken ?? string.Empty;
if (authToken.IsNullOrEmpty())
{
_log.LogWarning("Could not find an auth token to create the plex user with");
return new SaveWizardResult { Result = false };
}
var plexUser = await _plexApi.GetAccount(authToken);
var adminUser = new OmbiUser
{
UserName = plexUser.user.username,
UserType = UserType.PlexUser,
Email = plexUser.user.email,
ProviderUserId = plexUser.user.id,
StreamingCountry = "US" // Default
};
await _userManagementSettings.SaveSettingsAsync(new UserManagementSettings
{
ImportPlexAdmin = true
});
return await SaveWizardUser(user, adminUser, false);
}
var userToCreate = new OmbiUser
{
UserName = user.Username,
UserType = UserType.LocalUser,
StreamingCountry = "US"
};
return await SaveWizardUser(user, userToCreate, true);
}
private async Task<SaveWizardResult> SaveWizardUser(CreateUserWizardModel user, OmbiUser userToCreate, bool completeWizard)
{
IdentityResult result;
var retVal = new SaveWizardResult();
// When creating the admin as the plex user, we do not pass in the password.
if (user.Password.HasValue())
{
result = await UserManager.CreateAsync(userToCreate, user.Password);
}
else
{
result = await UserManager.CreateAsync(userToCreate);
}
if (result.Succeeded)
{
_log.LogInformation("Created User {0}", userToCreate.UserName);
await CreateRoles();
_log.LogInformation("Created the roles");
var roleResult = await UserManager.AddToRoleAsync(userToCreate, OmbiRoles.Admin);
if (!roleResult.Succeeded)
{
LogErrors(roleResult);
}
else
{
_log.LogInformation("Added the Admin role");
}
if (completeWizard)
{
// Update the wizard flag
var settings = await OmbiSettings.GetSettingsAsync();
settings.Wizard = true;
await OmbiSettings.SaveSettingsAsync(settings);
}
}
if (!result.Succeeded)
{
LogErrors(result);
retVal.Errors.AddRange(result.Errors.Select(x => x.Description));
}
retVal.Result = result.Succeeded;
return retVal;
}
private void LogErrors(IdentityResult result)
{
foreach (var err in result.Errors)
{
_log.LogCritical(err.Description);
}
}
private async Task CreateRoles()
{
await CreateRole(OmbiRoles.AutoApproveMovie);
await CreateRole(OmbiRoles.Admin);
await CreateRole(OmbiRoles.AutoApproveTv);
await CreateRole(OmbiRoles.AutoApproveMusic);
await CreateRole(OmbiRoles.RequestMusic);
await CreateRole(OmbiRoles.PowerUser);
await CreateRole(OmbiRoles.RequestMovie);
await CreateRole(OmbiRoles.RequestTv);
await CreateRole(OmbiRoles.Disabled);
await CreateRole(OmbiRoles.ReceivesNewsletter);
await CreateRole(OmbiRoles.ManageOwnRequests);
await CreateRole(OmbiRoles.EditCustomPage);
await CreateRole(OmbiRoles.EditCustomPage);
await CreateRole(OmbiRoles.Request4KMovie);
await CreateRole(OmbiRoles.AutoApprove4KMovie);
}
private async Task CreateRole(string role)
{
if (!await RoleManager.RoleExistsAsync(role))
{
await RoleManager.CreateAsync(new IdentityRole(role));
}
}
/// <summary>
/// Gets all users.
/// </summary>
/// <returns>Information about all users</returns>
[HttpGet("Users")]
[PowerUser]
public async Task<IEnumerable<UserViewModel>> GetAllUsers()
{
var users = await UserManager.Users.Where(x => x.UserType != UserType.SystemUser)
.ToListAsync();
var model = new List<UserViewModel>();
foreach (var user in users)
{
model.Add(await GetUserWithRoles(user));
}
return model.OrderBy(x => x.UserName);
}
/// <summary>
/// Gets all users for dropdown purposes.
/// </summary>
/// <returns>Basic Information about all users</returns>
[HttpGet("dropdown/Users")]
[PowerUser]
public async Task<IEnumerable<UserViewModelDropdown>> GetAllUsersDropdown()
{
var users = await _cacheService.GetOrAddAsync(CacheKeys.UsersDropdown,
() => UserManager.Users.Where(x => x.UserType != UserType.SystemUser).ToListAsync());
var model = new List<UserViewModelDropdown>();
foreach (var user in users)
{
model.Add(new UserViewModelDropdown
{
Id = user.Id,
Username = user.UserName,
Email = user.Email
});
}
return model.OrderBy(x => x.Username);
}
/// <summary>
/// Gets the current logged in user.
/// </summary>
/// <returns>Information about all users</returns>
[HttpGet]
[Authorize]
public async Task<UserViewModel> GetCurrentUser()
{
var username = User.Identity.Name.ToUpper();
var user = await UserManager.Users.FirstOrDefaultAsync(x => x.NormalizedUserName == username);
return await GetUserWithRoles(user);
}
/// <summary>
/// Sets the current users language
/// </summary>
[HttpPost("language")]
[Authorize]
public async Task<IActionResult> SetCurrentUserLanguage([FromBody] UserLanguage model)
{
var username = User.Identity.Name.ToUpper();
var user = await UserManager.Users.FirstOrDefaultAsync(x => x.NormalizedUserName == username);
user.Language = model.Lang;
await UserManager.UpdateAsync(user);
return Ok();
}
/// <summary>
/// Returns the supported country codes that we have streaming data for
/// </summary>
[HttpGet("streamingcountry")]
[Authorize]
public IActionResult GetSupportedStreamingCountries()
{
var resultsProp = typeof(Results).GetProperties();
return Json(resultsProp.Select(x => x.Name));
}
/// <summary>
/// Sets the current users country streaming preference
/// </summary>
[HttpPost("streamingcountry")]
[Authorize]
public async Task<IActionResult> SetCurrentUserCountryStreaming([FromBody] CountryStreamingPreference model)
{
var username = User.Identity.Name.ToUpper();
var user = await UserManager.Users.FirstOrDefaultAsync(x => x.NormalizedUserName == username);
user.StreamingCountry = model.Code;
await UserManager.UpdateAsync(user);
return Ok();
}
/// <summary>
/// Gets the user by the user id.
/// </summary>
/// <returns>Information about the user</returns>
[HttpGet("User/{id}")]
[PowerUser]
public async Task<UserViewModel> GetUser(string id)
{
var user = await UserManager.Users.FirstOrDefaultAsync(x => x.Id == id);
return await GetUserWithRoles(user);
}
private async Task<UserViewModel> GetUserWithRoles(OmbiUser user)
{
var userRoles = await UserManager.GetRolesAsync(user);
var vm = new UserViewModel
{
Alias = user.Alias,
UserName = user.UserName,
Id = user.Id,
EmailAddress = user.Email,
UserType = (Core.Models.UserType)(int)user.UserType,
Claims = new List<ClaimCheckboxes>(),
LastLoggedIn = user.LastLoggedIn,
HasLoggedIn = user.LastLoggedIn.HasValue,
EpisodeRequestLimit = user.EpisodeRequestLimit ?? 0,
MovieRequestLimit = user.MovieRequestLimit ?? 0,
MusicRequestLimit = user.MusicRequestLimit ?? 0,
MovieRequestLimitType = user.MovieRequestLimitType ?? RequestLimitType.Week,
EpisodeRequestLimitType = user.EpisodeRequestLimitType ?? RequestLimitType.Week,
MusicRequestLimitType = user.MusicRequestLimitType ?? RequestLimitType.Week,
Language = user.Language,
StreamingCountry = user.StreamingCountry
};
foreach (var role in userRoles)
{
vm.Claims.Add(new ClaimCheckboxes
{
Value = role,
Enabled = true
});
}
// Add the missing claims
var allRoles = await RoleManager.Roles.ToListAsync();
var missing = allRoles.Select(x => x.Name).Except(userRoles);
foreach (var role in missing)
{
vm.Claims.Add(new ClaimCheckboxes
{
Value = role,
Enabled = false
});
}
if (vm.EpisodeRequestLimit > 0)
{
vm.EpisodeRequestQuota = await _requestLimitService.GetRemainingTvRequests(user);
}
if (vm.MovieRequestLimit > 0)
{
vm.MovieRequestQuota = await _requestLimitService.GetRemainingMovieRequests(user);
}
if (vm.MusicRequestLimit > 0)
{
vm.MusicRequestQuota = await _requestLimitService.GetRemainingMusicRequests(user);
}
// Get the quality profiles
vm.UserQualityProfiles = await _userQualityProfiles.GetAll().FirstOrDefaultAsync(x => x.UserId == user.Id);
if (vm.UserQualityProfiles == null)
{
vm.UserQualityProfiles = new UserQualityProfiles
{
UserId = user.Id
};
}
return vm;
}
/// <summary>
/// Creates the user.
/// </summary>
/// <param name = "user" > The user.</param>
/// <returns></returns>
[HttpPost]
[PowerUser]
public async Task<OmbiIdentityResult> CreateUser([FromBody] UserViewModel user)
{
if (!EmailValidator.IsValidEmail(user.EmailAddress))
{
return Error($"The email address {user.EmailAddress} is not a valid format");
}
if (!CanModifyUser(user.Claims.Select(x => x.Value)))
{
return Error("You do not have the correct permissions to create this user");
}
if(user.EmailAddress.HasValue() && await UserManager.FindByEmailAsync(user.EmailAddress) != null)
{
return Error("This email has already been taken");
}
var ombiUser = new OmbiUser
{
Alias = user.Alias,
Email = user.EmailAddress,
UserName = user.UserName,
UserType = UserType.LocalUser,
MovieRequestLimit = user.MovieRequestLimit,
EpisodeRequestLimit = user.EpisodeRequestLimit,
MusicRequestLimit = user.MusicRequestLimit,
UserAccessToken = Guid.NewGuid().ToString("N"),
StreamingCountry = user.StreamingCountry.HasValue() ? user.StreamingCountry : "US"
};
var userResult = await UserManager.CreateAsync(ombiUser, user.Password);
if (!userResult.Succeeded)
{
// We did not create the user
return new OmbiIdentityResult
{
Errors = userResult.Errors.Select(x => x.Description).ToList()
};
}
var roleResult = await AddRoles(user.Claims, ombiUser);
if (roleResult.Any(x => !x.Succeeded))
{
var messages = new List<string>();
foreach (var errors in roleResult.Where(x => !x.Succeeded))
{
messages.AddRange(errors.Errors.Select(x => x.Description).ToList());
}
return new OmbiIdentityResult
{
Errors = messages
};
}
// Add the quality profiles
if (user.UserQualityProfiles != null)
{
user.UserQualityProfiles.UserId = ombiUser.Id;
await _userQualityProfiles.Add(user.UserQualityProfiles);
}
else
{
user.UserQualityProfiles = new UserQualityProfiles
{
UserId = ombiUser.Id
};
}
return new OmbiIdentityResult
{
Successful = true
};
}
private bool CanModifyUser(IEnumerable<string> roles)
{
if (roles.Any(x => x.Equals("admin", StringComparison.CurrentCultureIgnoreCase)))
{
// Only Admins can create admins
if (!User.IsInRole(OmbiRoles.Admin))
{
return false;
}
}
return true;
}
/// <summary>
/// This is for the local user to change their details.
/// </summary>
/// <param name="ui"></param>
/// <returns></returns>
[HttpPut("local")]
[Authorize]
public async Task<OmbiIdentityResult> UpdateLocalUser([FromBody] UpdateLocalUserModel ui)
{
if (string.IsNullOrEmpty(ui.CurrentPassword))
{
return Error("You need to provide your current password to make any changes");
}
var changingPass = !string.IsNullOrEmpty(ui.Password) || !string.IsNullOrEmpty(ui.ConfirmNewPassword);
if (changingPass)
{
if (!ui.Password.Equals(ui?.ConfirmNewPassword, StringComparison.CurrentCultureIgnoreCase))
{
return Error("Passwords do not match");
}
}
if (!EmailValidator.IsValidEmail(ui.EmailAddress))
{
return Error($"The email address {ui.EmailAddress} is not a valid format");
}
// Get the user
var user = await UserManager.Users.FirstOrDefaultAsync(x => x.Id == ui.Id);
if (user == null)
{
return Error("The user does not exist");
}
// Make sure the pass is ok
UserManager.ClientIpAddress = GetRequestIP();
var passwordCheck = await UserManager.CheckPasswordAsync(user, ui.CurrentPassword);
if (!passwordCheck)
{
return Error("Your password is incorrect");
}
user.Email = ui.EmailAddress;
var updateResult = await UserManager.UpdateAsync(user);
if (!updateResult.Succeeded)
{
return new OmbiIdentityResult
{
Errors = updateResult.Errors.Select(x => x.Description).ToList()
};
}
if (changingPass)
{
var result = await UserManager.ChangePasswordAsync(user, ui.CurrentPassword, ui.Password);
if (!result.Succeeded)
{
return new OmbiIdentityResult
{
Errors = result.Errors.Select(x => x.Description).ToList()
};
}
}
return new OmbiIdentityResult
{
Successful = true
};
}
/// <summary>
/// Updates the user.
/// </summary>
/// <param name = "ui" > The user.</param>
/// <returns></returns>
[HttpPut]
[PowerUser]
public async Task<OmbiIdentityResult> UpdateUser([FromBody] UserViewModel ui)
{
if (!EmailValidator.IsValidEmail(ui.EmailAddress))
{
return Error($"The email address {ui.EmailAddress} is not a valid format");
}
if (!CanModifyUser(ui.Claims.Select(x => x.Value)))
{
return Error("You do not have the correct permissions to create this user");
}
// Get the user
var user = await UserManager.Users.FirstOrDefaultAsync(x => x.Id == ui.Id);
user.Alias = ui.Alias;
user.Email = ui.EmailAddress;
user.MovieRequestLimit = ui.MovieRequestLimit;
user.EpisodeRequestLimit = ui.EpisodeRequestLimit;
user.MusicRequestLimit = ui.MusicRequestLimit;
user.EpisodeRequestLimitType = ui.EpisodeRequestLimitType;
user.MusicRequestLimitType = ui.MusicRequestLimitType;
user.MovieRequestLimitType = ui.MovieRequestLimitType;
if (ui.Password.HasValue())
{
user.PasswordHash = UserManager.PasswordHasher.HashPassword(user, ui.Password);
}
if (ui.StreamingCountry.HasValue())
{
user.StreamingCountry = ui.StreamingCountry;
}
var updateResult = await UserManager.UpdateAsync(user);
if (!updateResult.Succeeded)
{
return new OmbiIdentityResult
{
Errors = updateResult.Errors.Select(x => x.Description).ToList()
};
}
// Get the roles
var userRoles = await UserManager.GetRolesAsync(user);
// Am I modifying myself?
var modifyingSelf = user.UserName.Equals(User.Identity.Name, StringComparison.CurrentCultureIgnoreCase);
foreach (var role in userRoles)
{
if (modifyingSelf && role.Equals(OmbiRoles.Admin))
{
// We do not want to remove the admin role from yourself, this must be an accident
var claim = ui.Claims.FirstOrDefault(x => x.Value == OmbiRoles.Admin && x.Enabled);
ui.Claims.Remove(claim);
continue;
}
await UserManager.RemoveFromRoleAsync(user, role);
}
var result = await AddRoles(ui.Claims, user);
if (result.Any(x => !x.Succeeded))
{
var messages = new List<string>();
foreach (var errors in result.Where(x => !x.Succeeded))
{
messages.AddRange(errors.Errors.Select(x => x.Description).ToList());
}
return new OmbiIdentityResult
{
Errors = messages
};
}
// Add the quality profiles
if (ui.UserQualityProfiles != null)
{
var currentQualityProfiles = await
_userQualityProfiles.GetAll().FirstOrDefaultAsync(x => x.UserId == user.Id);
var add = false;
if (currentQualityProfiles == null)
{
currentQualityProfiles = new UserQualityProfiles
{
UserId = user.Id
};
add = true;
}
currentQualityProfiles.RadarrQualityProfile = ui.UserQualityProfiles.RadarrQualityProfile;
currentQualityProfiles.RadarrRootPath = ui.UserQualityProfiles.RadarrRootPath;
currentQualityProfiles.SonarrQualityProfile = ui.UserQualityProfiles.SonarrQualityProfile;
currentQualityProfiles.SonarrQualityProfileAnime = ui.UserQualityProfiles.SonarrQualityProfileAnime;
currentQualityProfiles.SonarrRootPath = ui.UserQualityProfiles.SonarrRootPath;
currentQualityProfiles.SonarrRootPathAnime = ui.UserQualityProfiles.SonarrRootPathAnime;
currentQualityProfiles.Radarr4KRootPath = ui.UserQualityProfiles.Radarr4KRootPath;
currentQualityProfiles.Radarr4KQualityProfile = ui.UserQualityProfiles.Radarr4KQualityProfile;
if (add)
{
await _userQualityProfiles.Add(currentQualityProfiles);
}
await _userQualityProfiles.SaveChangesAsync();
}
return new OmbiIdentityResult
{
Successful = true
};
}
/// <summary>
/// Deletes the user.
/// </summary>
/// <param name="userId">The user.</param>
/// <returns></returns>
[HttpDelete("{userId}")]
[PowerUser]
public async Task<OmbiIdentityResult> DeleteUser(string userId)
{
var userToDelete = await UserManager.Users.FirstOrDefaultAsync(x => x.Id == userId);
if (userToDelete != null)
{
// Can we delete this user?
var userRoles = await UserManager.GetRolesAsync(userToDelete);
if (!CanModifyUser(userRoles))
{
return Error("You do not have the correct permissions to delete this user");
}
var result = await _deletionEngine.DeleteUser(userToDelete);
if (result.Succeeded)
{
return new OmbiIdentityResult
{
Successful = true
};
}
return new OmbiIdentityResult
{
Errors = result.Errors.Select(x => x.Description).ToList()
};
}
return Error("Could not find user to delete.");
}
/// <summary>
/// Gets all available claims in the system.
/// </summary>
/// <returns></returns>
[HttpGet("claims")]
[PowerUser]
public async Task<IEnumerable<ClaimCheckboxes>> GetAllClaims()
{
var claims = new List<ClaimCheckboxes>();
// Add the missing claims
var allRoles = await RoleManager.Roles.ToListAsync();
var missing = allRoles.Select(x => x.Name);
foreach (var role in missing)
{
claims.Add(new ClaimCheckboxes
{
Value = role,
Enabled = false
});
}
return claims;
}
/// <summary>
/// Send out the email with the reset link
/// </summary>
/// <param name="email"></param>
/// <returns></returns>
[HttpPost("reset")]
[AllowAnonymous]
[ApiExplorerSettings(IgnoreApi = true)]
public async Task<OmbiIdentityResult> SubmitResetPassword([FromBody] SubmitPasswordReset email)
{
// Check if account exists
var user = await UserManager.FindByEmailAsync(email.Email);
var defaultMessage = new OmbiIdentityResult
{
Successful = true,
Errors = new List<string> { "If this account exists you should recieve a password reset link." }
};
if (user == null)
{
return defaultMessage;
}
var customizationSettings = await CustomizationSettings.GetSettingsAsync();
var appName = (string.IsNullOrEmpty(customizationSettings.ApplicationName)
? "Ombi"
: customizationSettings.ApplicationName);
var emailSettings = await EmailSettings.GetSettingsAsync();
var appUrl = customizationSettings.AddToUrl("/token?token=");
var url = (string.IsNullOrEmpty(appUrl)
? $"{HttpContext.Request.Scheme}://{HttpContext.Request.Host}/token?token="
: appUrl);
if (user.UserType == UserType.PlexUser)
{
await EmailProvider.SendAdHoc(new NotificationMessage
{
To = user.Email,
Subject = $"{appName} Password Reset",
Message =
$"You recently made a request to reset your {appName} account. Please click the link below to complete the process.<br/><br/>" +
$"<a href=\"https://www.plex.tv/sign-in/password-reset/\"> Reset </a>"
}, emailSettings);
}
else if (user.UserType == UserType.EmbyUser && user.IsEmbyConnect)
{
await EmailProvider.SendAdHoc(new NotificationMessage
{
To = user.Email,
Subject = $"{appName} Password Reset",
Message =
$"You recently made a request to reset your {appName} account.<br/><br/>" +
$"To reset your password you need to go to <a href=\"https://emby.media/community/index.php\">Emby.Media</a> and then click on your Username > Edit Profile > Email and Password"
}, emailSettings);
}
else
{
// We have the user
var token = await UserManager.GeneratePasswordResetTokenAsync(user);
var encodedToken = WebUtility.UrlEncode(token);
await EmailProvider.SendAdHoc(new NotificationMessage
{
To = user.Email,
Subject = $"{appName} Password Reset",
Message =
$"You recently made a request to reset your {appName} account. Please click the link below to complete the process.<br/><br/>" +
$"<a href=\"{url}{encodedToken}\"> Reset </a>"
}, emailSettings);
}
return defaultMessage;
}
/// <summary>
/// Resets the password
/// </summary>
/// <param name="token"></param>
/// <returns></returns>
[HttpPost("resetpassword")]
[AllowAnonymous]
[ApiExplorerSettings(IgnoreApi = true)]
public async Task<OmbiIdentityResult> ResetPassword([FromBody] ResetPasswordToken token)
{
var user = await UserManager.FindByEmailAsync(token.Email);
if (user == null)
{
return new OmbiIdentityResult
{
Successful = false,
Errors = new List<string> { "Please check you email." }
};
}
var validToken = WebUtility.UrlDecode(token.Token);
validToken = validToken.Replace(" ", "+");
var tokenValid = await UserManager.ResetPasswordAsync(user, validToken, token.Password);
if (tokenValid.Succeeded)
{
return new OmbiIdentityResult
{
Successful = true,
};
}
return new OmbiIdentityResult
{
Errors = tokenValid.Errors.Select(x => x.Description).ToList()
};
}
[HttpPost("welcomeEmail")]
[PowerUser]
public async Task<IActionResult> SendWelcomeEmail([FromBody] UserViewModel user)
{
var ombiUser = new OmbiUser
{
Alias = user.Alias,
Email = user.EmailAddress,
UserName = user.UserName
};
await WelcomeEmail.SendEmail(ombiUser);
return Ok();
}
[HttpGet("accesstoken")]
[ApiExplorerSettings(IgnoreApi = true)]
public async Task<string> GetUserAccessToken()
{
if (!User.Identity?.Name.HasValue() ?? true)
{
return Guid.Empty.ToString("N");
}
var username = User.Identity.Name.ToUpper();
var user = await UserManager.Users.FirstOrDefaultAsync(x => x.NormalizedUserName == username);
if (user == null)
{
return Guid.Empty.ToString("N");
}
if (user.UserAccessToken.IsNullOrEmpty())
{
// Let's create an access token for this user
user.UserAccessToken = Guid.NewGuid().ToString("N");
var result = await UserManager.UpdateAsync(user);
if (!result.Succeeded)
{
LogErrors(result);
return Guid.Empty.ToString("N");
}
}
return user.UserAccessToken;
}
[HttpGet("accesstoken/{userId}")]
[PowerUser]
[ApiExplorerSettings(IgnoreApi = true)]
public async Task<string> GetUserAccessToken(string userId)
{
var user = await UserManager.Users.FirstOrDefaultAsync(x => x.Id == userId);
if (user == null)
{
return Guid.Empty.ToString("N");
}
if (user.UserAccessToken.IsNullOrEmpty())
{
// Let's create an access token for this user
user.UserAccessToken = Guid.NewGuid().ToString("N");
var result = await UserManager.UpdateAsync(user);
if (!result.Succeeded)
{
LogErrors(result);
return Guid.Empty.ToString("N");
}
}
return user.UserAccessToken;
}
[HttpGet("notificationpreferences")]
public async Task<List<UserNotificationPreferences>> GetUserPreferences()
{
var username = User.Identity.Name.ToUpper();
var user = await UserManager.Users.FirstOrDefaultAsync(x => x.NormalizedUserName == username);
return await GetPreferences(user);
}
[HttpGet("notificationpreferences/{userId}")]
public async Task<List<UserNotificationPreferences>> GetUserPreferences(string userId)
{
var user = await UserManager.Users.FirstOrDefaultAsync(x => x.Id == userId);
return await GetPreferences(user);
}
private readonly List<NotificationAgent> _excludedAgents = new List<NotificationAgent>
{
NotificationAgent.Email,
NotificationAgent.Mobile,
NotificationAgent.Webhook
};
private async Task<List<UserNotificationPreferences>> GetPreferences(OmbiUser user)
{
var userPreferences = await _userNotificationPreferences.GetAll().Where(x => x.UserId == user.Id).ToListAsync();
var agents = Enum.GetValues(typeof(NotificationAgent)).Cast<NotificationAgent>().Where(x => !_excludedAgents.Contains(x));
foreach (var a in agents)
{
var agent = userPreferences.FirstOrDefault(x => x.Agent == a);
if (agent == null)
{
// Create the default
userPreferences.Add(new UserNotificationPreferences
{
Agent = a,
UserId = user.Id,
});
}
}
return userPreferences;
}
[HttpPost("NotificationPreferences")]
[ProducesResponseType(404)]
[ProducesResponseType(401)]
public async Task<IActionResult> AddUserNotificationPreference([FromBody] List<AddNotificationPreference> preferences)
{
foreach (var pref in preferences)
{
// Make sure the user exists
var user = await UserManager.Users.FirstOrDefaultAsync(x => x.Id == pref.UserId);
if (user == null)
{
return NotFound();
}
// Check if we are editing a different user than ourself, if we are then we need to power user role
var username = User.Identity.Name.ToUpper();
var me = await UserManager.Users.FirstOrDefaultAsync(x => x.NormalizedUserName == username);
if (!me.Id.Equals(user.Id, StringComparison.InvariantCultureIgnoreCase))
{
var isPowerUser = await UserManager.IsInRoleAsync(me, OmbiRoles.PowerUser);
var isAdmin = await UserManager.IsInRoleAsync(me, OmbiRoles.Admin);
if (!isPowerUser && !isAdmin)
{
return Unauthorized();
}
}
// Make sure we don't already have a preference for this agent
var existingPreference = await _userNotificationPreferences.GetAll()
.FirstOrDefaultAsync(x => x.UserId == user.Id && x.Agent == pref.Agent);
if (existingPreference != null)
{
// Update it
existingPreference.Value = pref.Value;
await _userNotificationPreferences.SaveChangesAsync();
}
else
{
await _userNotificationPreferences.Add(new UserNotificationPreferences
{
Agent = pref.Agent,
UserId = pref.UserId,
Value = pref.Value
});
}
}
return Json(true);
}
[HttpGet("newsletter/unsubscribe/{userId}")]
[AllowAnonymous]
public async Task<IActionResult> UnsubscribeUser(string userId)
{
// lookup user
var user = await UserManager.Users.FirstOrDefaultAsync(x => x.Id == userId);
if (user == null)
{
return Ok();
}
await UserManager.RemoveFromRoleAsync(user, OmbiRoles.ReceivesNewsletter);
return Ok();
}
private async Task<List<IdentityResult>> AddRoles(IEnumerable<ClaimCheckboxes> roles, OmbiUser ombiUser)
{
var roleResult = new List<IdentityResult>();
foreach (var role in roles)
{
if (role.Enabled)
{
roleResult.Add(await UserManager.AddToRoleAsync(ombiUser, role.Value));
}
}
return roleResult;
}
private OmbiIdentityResult Error(string message)
{
return new OmbiIdentityResult
{
Errors = new List<string> { message }
};
}
}
}