mirror of
https://github.com/Ombi-app/Ombi.git
synced 2025-07-12 16:22:55 -07:00
Removed IdentityServer, it was overkill #865
This commit is contained in:
parent
7645aff996
commit
046211e017
17 changed files with 226 additions and 346 deletions
|
@ -7,14 +7,12 @@ using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Security.Claims;
|
|
||||||
using System.Security.Principal;
|
using System.Security.Principal;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNetCore.Identity;
|
using Microsoft.AspNetCore.Identity;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Ombi.Core.Engine.Interfaces;
|
using Ombi.Core.Engine.Interfaces;
|
||||||
using Ombi.Core.IdentityResolver;
|
|
||||||
using Ombi.Core.Rule.Interfaces;
|
using Ombi.Core.Rule.Interfaces;
|
||||||
using Ombi.Store.Entities.Requests;
|
using Ombi.Store.Entities.Requests;
|
||||||
|
|
||||||
|
|
|
@ -1,53 +0,0 @@
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Security.Claims;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using IdentityModel;
|
|
||||||
using IdentityServer4.Extensions;
|
|
||||||
using IdentityServer4.Models;
|
|
||||||
using IdentityServer4.Services;
|
|
||||||
using Microsoft.AspNetCore.Identity;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using Ombi.Store.Entities;
|
|
||||||
|
|
||||||
namespace Ombi.Core.IdentityResolver
|
|
||||||
{
|
|
||||||
public class OmbiProfileService : IProfileService
|
|
||||||
{
|
|
||||||
public OmbiProfileService(UserManager<OmbiUser> um)
|
|
||||||
{
|
|
||||||
UserManager = um;
|
|
||||||
}
|
|
||||||
|
|
||||||
private UserManager<OmbiUser> UserManager { get; }
|
|
||||||
|
|
||||||
public async Task GetProfileDataAsync(ProfileDataRequestContext context)
|
|
||||||
{
|
|
||||||
|
|
||||||
if (context.RequestedClaimTypes.Any())
|
|
||||||
{
|
|
||||||
var user = await UserManager.Users.FirstOrDefaultAsync(x => x.UserName == context.Subject.GetSubjectId());
|
|
||||||
if (user != null)
|
|
||||||
{
|
|
||||||
var roles = await UserManager.GetRolesAsync(user);
|
|
||||||
var claims = new List<Claim>
|
|
||||||
{
|
|
||||||
new Claim(JwtClaimTypes.Name, user.UserName)
|
|
||||||
};
|
|
||||||
|
|
||||||
foreach (var role in roles)
|
|
||||||
{
|
|
||||||
claims.Add(new Claim(JwtClaimTypes.Role, role));
|
|
||||||
}
|
|
||||||
context.AddFilteredClaims(claims);
|
|
||||||
context.IssuedClaims.AddRange(claims);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task IsActiveAsync(IsActiveContext context)
|
|
||||||
{
|
|
||||||
return Task.FromResult(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,156 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Security.Claims;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using IdentityServer4.Models;
|
|
||||||
using IdentityServer4.Validation;
|
|
||||||
using Microsoft.AspNetCore.Identity;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using Microsoft.Extensions.Logging;
|
|
||||||
using Ombi.Api.Emby;
|
|
||||||
using Ombi.Api.Emby.Models;
|
|
||||||
using Ombi.Api.Plex;
|
|
||||||
using Ombi.Api.Plex.Models;
|
|
||||||
using Ombi.Core.Settings;
|
|
||||||
using Ombi.Core.Settings.Models.External;
|
|
||||||
using Ombi.Helpers;
|
|
||||||
using Ombi.Settings.Settings.Models;
|
|
||||||
using Ombi.Store.Entities;
|
|
||||||
using Ombi.Store.Repository;
|
|
||||||
|
|
||||||
namespace Ombi.Core.IdentityResolver
|
|
||||||
{
|
|
||||||
public class OmbiOwnerPasswordValidator : IResourceOwnerPasswordValidator
|
|
||||||
{
|
|
||||||
public OmbiOwnerPasswordValidator(UserManager<OmbiUser> um, IPlexApi plexApi, IEmbyApi embyApi,
|
|
||||||
ISettingsService<PlexSettings> settings, ISettingsService<OmbiSettings> ombiSettings,
|
|
||||||
ISettingsService<EmbySettings> embySettings, IAuditRepository log)
|
|
||||||
{
|
|
||||||
UserManager = um;
|
|
||||||
PlexApi = plexApi;
|
|
||||||
PlexSettings = settings;
|
|
||||||
OmbiSettings = ombiSettings;
|
|
||||||
EmbyApi = embyApi;
|
|
||||||
EmbySettings = embySettings;
|
|
||||||
Audit = log;
|
|
||||||
}
|
|
||||||
|
|
||||||
private UserManager<OmbiUser> UserManager { get; }
|
|
||||||
private IPlexApi PlexApi { get; }
|
|
||||||
private IEmbyApi EmbyApi{ get; }
|
|
||||||
private ISettingsService<PlexSettings> PlexSettings { get; }
|
|
||||||
private ISettingsService<EmbySettings> EmbySettings { get; }
|
|
||||||
private ISettingsService<OmbiSettings> OmbiSettings { get; }
|
|
||||||
private IAuditRepository Audit { get; }
|
|
||||||
|
|
||||||
public async Task ValidateAsync(ResourceOwnerPasswordValidationContext context)
|
|
||||||
{
|
|
||||||
await Audit.Record(AuditType.None, AuditArea.Authentication, $"User {context.UserName} attempted to login", context.UserName);
|
|
||||||
var users = UserManager.Users;
|
|
||||||
if (await LocalUser(context, users))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var ombi = await OmbiSettings.GetSettingsAsync();
|
|
||||||
if (ombi.AllowExternalUsersToAuthenticate)
|
|
||||||
{
|
|
||||||
if (await PlexUser(context, users))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (await EmbyUser(context, users))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await Audit.Record(AuditType.Fail, AuditArea.Authentication, $"User {context.UserName} failed to login", context.UserName);
|
|
||||||
context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant, "Username or password is incorrect");
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<bool> PlexUser(ResourceOwnerPasswordValidationContext context, IQueryable<OmbiUser> users)
|
|
||||||
{
|
|
||||||
var signInResult = await PlexApi.SignIn(new UserRequest {login = context.UserName, password = context.Password});
|
|
||||||
if (signInResult?.user == null)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Do we have a local user?
|
|
||||||
return await GetUserDetails(context, users, UserType.PlexUser);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<bool> EmbyUser(ResourceOwnerPasswordValidationContext context, IQueryable<OmbiUser> users)
|
|
||||||
{
|
|
||||||
var embySettings = await EmbySettings.GetSettingsAsync();
|
|
||||||
var signInResult = await EmbyApi.LogIn(context.UserName, context.Password, embySettings.ApiKey,
|
|
||||||
embySettings.FullUri);
|
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(signInResult?.Name))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return await GetUserDetails(context, users, UserType.EmbyUser);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<bool> GetUserDetails(ResourceOwnerPasswordValidationContext context, IQueryable<OmbiUser> users, UserType userType)
|
|
||||||
{
|
|
||||||
var user = await users.FirstOrDefaultAsync(x => x.UserName == context.UserName && x.UserType == userType);
|
|
||||||
if (user != null)
|
|
||||||
{
|
|
||||||
var roles = await UserManager.GetRolesAsync(user);
|
|
||||||
var claims = new List<Claim>
|
|
||||||
{
|
|
||||||
new Claim(ClaimTypes.Name, user.UserName)
|
|
||||||
};
|
|
||||||
|
|
||||||
foreach (var role in roles)
|
|
||||||
{
|
|
||||||
claims.Add(new Claim(ClaimTypes.Role, role));
|
|
||||||
}
|
|
||||||
context.Result = new GrantValidationResult(user.UserName, "password", claims);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create the user?
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<bool> LocalUser(ResourceOwnerPasswordValidationContext context, IQueryable<OmbiUser> users)
|
|
||||||
{
|
|
||||||
var user = await users.FirstOrDefaultAsync(x => x.UserName == context.UserName && x.UserType == UserType.LocalUser);
|
|
||||||
|
|
||||||
if (user == null)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var passwordValid = await UserManager.CheckPasswordAsync(user, context.Password);
|
|
||||||
if (!passwordValid)
|
|
||||||
{
|
|
||||||
await Audit.Record(AuditType.Fail, AuditArea.Authentication, $"User {context.UserName} failed to login", context.UserName);
|
|
||||||
context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant, "Username or password is incorrect");
|
|
||||||
return true;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
var roles = await UserManager.GetRolesAsync(user);
|
|
||||||
var claims = new List<Claim>
|
|
||||||
{
|
|
||||||
new Claim(ClaimTypes.Name, user.UserName)
|
|
||||||
};
|
|
||||||
|
|
||||||
foreach (var role in roles)
|
|
||||||
{
|
|
||||||
claims.Add(new Claim(ClaimTypes.Role, role));
|
|
||||||
}
|
|
||||||
context.Result = new GrantValidationResult(user.UserName, "password", claims);
|
|
||||||
|
|
||||||
await Audit.Record(AuditType.Success, AuditArea.Authentication, $"User {context.UserName} has logged in", context.UserName);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -9,7 +9,6 @@
|
||||||
<PackageReference Include="AutoMapper" Version="6.1.0" />
|
<PackageReference Include="AutoMapper" Version="6.1.0" />
|
||||||
<PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="2.0.1" />
|
<PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="2.0.1" />
|
||||||
<PackageReference Include="Hangfire" Version="1.6.14" />
|
<PackageReference Include="Hangfire" Version="1.6.14" />
|
||||||
<PackageReference Include="IdentityServer4" Version="1.5.2" />
|
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Cryptography.KeyDerivation" Version="1.1.1" />
|
<PackageReference Include="Microsoft.AspNetCore.Cryptography.KeyDerivation" Version="1.1.1" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="1.1.2" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="1.1.2" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Design" Version="1.1.2" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Design" Version="1.1.2" />
|
||||||
|
|
|
@ -14,7 +14,6 @@ using Ombi.Api.TvMaze;
|
||||||
using Ombi.Core;
|
using Ombi.Core;
|
||||||
using Ombi.Core.Engine;
|
using Ombi.Core.Engine;
|
||||||
using Ombi.Core.Engine.Interfaces;
|
using Ombi.Core.Engine.Interfaces;
|
||||||
using Ombi.Core.IdentityResolver;
|
|
||||||
using Ombi.Core.Models.Requests;
|
using Ombi.Core.Models.Requests;
|
||||||
using Ombi.Core.Notifications;
|
using Ombi.Core.Notifications;
|
||||||
using Ombi.Core.Rule;
|
using Ombi.Core.Rule;
|
||||||
|
|
|
@ -7,28 +7,21 @@ import { IUserLogin, ILocalUser } from './IUserLogin';
|
||||||
|
|
||||||
import { tokenNotExpired, JwtHelper } from 'angular2-jwt';
|
import { tokenNotExpired, JwtHelper } from 'angular2-jwt';
|
||||||
|
|
||||||
import { Http, Headers, URLSearchParams } from '@angular/http';
|
import { Http, Headers } from '@angular/http';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AuthService extends ServiceHelpers {
|
export class AuthService extends ServiceHelpers {
|
||||||
constructor(http: Http) {
|
constructor(http: Http) {
|
||||||
super(http, '/connect/token');
|
super(http, '/api/v1/token');
|
||||||
}
|
}
|
||||||
|
|
||||||
jwtHelper: JwtHelper = new JwtHelper();
|
jwtHelper: JwtHelper = new JwtHelper();
|
||||||
|
|
||||||
login(login: IUserLogin): Observable<any> {
|
login(login: IUserLogin): Observable<any> {
|
||||||
this.headers = new Headers();
|
this.headers = new Headers();
|
||||||
this.headers.append('Content-Type', 'application/x-www-form-urlencoded');
|
this.headers.append('Content-Type', 'application/json');
|
||||||
let data = new URLSearchParams();
|
|
||||||
data.append('client_id', 'frontend');
|
|
||||||
data.append('scope', 'api');
|
|
||||||
data.append('client_secret', 'secret');
|
|
||||||
data.append('grant_type', 'password');
|
|
||||||
data.append('username', login.username);
|
|
||||||
data.append('password', login.password);
|
|
||||||
|
|
||||||
return this.http.post(`${this.url}/`, data.toString(), { headers: this.headers })
|
return this.http.post(`${this.url}/`, JSON.stringify(login), { headers: this.headers })
|
||||||
.map(this.extractData);
|
.map(this.extractData);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,8 @@
|
||||||
|
|
||||||
|
<img class="landing-header" src="/images/logo.png" width="300" />
|
||||||
|
<div class="landing-block shadow">
|
||||||
|
<div class="media">
|
||||||
|
<div id="contentBody" class="media-body">
|
||||||
<h4 class="media-heading landing-title">Create the Admin account</h4>
|
<h4 class="media-heading landing-title">Create the Admin account</h4>
|
||||||
<small>This account will be used to configure your settings and also manage all of the requests. Note: this should not be the same as your Plex/Emby account (you can change this later in the User Management Settings)</small>
|
<small>This account will be used to configure your settings and also manage all of the requests. Note: this should not be the same as your Plex/Emby account (you can change this later in the User Management Settings)</small>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
|
@ -17,4 +21,7 @@
|
||||||
<div style="text-align: center; margin-top: 20px">
|
<div style="text-align: center; margin-top: 20px">
|
||||||
<button (click)="createUser()" type="submit" class="btn btn-success-outline">Finish</button>
|
<button (click)="createUser()" type="submit" class="btn btn-success-outline">Finish</button>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
|
@ -1,4 +1,8 @@
|
||||||
|
|
||||||
|
<img class="landing-header" src="/images/logo.png" width="300" />
|
||||||
|
<div class="landing-block shadow">
|
||||||
|
<div class="media">
|
||||||
|
<div id="contentBody" class="media-body">
|
||||||
<h4 class="media-heading landing-title">Emby Authentication</h4>
|
<h4 class="media-heading landing-title">Emby Authentication</h4>
|
||||||
<div *ngIf="embySettings">
|
<div *ngIf="embySettings">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
|
@ -29,4 +33,7 @@
|
||||||
<div style="text-align: center; margin-top: 20px">
|
<div style="text-align: center; margin-top: 20px">
|
||||||
<a (click)="save()" id="embyApiKeySave" class="btn btn-primary-outline">Next <div id="spinner"></div></a>
|
<a (click)="save()" id="embyApiKeySave" class="btn btn-primary-outline">Next <div id="spinner"></div></a>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
|
@ -1,21 +1,29 @@
|
||||||
<div>
|
|
||||||
<h4 class="media-heading landing-title wizard-heading" id="statusTitle">Please choose your media server</h4>
|
<img class="landing-header" src="/images/logo.png" width="300" />
|
||||||
<div class="form-group">
|
<div class="landing-block shadow">
|
||||||
<div class="row">
|
<div class="media">
|
||||||
<a (click)="emby()" id="embyImg">
|
<div id="contentBody" class="media-body">
|
||||||
<img class="wizard-img" src="/images/emby-logo-dark.jpg" />
|
<h4 class="media-heading landing-title wizard-heading" id="statusTitle">Please choose your media server</h4>
|
||||||
</a>
|
<div class="form-group">
|
||||||
</div>
|
<div class="row">
|
||||||
<div class="row">
|
<a (click)="emby()" id="embyImg">
|
||||||
<a (click)="plex()" id="plexImg">
|
<img class="wizard-img" src="/images/emby-logo-dark.jpg" />
|
||||||
<img class="wizard-img" src="/images/plex-logo-reversed.png" />
|
</a>
|
||||||
</a>
|
</div>
|
||||||
</div>
|
<div class="row">
|
||||||
<div class="row">
|
<a (click)="plex()" id="plexImg">
|
||||||
<button (click)="skip()" class="btn btn-primary-outline wizard-img" id="plexImg">
|
<img class="wizard-img" src="/images/plex-logo-reversed.png" />
|
||||||
Skip
|
</a>
|
||||||
</button>
|
</div>
|
||||||
</div>
|
<div class="row">
|
||||||
|
<button (click)="skip()" class="btn btn-primary-outline wizard-img" id="plexImg">
|
||||||
|
Skip
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
|
@ -3,7 +3,6 @@ import { Router } from '@angular/router';
|
||||||
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
|
|
||||||
templateUrl: './mediaserver.component.html',
|
templateUrl: './mediaserver.component.html',
|
||||||
})
|
})
|
||||||
export class MediaServerComponent {
|
export class MediaServerComponent {
|
||||||
|
@ -19,8 +18,7 @@ export class MediaServerComponent {
|
||||||
this.router.navigate(['Wizard/Emby']);
|
this.router.navigate(['Wizard/Emby']);
|
||||||
}
|
}
|
||||||
|
|
||||||
skip()
|
skip() {
|
||||||
{
|
|
||||||
this.router.navigate(['Wizard/CreateAdmin']);
|
this.router.navigate(['Wizard/CreateAdmin']);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,4 +1,9 @@
|
||||||
<h4 class="media-heading landing-title">Plex Authentication</h4>
|
|
||||||
|
<img class="landing-header" src="/images/logo.png" width="300" />
|
||||||
|
<div class="landing-block shadow">
|
||||||
|
<div class="media">
|
||||||
|
<div id="contentBody" class="media-body">
|
||||||
|
<h4 class="media-heading landing-title">Plex Authentication</h4>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="username" class="control-label">Username and Password</label>
|
<label for="username" class="control-label">Username and Password</label>
|
||||||
<div>
|
<div>
|
||||||
|
@ -14,4 +19,7 @@
|
||||||
<div style="text-align: center; margin-top: 20px">
|
<div style="text-align: center; margin-top: 20px">
|
||||||
<button (click)="requestAuthToken()" class="btn btn-primary-outline">Request Token <i class="fa fa-key"></i></button>
|
<button (click)="requestAuthToken()" class="btn btn-primary-outline">Request Token <i class="fa fa-key"></i></button>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
106
src/Ombi/Controllers/TokenController.cs
Normal file
106
src/Ombi/Controllers/TokenController.cs
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IdentityModel.Tokens.Jwt;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Security.Claims;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Identity;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
using Microsoft.IdentityModel.Tokens;
|
||||||
|
using Ombi.Models;
|
||||||
|
using Ombi.Models.Identity;
|
||||||
|
using Ombi.Store.Entities;
|
||||||
|
using Ombi.Store.Repository;
|
||||||
|
|
||||||
|
namespace Ombi.Controllers
|
||||||
|
{
|
||||||
|
[ApiV1]
|
||||||
|
public class TokenController
|
||||||
|
{
|
||||||
|
public TokenController(UserManager<OmbiUser> um, IOptions<TokenAuthentication> ta,
|
||||||
|
IApplicationConfigRepository config)
|
||||||
|
{
|
||||||
|
UserManager = um;
|
||||||
|
TokenAuthenticationOptions = ta.Value;
|
||||||
|
Config = config;
|
||||||
|
}
|
||||||
|
|
||||||
|
private TokenAuthentication TokenAuthenticationOptions { get; }
|
||||||
|
private IApplicationConfigRepository Config { get; }
|
||||||
|
private UserManager<OmbiUser> UserManager { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the token.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="model">The model.</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
[HttpPost]
|
||||||
|
public async Task<IActionResult> GetToken([FromBody] UserAuthModel model)
|
||||||
|
{
|
||||||
|
|
||||||
|
var user = await UserManager.FindByNameAsync(model.Username);
|
||||||
|
|
||||||
|
if (user == null)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify Password
|
||||||
|
if ((await UserManager.CheckPasswordAsync(user, model.Password)))
|
||||||
|
{
|
||||||
|
// Get the url
|
||||||
|
var url = Config.Get(ConfigurationTypes.Url);
|
||||||
|
var port = Config.Get(ConfigurationTypes.Port);
|
||||||
|
|
||||||
|
#if !DEBUG
|
||||||
|
var audience = $"{url}:{port}";
|
||||||
|
#else
|
||||||
|
|
||||||
|
var audience = $"http://localhost:52038/";
|
||||||
|
#endif
|
||||||
|
var roles = await UserManager.GetRolesAsync(user);
|
||||||
|
|
||||||
|
var claims = new List<Claim>
|
||||||
|
{
|
||||||
|
new Claim(JwtRegisteredClaimNames.Sub, user.UserName),
|
||||||
|
new Claim("name", user.UserName),
|
||||||
|
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
|
||||||
|
};
|
||||||
|
claims.AddRange(roles.Select(role => new Claim("role", role)));
|
||||||
|
|
||||||
|
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(TokenAuthenticationOptions.SecretKey));
|
||||||
|
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
|
||||||
|
|
||||||
|
var token = new JwtSecurityToken(
|
||||||
|
claims: claims,
|
||||||
|
expires: DateTime.UtcNow.AddHours(5),
|
||||||
|
signingCredentials: creds,
|
||||||
|
audience: "Ombi", issuer:"Ombi"
|
||||||
|
);
|
||||||
|
|
||||||
|
return new JsonResult(new
|
||||||
|
{
|
||||||
|
access_token = new JwtSecurityTokenHandler().WriteToken(token),
|
||||||
|
expiration = token.ValidTo
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Refreshes the token.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="model">The model.</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
/// <exception cref="NotImplementedException"></exception>
|
||||||
|
[HttpPost("refresh")]
|
||||||
|
public async Task<IActionResult> RefreshToken([FromBody] UserAuthModel model)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,59 +0,0 @@
|
||||||
using System.Collections.Generic;
|
|
||||||
using IdentityModel;
|
|
||||||
using IdentityServer4.Models;
|
|
||||||
|
|
||||||
namespace Ombi
|
|
||||||
{
|
|
||||||
public class IdentityConfig
|
|
||||||
{
|
|
||||||
// scopes define the resources in your system
|
|
||||||
public static IEnumerable<IdentityResource> GetIdentityResources()
|
|
||||||
{
|
|
||||||
return new List<IdentityResource>
|
|
||||||
{
|
|
||||||
new IdentityResources.OpenId(),
|
|
||||||
new IdentityResources.Profile(),
|
|
||||||
new IdentityResource {
|
|
||||||
Name = "role",
|
|
||||||
UserClaims = new List<string> {"role"}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public static IEnumerable<ApiResource> GetApiResources()
|
|
||||||
{
|
|
||||||
return new List<ApiResource>
|
|
||||||
{
|
|
||||||
new ApiResource("api", "API")
|
|
||||||
{
|
|
||||||
UserClaims = {JwtClaimTypes.Name, JwtClaimTypes.Role, JwtClaimTypes.Email, JwtClaimTypes.Id},
|
|
||||||
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// clients want to access resources (aka scopes)
|
|
||||||
public static IEnumerable<Client> GetClients()
|
|
||||||
{
|
|
||||||
// client credentials client
|
|
||||||
return new List<Client>
|
|
||||||
{
|
|
||||||
new Client
|
|
||||||
{
|
|
||||||
ClientId = "frontend",
|
|
||||||
AllowedGrantTypes = GrantTypes.ResourceOwnerPassword,
|
|
||||||
|
|
||||||
ClientSecrets =
|
|
||||||
{
|
|
||||||
new Secret("secret".Sha256()) // TODO read up on what this actually is
|
|
||||||
},
|
|
||||||
AllowedScopes =
|
|
||||||
{
|
|
||||||
"api",
|
|
||||||
},
|
|
||||||
AccessTokenType = AccessTokenType.Jwt
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
7
src/Ombi/Models/Identity/TokenAuthentication.cs
Normal file
7
src/Ombi/Models/Identity/TokenAuthentication.cs
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
namespace Ombi.Models.Identity
|
||||||
|
{
|
||||||
|
public class TokenAuthentication
|
||||||
|
{
|
||||||
|
public string SecretKey { get; set; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -43,9 +43,6 @@
|
||||||
<PackageReference Include="Hangfire.MemoryStorage.Core" Version="1.4.0" />
|
<PackageReference Include="Hangfire.MemoryStorage.Core" Version="1.4.0" />
|
||||||
<PackageReference Include="Hangfire.RecurringJobExtensions" Version="1.1.6" />
|
<PackageReference Include="Hangfire.RecurringJobExtensions" Version="1.1.6" />
|
||||||
<PackageReference Include="Hangfire.SQLite.Core" Version="1.0.2" />
|
<PackageReference Include="Hangfire.SQLite.Core" Version="1.0.2" />
|
||||||
<PackageReference Include="IdentityServer4" Version="1.5.2" />
|
|
||||||
<PackageReference Include="IdentityServer4.AccessTokenValidation" Version="1.2.1" />
|
|
||||||
<PackageReference Include="IdentityServer4.AspNetIdentity" Version="1.0.1" />
|
|
||||||
<PackageReference Include="Microsoft.AspNetCore" Version="1.1.2" />
|
<PackageReference Include="Microsoft.AspNetCore" Version="1.1.2" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="1.1.2" />
|
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="1.1.2" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="1.1.3" />
|
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="1.1.3" />
|
||||||
|
|
|
@ -2,16 +2,13 @@
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Security.Principal;
|
using System.Security.Principal;
|
||||||
|
using System.Text;
|
||||||
using AutoMapper;
|
using AutoMapper;
|
||||||
using AutoMapper.EquivalencyExpression;
|
using AutoMapper.EquivalencyExpression;
|
||||||
using Hangfire;
|
using Hangfire;
|
||||||
using Hangfire.MemoryStorage;
|
using Hangfire.MemoryStorage;
|
||||||
using Hangfire.SQLite;
|
|
||||||
using IdentityServer4.Services;
|
|
||||||
using IdentityServer4.Validation;
|
|
||||||
using Microsoft.AspNetCore.Builder;
|
using Microsoft.AspNetCore.Builder;
|
||||||
using Microsoft.AspNetCore.Hosting;
|
using Microsoft.AspNetCore.Hosting;
|
||||||
using Microsoft.AspNetCore.Hosting.Server.Features;
|
|
||||||
using Microsoft.AspNetCore.Http;
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
|
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
|
||||||
using Microsoft.AspNetCore.SpaServices.Webpack;
|
using Microsoft.AspNetCore.SpaServices.Webpack;
|
||||||
|
@ -25,10 +22,10 @@ using Microsoft.Extensions.Options;
|
||||||
using Microsoft.Extensions.PlatformAbstractions;
|
using Microsoft.Extensions.PlatformAbstractions;
|
||||||
using Microsoft.IdentityModel.Tokens;
|
using Microsoft.IdentityModel.Tokens;
|
||||||
using Ombi.Config;
|
using Ombi.Config;
|
||||||
using Ombi.Core.IdentityResolver;
|
|
||||||
using Ombi.DependencyInjection;
|
using Ombi.DependencyInjection;
|
||||||
using Ombi.Helpers;
|
using Ombi.Helpers;
|
||||||
using Ombi.Mapping;
|
using Ombi.Mapping;
|
||||||
|
using Ombi.Models.Identity;
|
||||||
using Ombi.Schedule;
|
using Ombi.Schedule;
|
||||||
using Ombi.Store.Context;
|
using Ombi.Store.Context;
|
||||||
using Ombi.Store.Entities;
|
using Ombi.Store.Entities;
|
||||||
|
@ -83,15 +80,16 @@ namespace Ombi
|
||||||
.AddEntityFrameworkStores<OmbiContext>()
|
.AddEntityFrameworkStores<OmbiContext>()
|
||||||
.AddDefaultTokenProviders();
|
.AddDefaultTokenProviders();
|
||||||
|
|
||||||
services.AddIdentityServer()
|
|
||||||
.AddTemporarySigningCredential()
|
//services.AddIdentityServer()
|
||||||
.AddInMemoryPersistedGrants()
|
// .AddTemporarySigningCredential()
|
||||||
.AddInMemoryIdentityResources(IdentityConfig.GetIdentityResources())
|
// .AddInMemoryPersistedGrants()
|
||||||
.AddInMemoryApiResources(IdentityConfig.GetApiResources())
|
// .AddInMemoryIdentityResources(IdentityConfig.GetIdentityResources())
|
||||||
.AddInMemoryClients(IdentityConfig.GetClients())
|
// .AddInMemoryApiResources(IdentityConfig.GetApiResources())
|
||||||
.AddAspNetIdentity<OmbiUser>()
|
// .AddInMemoryClients(IdentityConfig.GetClients())
|
||||||
.Services.AddTransient<IResourceOwnerPasswordValidator, OmbiOwnerPasswordValidator>()
|
// .AddAspNetIdentity<OmbiUser>()
|
||||||
.AddTransient<IProfileService, OmbiProfileService>();
|
// .Services.AddTransient<IResourceOwnerPasswordValidator, OmbiOwnerPasswordValidator>()
|
||||||
|
// .AddTransient<IProfileService, OmbiProfileService>();
|
||||||
|
|
||||||
services.Configure<IdentityOptions>(options =>
|
services.Configure<IdentityOptions>(options =>
|
||||||
{
|
{
|
||||||
|
@ -151,10 +149,9 @@ namespace Ombi
|
||||||
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
|
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
|
||||||
services.AddScoped<IPrincipal>(sp => sp.GetService<IHttpContextAccessor>().HttpContext.User);
|
services.AddScoped<IPrincipal>(sp => sp.GetService<IHttpContextAccessor>().HttpContext.User);
|
||||||
|
|
||||||
|
|
||||||
//services.Configure<TokenAuthenticationOptions>(Configuration.GetSection("TokenAuthentication"));
|
|
||||||
services.Configure<ApplicationSettings>(Configuration.GetSection("ApplicationSettings"));
|
services.Configure<ApplicationSettings>(Configuration.GetSection("ApplicationSettings"));
|
||||||
services.Configure<UserSettings>(Configuration.GetSection("UserSettings"));
|
services.Configure<UserSettings>(Configuration.GetSection("UserSettings"));
|
||||||
|
services.Configure<TokenAuthentication>(Configuration.GetSection("TokenAuthentication"));
|
||||||
services.Configure<LandingPageBackground>(Configuration.GetSection("LandingPageBackground"));
|
services.Configure<LandingPageBackground>(Configuration.GetSection("LandingPageBackground"));
|
||||||
|
|
||||||
services.AddHangfire(x =>
|
services.AddHangfire(x =>
|
||||||
|
@ -179,8 +176,8 @@ namespace Ombi
|
||||||
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
|
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
|
||||||
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, IMemoryCache cache)
|
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, IMemoryCache cache)
|
||||||
{
|
{
|
||||||
var options = (IOptions<UserSettings>) app.ApplicationServices.GetService(
|
var tokenOptions = (IOptions<TokenAuthentication>)app.ApplicationServices.GetService(
|
||||||
typeof(IOptions<UserSettings>));
|
typeof(IOptions<TokenAuthentication>));
|
||||||
|
|
||||||
var ctx = (IOmbiContext)app.ApplicationServices.GetService(typeof(IOmbiContext));
|
var ctx = (IOmbiContext)app.ApplicationServices.GetService(typeof(IOmbiContext));
|
||||||
|
|
||||||
|
@ -190,26 +187,54 @@ namespace Ombi
|
||||||
|
|
||||||
Console.WriteLine($"Using Url {url.Value}:{port.Value} for Identity Server");
|
Console.WriteLine($"Using Url {url.Value}:{port.Value} for Identity Server");
|
||||||
app.UseIdentity();
|
app.UseIdentity();
|
||||||
app.UseIdentityServer();
|
|
||||||
app.UseIdentityServerAuthentication(new IdentityServerAuthenticationOptions
|
|
||||||
{
|
|
||||||
#if !DEBUG
|
#if !DEBUG
|
||||||
Authority = $"{url.Value}:{port.Value}",
|
var audience = $"{url.Value}:{port.Value}";
|
||||||
#else
|
#else
|
||||||
Authority = $"http://localhost:52038/",
|
|
||||||
|
var audience = $"http://localhost:52038/";
|
||||||
#endif
|
#endif
|
||||||
ApiName = "api",
|
|
||||||
ApiSecret = "secret",
|
|
||||||
|
|
||||||
EnableCaching = true,
|
var tokenValidationParameters = new TokenValidationParameters
|
||||||
CacheDuration = TimeSpan.FromMinutes(10), // that's the default
|
{
|
||||||
RequireHttpsMetadata = options.Value.UseHttps, // FOR DEV set to false
|
|
||||||
|
ValidateIssuerSigningKey = true,
|
||||||
|
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(tokenOptions.Value.SecretKey)),
|
||||||
|
|
||||||
|
RequireExpirationTime = true,
|
||||||
|
ValidateLifetime = true,
|
||||||
|
ValidAudience = "Ombi",
|
||||||
|
ValidIssuer = "Ombi",
|
||||||
|
ClockSkew = TimeSpan.Zero
|
||||||
|
};
|
||||||
|
|
||||||
|
app.UseJwtBearerAuthentication(new JwtBearerOptions()
|
||||||
|
{
|
||||||
|
Audience = "Ombi",
|
||||||
AutomaticAuthenticate = true,
|
AutomaticAuthenticate = true,
|
||||||
AutomaticChallenge = true,
|
TokenValidationParameters = tokenValidationParameters
|
||||||
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// app.UseIdentityServer();
|
||||||
|
// app.UseIdentityServerAuthentication(new IdentityServerAuthenticationOptions
|
||||||
|
// {
|
||||||
|
//#if !DEBUG
|
||||||
|
// Authority = $"{url.Value}:{port.Value}",
|
||||||
|
//#else
|
||||||
|
// Authority = $"http://localhost:52038/",
|
||||||
|
//#endif
|
||||||
|
// ApiName = "api",
|
||||||
|
// ApiSecret = "secret",
|
||||||
|
|
||||||
|
// EnableCaching = true,
|
||||||
|
// CacheDuration = TimeSpan.FromMinutes(10), // that's the default
|
||||||
|
// RequireHttpsMetadata = options.Value.UseHttps, // FOR DEV set to false
|
||||||
|
// AutomaticAuthenticate = true,
|
||||||
|
// AutomaticChallenge = true,
|
||||||
|
|
||||||
|
|
||||||
|
// });
|
||||||
|
|
||||||
loggerFactory.AddSerilog();
|
loggerFactory.AddSerilog();
|
||||||
|
|
||||||
if (env.IsDevelopment())
|
if (env.IsDevelopment())
|
||||||
|
|
|
@ -16,11 +16,7 @@
|
||||||
"UseHttps": false
|
"UseHttps": false
|
||||||
},
|
},
|
||||||
"TokenAuthentication": {
|
"TokenAuthentication": {
|
||||||
"SecretKey": "secretkey_secretkey123!",
|
"SecretKey": "secretkey_secretkey123!"
|
||||||
"Issuer": "OmbiIssuer",
|
|
||||||
"Audience": "OmbiAudience",
|
|
||||||
"TokenPath": "/api/v1/token/",
|
|
||||||
"CookieName": "access_token"
|
|
||||||
},
|
},
|
||||||
"LandingPageBackground": {
|
"LandingPageBackground": {
|
||||||
"Movies": [
|
"Movies": [
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue