Browse Source

- Added client management to user's settings page.

- Cleaned up identity server pages.
- Added paste migration to service worker.
core
Teknikode 4 years ago
parent
commit
179a27dd26
  1. 6
      IdentityServer/Content/common.css
  2. 5
      IdentityServer/Controllers/ConsentController.cs
  3. 116
      IdentityServer/Controllers/ManageController.cs
  4. 5
      IdentityServer/Models/Manage/CreateClientModel.cs
  5. 12
      IdentityServer/Models/Manage/DeleteClientModel.cs
  6. 13
      IdentityServer/Models/Manage/GetClientModel.cs
  7. 12
      IdentityServer/Models/Manage/GetClientsModel.cs
  8. 7
      IdentityServer/Views/Account/LoggedOut.cshtml
  9. 37
      IdentityServer/Views/Account/Logout.cshtml
  10. 136
      IdentityServer/Views/Consent/Index.cshtml
  11. 36
      IdentityServer/Views/Consent/_ScopeListItem.cshtml
  12. 140
      IdentityServer/Views/Grants/Index.cshtml
  13. 7
      IdentityServer/Views/Home/Index.cshtml
  14. 5
      ServiceWorker/ArgumentOptions.cs
  15. 9
      ServiceWorker/Program.cs
  16. 2
      ServiceWorker/ServiceWorker.csproj
  17. 64
      ServiceWorker/TeknikMigration.cs
  18. 32
      Teknik/Areas/Paste/PasteHelper.cs
  19. 79
      Teknik/Areas/User/Controllers/UserController.cs
  20. 53
      Teknik/Areas/User/Utility/IdentityHelper.cs
  21. 17
      Teknik/Areas/User/ViewModels/ClientViewModel.cs
  22. 6
      Teknik/Areas/User/ViewModels/DeveloperSettingsViewModel.cs
  23. 25
      Teknik/Areas/User/Views/User/Settings/ClientView.cshtml
  24. 41
      Teknik/Areas/User/Views/User/Settings/DeveloperSettings.cshtml
  25. 8
      Teknik/Areas/User/Views/User/Settings/Settings.cshtml
  26. 1005
      Teknik/Data/Migrations/20180624191511_UserLoginInfo.Designer.cs
  27. 40
      Teknik/Data/Migrations/20180624191511_UserLoginInfo.cs
  28. 789
      Teknik/Data/Migrations/20181018071735_IdentityAuth.Designer.cs
  29. 277
      Teknik/Data/Migrations/20181018071735_IdentityAuth.cs
  30. 733
      Teknik/Data/Migrations/20181021081119_RemovedVerificationTables.Designer.cs
  31. 73
      Teknik/Data/Migrations/20181021081119_RemovedVerificationTables.cs
  32. 51
      Teknik/Data/Migrations/20181121074110_PasteFiles.cs
  33. 4
      Teknik/Data/Migrations/20181222043715_InitialMigration.Designer.cs
  34. 641
      Teknik/Data/Migrations/20181222043715_InitialMigration.cs
  35. 86
      Teknik/IdentityServerConfig.cs
  36. 6
      Teknik/Routes.cs
  37. 98
      Teknik/Scripts/User/DeveloperSettings.js
  38. 3
      Teknik/Startup.cs
  39. 4
      Teknik/Teknik.csproj
  40. 19
      Teknik/TeknikMigration.cs
  41. 7
      Teknik/bundleconfig.json

6
IdentityServer/Content/common.css

@ -17,6 +17,12 @@ label { @@ -17,6 +17,12 @@ label {
position: relative;
top: -10px;
}
.abc-checkbox label {
display: inline-block !important;
padding-left: 10px !important;
}
.logged-out iframe {
display: none;
width: 0;

5
IdentityServer/Controllers/ConsentController.cs

@ -2,6 +2,7 @@ @@ -2,6 +2,7 @@
using IdentityServer4.Stores;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using System;
using System.Threading.Tasks;
using Teknik.Configuration;
using Teknik.IdentityServer.Models;
@ -42,7 +43,7 @@ namespace Teknik.IdentityServer.Controllers @@ -42,7 +43,7 @@ namespace Teknik.IdentityServer.Controllers
return View("Index", vm);
}
return View("Error");
throw new ApplicationException($"Unable to load consent view model.");
}
/// <summary>
@ -69,7 +70,7 @@ namespace Teknik.IdentityServer.Controllers @@ -69,7 +70,7 @@ namespace Teknik.IdentityServer.Controllers
return View("Index", result.ViewModel);
}
return View("Error");
throw new ApplicationException($"Unable to load consent view model.");
}
}
}

116
IdentityServer/Controllers/ManageController.cs

@ -3,6 +3,12 @@ using System.Collections.Generic; @@ -3,6 +3,12 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using IdentityServer4;
using IdentityServer4.EntityFramework.DbContexts;
using IdentityServer4.EntityFramework.Entities;
using IdentityServer4.EntityFramework.Mappers;
using IdentityServer4.Models;
using IdentityServer4.Stores;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
@ -11,6 +17,7 @@ using Teknik.Configuration; @@ -11,6 +17,7 @@ using Teknik.Configuration;
using Teknik.IdentityServer.Models;
using Teknik.IdentityServer.Models.Manage;
using Teknik.Logging;
using Teknik.Utilities;
namespace Teknik.IdentityServer.Controllers
{
@ -400,6 +407,115 @@ namespace Teknik.IdentityServer.Controllers @@ -400,6 +407,115 @@ namespace Teknik.IdentityServer.Controllers
return new JsonResult(new { success = false, message = "User does not exist." });
}
[HttpGet]
public async Task<IActionResult> GetClient(string username, string clientId, [FromServices] IClientStore clientStore, [FromServices] ConfigurationDbContext configContext)
{
if (string.IsNullOrEmpty(username))
return new JsonResult(new { success = false, message = "Username is required" });
if (string.IsNullOrEmpty(clientId))
return new JsonResult(new { success = false, message = "Client Id is required" });
var client = configContext.Clients.FirstOrDefault(c =>
c.ClientId == clientId &&
c.Properties.Exists(p =>
p.Key == "username" &&
p.Value.ToLower() == username.ToLower())
);
if (client != null)
{
var foundClient = await clientStore.FindClientByIdAsync(client.ClientId);
return new JsonResult(new { success = true, data = foundClient });
}
return new JsonResult(new { success = false, message = "Client does not exist." });
}
[HttpGet]
public async Task<IActionResult> GetClients(string username, [FromServices] IClientStore clientStore, [FromServices] ConfigurationDbContext configContext)
{
if (string.IsNullOrEmpty(username))
return new JsonResult(new { success = false, message = "Username is required" });
var foundClientIds = configContext.Clients.Where(c =>
c.Properties.Exists(p =>
p.Key == "username" &&
p.Value.ToLower() == username.ToLower())
).Select(c => c.ClientId);
var clients = new List<IdentityServer4.Models.Client>();
foreach (var clientId in foundClientIds)
{
var foundClient = await clientStore.FindClientByIdAsync(clientId);
if (foundClient != null)
clients.Add(foundClient);
}
return new JsonResult(new { success = true, data = clients });
}
[HttpPost]
public IActionResult CreateClient(CreateClientModel model, [FromServices] ConfigurationDbContext configContext)
{
var clientId = StringHelper.RandomString(20, "abcdefghjkmnpqrstuvwxyz1234567890");
var clientSecret = StringHelper.RandomString(40, "abcdefghjkmnpqrstuvwxyz1234567890");
var client = new IdentityServer4.Models.Client
{
Properties = new Dictionary<string, string>()
{
{ "username", model.Username }
},
ClientId = clientId,
ClientName = model.Name,
AllowedGrantTypes = new List<string>()
{
GrantType.AuthorizationCode,
GrantType.ClientCredentials
},
ClientSecrets =
{
new IdentityServer4.Models.Secret(clientSecret.Sha256())
},
RequireConsent = true,
RedirectUris =
{
model.RedirectURI
},
PostLogoutRedirectUris =
{
model.PostLogoutRedirectURI
},
AllowedScopes = model.AllowedScopes,
AllowOfflineAccess = true
};
configContext.Clients.Add(client.ToEntity());
configContext.SaveChanges();
return new JsonResult(new { success = true, data = new { id = clientId, secret = clientSecret } });
}
[HttpPost]
public async Task<IActionResult> DeleteClient(DeleteClientModel model, [FromServices] IClientStore clientStore, [FromServices] ConfigurationDbContext configContext)
{
var foundClient = await clientStore.FindClientByIdAsync(model.ClientId);
if (foundClient != null)
{
configContext.Clients.Remove(foundClient.ToEntity());
configContext.SaveChanges();
return new JsonResult(new { success = true });
}
return new JsonResult(new { success = false, message = "Client does not exist." });
}
private string FormatKey(string unformattedKey)
{
var result = new StringBuilder();

5
IdentityServer/Models/Manage/CreateClientModel.cs

@ -7,5 +7,10 @@ namespace Teknik.IdentityServer.Models.Manage @@ -7,5 +7,10 @@ namespace Teknik.IdentityServer.Models.Manage
{
public class CreateClientModel
{
public string Username { get; set; }
public string Name { get; set; }
public string RedirectURI { get; set; }
public string PostLogoutRedirectURI { get; set; }
public ICollection<string> AllowedScopes { get; set; }
}
}

12
IdentityServer/Models/Manage/DeleteClientModel.cs

@ -0,0 +1,12 @@ @@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Teknik.IdentityServer.Models.Manage
{
public class DeleteClientModel
{
public string ClientId { get; set; }
}
}

13
IdentityServer/Models/Manage/GetClientModel.cs

@ -0,0 +1,13 @@ @@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Teknik.IdentityServer.Models.Manage
{
public class GetClientModel
{
public string Username { get; set; }
public string ClientID { get; set; }
}
}

12
IdentityServer/Models/Manage/GetClientsModel.cs

@ -0,0 +1,12 @@ @@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Teknik.IdentityServer.Models.Manage
{
public class GetClientsModel
{
public string Username { get; set; }
}
}

7
IdentityServer/Views/Account/LoggedOut.cshtml

@ -8,10 +8,9 @@ @@ -8,10 +8,9 @@
<div class="container">
<div class="row">
<div class="col-md-12 text-center">
<h1>
Logout
<small>You are now logged out</small>
</h1>
<h2>
You are now logged out
</h2>
@if (Model.PostLogoutRedirectUri != null)
{

37
IdentityServer/Views/Account/Logout.cshtml

@ -1,23 +1,30 @@ @@ -1,23 +1,30 @@
@model LogoutViewModel
<div class="logout-page">
<div class="page-header">
<h1>Logout</h1>
</div>
<div class="container">
<div class="row">
<div class="col-sm-6">
<p>Would you like to logout of IdentityServer?</p>
<form asp-action="Logout">
<input type="hidden" name="logoutId" value="@Model.LogoutId" />
<fieldset>
<div class="form-group">
<button class="btn btn-primary">Yes</button>
<div class="col-sm-8 col-sm-offset-2 text-center">
<div class="logout-page">
<div class="page-header">
<h1>Logout</h1>
</div>
<div class="row">
<div class="col-sm-8 col-sm-offset-2">
<p>Would you like to logout of IdentityServer?</p>
<form asp-action="Logout">
<input type="hidden" name="logoutId" value="@Model.LogoutId" />
<fieldset>
<div class="form-group">
<button class="btn btn-primary">Yes</button>
</div>
</fieldset>
</form>
</div>
</fieldset>
</form>
</div>
</div>
</div>
</div>
</div>
<bundle src="js/signout-redirect.min.js" append-version="true"></bundle>
<bundle src="js/signout-redirect.min.js" append-version="true"></bundle>

136
IdentityServer/Views/Consent/Index.cshtml

@ -1,82 +1,86 @@ @@ -1,82 +1,86 @@
@model ConsentViewModel
<div class="page-consent">
<div class="row page-header">
<div class="col-sm-10">
@if (Model.ClientLogoUrl != null)
{
<div class="client-logo"><img src="@Model.ClientLogoUrl"></div>
}
<h1>
@Model.ClientName
<small>is requesting your permission</small>
</h1>
</div>
</div>
<div class="container">
<div class="row">
<div class="col-sm-8">
@Html.Partial("_ValidationSummary")
<div class="col-sm-12 text-center">
<div class="page-consent">
<div class="row page-header">
<div class="col-sm-10 col-sm-offset-1">
@if (Model.ClientLogoUrl != null)
{
<div class="client-logo"><img src="@Model.ClientLogoUrl"></div>
}
<h1>
@Model.ClientName
<small>is requesting your permission</small>
</h1>
</div>
</div>
<div class="row">
<div class="col-sm-10 col-sm-offset-1">
@await Html.PartialAsync("_ValidationSummary")
<form asp-action="Index" class="consent-form">
<input type="hidden" asp-for="ReturnUrl" />
<form asp-action="Index" class="consent-form">
<input type="hidden" asp-for="ReturnUrl" />
<div>Uncheck the permissions you do not wish to grant.</div>
<div>Uncheck the permissions you do not wish to grant.</div>
@if (Model.IdentityScopes.Any())
{
<div class="panel panel-default consent-buttons">
<div class="panel-heading">
<span class="glyphicon glyphicon-user"></span>
Personal Information
</div>
<ul class="list-group">
@foreach (var scope in Model.IdentityScopes)
@if (Model.IdentityScopes.Any())
{
@Html.Partial("_ScopeListItem", scope)
<div class="panel panel-default consent-buttons">
<div class="panel-heading">
<span class="glyphicon glyphicon-user"></span>
Personal Information
</div>
<ul class="list-group">
@foreach (var scope in Model.IdentityScopes)
{
@await Html.PartialAsync("_ScopeListItem", scope)
}
</ul>
</div>
}
</ul>
</div>
}
@if (Model.ResourceScopes.Any())
{
<div class="panel panel-default">
<div class="panel-heading">
<span class="glyphicon glyphicon-tasks"></span>
Application Access
</div>
<ul class="list-group">
@foreach (var scope in Model.ResourceScopes)
@if (Model.ResourceScopes.Any())
{
@Html.Partial("_ScopeListItem", scope)
<div class="panel panel-default">
<div class="panel-heading">
<span class="glyphicon glyphicon-tasks"></span>
Application Access
</div>
<ul class="list-group">
@foreach (var scope in Model.ResourceScopes)
{
@await Html.PartialAsync("_ScopeListItem", scope)
}
</ul>
</div>
}
</ul>
</div>
}
@if (Model.AllowRememberConsent)
{
<div class="consent-remember">
<label>
<input class="consent-scopecheck" asp-for="RememberConsent" />
<strong>Remember My Decision</strong>
</label>
</div>
}
@if (Model.AllowRememberConsent)
{
<div class="consent-remember abc-checkbox">
<input class="consent-scopecheck" asp-for="RememberConsent" />
<label asp-for="RememberConsent"><strong>Remember My Decision</strong></label>
</div>
}
<div class="consent-buttons">
<button name="button" value="yes" class="btn btn-primary" autofocus>Yes, Allow</button>
<button name="button" value="no" class="btn">No, Do Not Allow</button>
@if (Model.ClientUrl != null)
{
<a class="pull-right btn btn-default" target="_blank" href="@Model.ClientUrl">
<span class="glyphicon glyphicon-info-sign"></span>
<strong>@Model.ClientName</strong>
</a>
}
<div class="consent-buttons">
<button name="button" value="yes" class="btn btn-primary" autofocus>Yes, Allow</button>
<button name="button" value="no" class="btn">No, Do Not Allow</button>
@if (Model.ClientUrl != null)
{
<a class="pull-right btn btn-default" target="_blank" href="@Model.ClientUrl">
<span class="glyphicon glyphicon-info-sign"></span>
<strong>@Model.ClientName</strong>
</a>
}
</div>
</form>
</div>
</div>
</form>
</div>
</div>
</div>
</div>

36
IdentityServer/Views/Consent/_ScopeListItem.cshtml

@ -1,30 +1,30 @@ @@ -1,30 +1,30 @@
@model ScopeViewModel
<li class="list-group-item">
<label>
<input class="consent-scopecheck"
type="checkbox"
<li class="list-group-item abc-checkbox">
@if (Model.Required)
{
<input type="hidden"
name="ScopesConsented"
id="scopes_@Model.Name"
value="@Model.Name"
checked="@Model.Checked"
disabled="@Model.Required" />
@if (Model.Required)
{
<input type="hidden"
name="ScopesConsented"
value="@Model.Name" />
}
value="@Model.Name" />
}
<input class="consent-scopecheck"
type="checkbox"
name="ScopesConsented"
id="scopes_@Model.Name"
value="@Model.Name"
checked="@Model.Checked"
disabled="@Model.Required" />
<label for="ScopesConsented">
<strong>@Model.DisplayName</strong>
@if (Model.Emphasize)
{
<span class="glyphicon glyphicon-exclamation-sign"></span>
}
@if (Model.Required)
{
<span><em>(required)</em></span>
}
</label>
@if (Model.Required)
{
<span><em>(required)</em></span>
}
@if (Model.Description != null)
{
<div class="consent-description">

140
IdentityServer/Views/Grants/Index.cshtml

@ -1,79 +1,85 @@ @@ -1,79 +1,85 @@
@model GrantsViewModel
<div class="grants">
<div class="row page-header">
<div class="col-sm-10">
<h1>
Client Application Access
</h1>
<div>Below is the list of applications you have given access to and the names of the resources they have access to.</div>
</div>
</div>
@if (Model.Grants.Any() == false)
{
<div class="row">
<div class="col-sm-8">
<div class="alert alert-info">
You have not given access to any applications
</div>
</div>
</div>
}
else
{
foreach (var grant in Model.Grants)
{
<div class="row grant">
<div class="col-sm-2">
@if (grant.ClientLogoUrl != null)
{
<img src="@grant.ClientLogoUrl">
}
</div>
<div class="col-sm-8">
<div class="clientname">@grant.ClientName</div>
<div>
<span class="created">Created:</span> @grant.Created.ToString("yyyy-MM-dd")
<div class="container">
<div class="row">
<div class="col-md-12 text-center">
<div class="grants">
<div class="row page-header">
<div class="col-sm-12">
<h1>
Client Application Access
</h1>
<div>Below is the list of applications you have given access to and the names of the resources they have access to.</div>
</div>
@if (grant.Expires.HasValue)
{
<div>
<span class="expires">Expires:</span> @grant.Expires.Value.ToString("yyyy-MM-dd")
</div>
@if (Model.Grants.Any() == false)
{
<div class="row">
<div class="col-sm-8">
<div class="alert alert-info">
You have not given access to any applications
</div>
</div>
}
@if (grant.IdentityGrantNames.Any())
</div>
}
else
{
foreach (var grant in Model.Grants)
{
<div>
<div class="granttype">Identity Grants</div>
<ul>
@foreach (var name in grant.IdentityGrantNames)
{
<li>@name</li>
<div class="row grant">
<div class="col-sm-2">
@if (grant.ClientLogoUrl != null)
{
<img src="@grant.ClientLogoUrl">
}
</ul>
</div>
}
@if (grant.ApiGrantNames.Any())
{
<div>
<div class="granttype">API Grants</div>
<ul>
@foreach (var name in grant.ApiGrantNames)
</div>
<div class="col-sm-8">
<div class="clientname">@grant.ClientName</div>
<div>
<span class="created">Created:</span> @grant.Created.ToString("yyyy-MM-dd")
</div>
@if (grant.Expires.HasValue)
{
<div>
<span class="expires">Expires:</span> @grant.Expires.Value.ToString("yyyy-MM-dd")
</div>
}
@if (grant.IdentityGrantNames.Any())
{
<li>@name</li>
<div>
<div class="granttype">Identity Grants</div>
<ul>
@foreach (var name in grant.IdentityGrantNames)
{
<li>@name</li>
}
</ul>
</div>
}
</ul>
@if (grant.ApiGrantNames.Any())
{
<div>
<div class="granttype">API Grants</div>
<ul>
@foreach (var name in grant.ApiGrantNames)
{
<li>@name</li>
}
</ul>
</div>
}
</div>
<div class="col-sm-2">
<form asp-action="Revoke">
<input type="hidden" name="clientId" value="@grant.ClientId">
<button class="btn btn-danger">Revoke Access</button>
</form>
</div>
</div>
}
</div>
<div class="col-sm-2">
<form asp-action="Revoke">
<input type="hidden" name="clientId" value="@grant.ClientId">
<button class="btn btn-danger">Revoke Access</button>
</form>
</div>
}
</div>
}
}
</div>
</div>
</div>

7
IdentityServer/Views/Home/Index.cshtml

@ -27,6 +27,13 @@ @@ -27,6 +27,13 @@
where you can find metadata and links to all the endpoints, key material, etc.
</p>
</div>
@if (User.Identity.IsAuthenticated)
{
<div class="col-md-10 col-md-offset-1">
<a class="btn btn-default" href="@Url.Action("Index", "Grants")">View Granted Clients</a>
<a class="btn btn-default" href="@Url.Action("Logout", "Account")">Logout</a>
</div>
}
</div>
</div>
</div>

5
ServiceWorker/ArgumentOptions.cs

@ -3,7 +3,7 @@ using System; @@ -3,7 +3,7 @@ using System;
using System.Collections.Generic;
using System.Text;
namespace ServiceWorker
namespace Teknik.ServiceWorker
{
public class ArgumentOptions
{
@ -16,6 +16,9 @@ namespace ServiceWorker @@ -16,6 +16,9 @@ namespace ServiceWorker
[Option('s', "scan", Default = false, Required = false, HelpText = "Scan all uploads for viruses")]
public bool ScanUploads { get; set; }
[Option('m', "migrate", Default = false, Required = false, HelpText = "Migrate everything")]
public bool Migrate { get; set; }
// Omitting long name, default --verbose
[Option(HelpText = "Prints all messages to standard output.")]
public bool Verbose { get; set; }

9
ServiceWorker/Program.cs

@ -17,7 +17,7 @@ using Teknik.Data; @@ -17,7 +17,7 @@ using Teknik.Data;
using Teknik.Utilities;
using Teknik.Utilities.Cryptography;
namespace ServiceWorker
namespace Teknik.ServiceWorker
{
public class Program
{
@ -57,6 +57,13 @@ namespace ServiceWorker @@ -57,6 +57,13 @@ namespace ServiceWorker
{
ScanUploads(config, db);
}
// Runs the migration
if (options.Migrate)
{
// Run the overall migration calls
TeknikMigration.RunMigration(db, config);
}
}
Output(string.Format("[{0}] Finished Server Maintenance Process.", DateTime.Now));

2
ServiceWorker/ServiceWorker.csproj

@ -3,6 +3,8 @@ @@ -3,6 +3,8 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.1</TargetFramework>
<RootNamespace>Teknik.ServiceWorker</RootNamespace>
<AssemblyName>Teknik.ServiceWorker</AssemblyName>
</PropertyGroup>
<ItemGroup>

64
ServiceWorker/TeknikMigration.cs

@ -0,0 +1,64 @@ @@ -0,0 +1,64 @@
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Teknik.Areas.Paste;
using Teknik.Areas.Paste.Models;
using Teknik.Configuration;
using Teknik.Data;
using Teknik.Utilities;
namespace Teknik.ServiceWorker
{
public static class TeknikMigration
{
public static bool RunMigration(TeknikEntities db, Config config)
{
bool success = false;
MigratePastes(db, config);
return success;
}
public static void MigratePastes(TeknikEntities db, Config config)
{
if (!Directory.Exists(config.PasteConfig.PasteDirectory))
{
Directory.CreateDirectory(config.PasteConfig.PasteDirectory);
}
foreach (var paste in db.Pastes)
{
if (!string.IsNullOrEmpty(paste.Content) && string.IsNullOrEmpty(paste.FileName) && string.IsNullOrEmpty(paste.HashedPassword))
{
// Generate a unique file name that does not currently exist
string filePath = FileHelper.GenerateRandomFileName(config.PasteConfig.PasteDirectory, config.PasteConfig.FileExtension, 10);
string fileName = Path.GetFileName(filePath);
string key = PasteHelper.GenerateKey(config.PasteConfig.KeySize);
string iv = PasteHelper.GenerateIV(config.PasteConfig.BlockSize);
// Encrypt the contents to the file
PasteHelper.EncryptContents(paste.Content, filePath, null, key, iv, config.PasteConfig.KeySize, config.PasteConfig.ChunkSize);
// Generate a deletion key
paste.DeleteKey = StringHelper.RandomString(config.PasteConfig.DeleteKeyLength);
paste.Key = key;
paste.KeySize = config.PasteConfig.KeySize;
paste.IV = iv;
paste.BlockSize = config.PasteConfig.BlockSize;
paste.FileName = fileName;
paste.Content = string.Empty;
db.Entry(paste).State = EntityState.Modified;
db.SaveChanges();
}
}
}
}
}

32
Teknik/Areas/Paste/PasteHelper.cs

@ -68,22 +68,13 @@ namespace Teknik.Areas.Paste @@ -68,22 +68,13 @@ namespace Teknik.Areas.Paste
string key = GenerateKey(config.PasteConfig.KeySize);
string iv = GenerateIV(config.PasteConfig.BlockSize);
byte[] ivBytes = Encoding.Unicode.GetBytes(iv);
byte[] keyBytes = AesCounterManaged.CreateKey(key, ivBytes, config.PasteConfig.KeySize);
// Set the hashed password if one is provided and modify the key
if (!string.IsNullOrEmpty(password))
{
paste.HashedPassword = HashPassword(key, password);
keyBytes = AesCounterManaged.CreateKey(password, ivBytes, config.PasteConfig.KeySize);
}
// Encrypt Content
byte[] data = Encoding.Unicode.GetBytes(content);
using (MemoryStream ms = new MemoryStream(data))
{
AesCounterManaged.EncryptToFile(filePath, ms, config.PasteConfig.ChunkSize, keyBytes, ivBytes);
}
// Encrypt the contents to the file
EncryptContents(content, filePath, password, key, iv, config.PasteConfig.KeySize, config.PasteConfig.ChunkSize);
// Generate a deletion key
string delKey = StringHelper.RandomString(config.PasteConfig.DeleteKeyLength);
@ -127,5 +118,24 @@ namespace Teknik.Areas.Paste @@ -127,5 +118,24 @@ namespace Teknik.Areas.Paste
{
return SHA384.Hash(key, password).ToHex();
}
public static void EncryptContents(string content, string filePath, string password, string key, string iv, int keySize, int chunkSize)
{
byte[] ivBytes = Encoding.Unicode.GetBytes(iv);
byte[] keyBytes = AesCounterManaged.CreateKey(key, ivBytes, keySize);
// Set the hashed password if one is provided and modify the key
if (!string.IsNullOrEmpty(password))
{
keyBytes = AesCounterManaged.CreateKey(password, ivBytes, keySize);
}
// Encrypt Content
byte[] data = Encoding.Unicode.GetBytes(content);
using (MemoryStream ms = new MemoryStream(data))
{
AesCounterManaged.EncryptToFile(filePath, ms, chunkSize, keyBytes, ivBytes);
}
}
}
}

79
Teknik/Areas/User/Controllers/UserController.cs

@ -35,6 +35,7 @@ using IdentityModel; @@ -35,6 +35,7 @@ using IdentityModel;
using System.Security.Cryptography;
using System.IdentityModel.Tokens.Jwt;
using Microsoft.AspNetCore.Http;
using IdentityServer4.Models;
namespace Teknik.Areas.Users.Controllers
{
@ -406,22 +407,23 @@ namespace Teknik.Areas.Users.Controllers @@ -406,22 +407,23 @@ namespace Teknik.Areas.Users.Controllers
return new StatusCodeResult(StatusCodes.Status403Forbidden);
}
public IActionResult AccessTokenSettings()
public async Task<IActionResult> DeveloperSettings()
{
string username = User.Identity.Name;
User user = UserHelper.GetUser(_dbContext, username);
if (user != null)
{
ViewBag.Title = "Access Token Settings - " + _config.Title;
ViewBag.Description = "Your " + _config.Title + " Access Token Settings";
ViewBag.Title = "Developer Settings - " + _config.Title;
ViewBag.Description = "Your " + _config.Title + " Developer Settings";
APIClientSettingsViewModel model = new APIClientSettingsViewModel();
model.Page = "AccessTokens";
DeveloperSettingsViewModel model = new DeveloperSettingsViewModel();
model.Page = "Developer";
model.UserID = user.UserId;
model.Username = user.Username;
model.AuthTokens = new List<AuthTokenViewModel>();
model.Clients = new List<ClientViewModel>();
//foreach (AuthToken token in user.AuthTokens)
//{
// AuthTokenViewModel tokenModel = new AuthTokenViewModel();
@ -432,7 +434,20 @@ namespace Teknik.Areas.Users.Controllers @@ -432,7 +434,20 @@ namespace Teknik.Areas.Users.Controllers
// model.AuthTokens.Add(tokenModel);
//}
return View("/Areas/User/Views/User/Settings/AccessTokenSettings.cshtml", model);
Client[] clients = await IdentityHelper.GetClients(_config, username);
foreach (Client client in clients)
{
model.Clients.Add(new ClientViewModel()
{
Id = client.ClientId,
Name = client.ClientName,
RedirectURI = string.Join(',', client.RedirectUris),
PostLogoutRedirectURI = string.Join(',', client.PostLogoutRedirectUris),
AllowedScopes = client.AllowedScopes
});
}
return View("/Areas/User/Views/User/Settings/DeveloperSettings.cshtml", model);
}
return new StatusCodeResult(StatusCodes.Status403Forbidden);
@ -1211,6 +1226,58 @@ namespace Teknik.Areas.Users.Controllers @@ -1211,6 +1226,58 @@ namespace Teknik.Areas.Users.Controllers
}
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> CreateClient(string name, string redirectUri, string logoutUri, [FromServices] ICompositeViewEngine viewEngine)
{
try
{
// Validate the code with the identity server
var result = await IdentityHelper.CreateClient(_config, User.Identity.Name, name, redirectUri, logoutUri, "openid", "teknik-api.read", "teknik-api.write");
if (result.Success)
{
var client = (JObject)result.Data;
ClientViewModel model = new ClientViewModel();
model.Id = client["id"].ToString();
model.Name = name;
model.RedirectURI = redirectUri;
model.PostLogoutRedirectURI = logoutUri;
string renderedView = await RenderPartialViewToString(viewEngine, "~/Areas/User/Views/User/Settings/ClientView.cshtml", model);
return Json(new { result = true, clientId = client["id"], secret = client["secret"], html = renderedView });
}
return Json(new { error = result.Message });
}
catch (Exception ex)
{
return Json(new { error = ex.GetFullMessage(true) });
}
}
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> DeleteClient(string clientId)
{
try
{
// Validate the code with the identity server
var result = await IdentityHelper.DeleteClient(_config, clientId);
if (result.Success)
{
return Json(new { result = true });
}
return Json(new { error = result.Message });
}
catch (Exception ex)
{
return Json(new { error = ex.GetFullMessage(true) });
}
}
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult CreateInviteCodeLink(int inviteCodeId)

53
Teknik/Areas/User/Utility/IdentityHelper.cs

@ -1,4 +1,5 @@ @@ -1,4 +1,5 @@
using IdentityModel.Client;
using IdentityServer4.Models;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
@ -324,5 +325,57 @@ namespace Teknik.Areas.Users.Utility @@ -324,5 +325,57 @@ namespace Teknik.Areas.Users.Utility
}
throw new Exception(response.Message);
}
public static async Task<Client> GetClient(Config config, string username, string clientId)
{
var manageUrl = CreateUrl(config, $"Manage/GetClient?username={username}&clientId={clientId}");
var result = await Get(config, manageUrl);
if (result.Success)
{
return (Client)result.Data;
}
throw new Exception(result.Message);
}
public static async Task<Client[]> GetClients(Config config, string username)
{
var manageUrl = CreateUrl(config, $"Manage/GetClients?username={username}");
var result = await Get(config, manageUrl);
if (result.Success)
{
return ((JArray)result.Data).ToObject<Client[]>();
}
throw new Exception(result.Message);
}
public static async Task<IdentityResult> CreateClient(Config config, string username, string name, string redirectURI, string postLogoutRedirectURI, params string[] allowedScopes)
{
var manageUrl = CreateUrl(config, $"Manage/CreateClient");
var response = await Post(config, manageUrl,
new
{
username = username,
name = name,
redirectURI = redirectURI,
postLogoutRedirectURI = postLogoutRedirectURI,
allowedScopes = allowedScopes
});
return response;
}
public static async Task<IdentityResult> DeleteClient(Config config, string clientId)
{
var manageUrl = CreateUrl(config, $"Manage/DeleteClient");
var response = await Post(config, manageUrl,
new
{
clientId = clientId
});
return response;
}
}
}

17
Teknik/Areas/User/ViewModels/ClientViewModel.cs

@ -0,0 +1,17 @@ @@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Teknik.ViewModels;
namespace Teknik.Areas.Users.ViewModels
{
public class ClientViewModel : ViewModelBase
{
public string Id { get; set; }
public string Name { get; set; }
public string RedirectURI { get; set; }
public string PostLogoutRedirectURI { get; set; }
public ICollection<string> AllowedScopes { get; set; }
}
}

6
Teknik/Areas/User/ViewModels/APIClientSettingsViewModel.cs → Teknik/Areas/User/ViewModels/DeveloperSettingsViewModel.cs

@ -5,14 +5,16 @@ using System.Threading.Tasks; @@ -5,14 +5,16 @@ using System.Threading.Tasks;
namespace Teknik.Areas.Users.ViewModels
{
public class APIClientSettingsViewModel : SettingsViewModel
public class DeveloperSettingsViewModel : SettingsViewModel
{
public List<AuthTokenViewModel> AuthTokens { get; set; }
public List<ClientViewModel> Clients { get; set; }
public APIClientSettingsViewModel()
public DeveloperSettingsViewModel()
{
AuthTokens = new List<AuthTokenViewModel>();
Clients = new List<ClientViewModel>();
}
}
}

25
Teknik/Areas/User/Views/User/Settings/ClientView.cshtml

@ -0,0 +1,25 @@ @@ -0,0 +1,25 @@
@model Teknik.Areas.Users.ViewModels.ClientViewModel
<li class="list-group-item" id="client_@Model.Id">
<div class="btn-group btn-group-sm pull-right" role="group" aria-label="...">
<button type="button" class="btn btn-danger text-danger deleteClient" id="deleteClient_@Model.Id" data-clientId="@Model.Id">Delete</button>
</div>
<h3 class="list-group-item-heading" id="clientName_@Model.Name">@Model.Name</h3>
<div class="list-group-item-text">
<div class="row">
<div class="col-sm-4">
<strong>Client Id</strong>: @Model.Id
</div>
<div class="col-sm-8">
<div class="row">
<div class="col-sm-4"><strong>Redirect Url</strong></div>
<div class="col-sm-8">@Model.RedirectURI</div>
</div>
<div class="row">
<div class="col-sm-4"><strong>Logout Url</strong></div>
<div class="col-sm-8">@Model.PostLogoutRedirectURI</div>
</div>
</div>
</div>
</div>
</li>

41
Teknik/Areas/User/Views/User/Settings/APIClientSettings.cshtml → Teknik/Areas/User/Views/User/Settings/DeveloperSettings.cshtml

@ -1,4 +1,4 @@ @@ -1,4 +1,4 @@
@model Teknik.Areas.Users.ViewModels.APIClientSettingsViewModel
@model Teknik.Areas.Users.ViewModels.DeveloperSettingsViewModel
@using Teknik.Areas.Users.ViewModels
@ -7,13 +7,17 @@ @@ -7,13 +7,17 @@
}
<script>
var clearTrustedDevicesURL = '@Url.SubRouteUrl("user", "User.Action", new { action = "ClearTrustedDevices" })';
var generateTokenURL = '@Url.SubRouteUrl("user", "User.Action", new { action = "GenerateToken" })';
var revokeAllTokensURL = '@Url.SubRouteUrl("user", "User.Action", new { action = "RevokeAllTokens" })';
var editTokenNameURL = '@Url.SubRouteUrl("user", "User.Action", new { action = "EditTokenName" })';
var deleteTokenURL = '@Url.SubRouteUrl("user", "User.Action", new { action = "DeleteToken" })';
var createClientURL = '@Url.SubRouteUrl("user", "User.Action", new { action = "CreateClient" })';
var deleteClientURL = '@Url.SubRouteUrl("user", "User.Action", new { action = "DeleteClient" })';
</script>
<!--
<div class="row">
<div class="col-sm-12">
<h2>API Clients</h2>
@ -23,7 +27,7 @@ @@ -23,7 +27,7 @@
<div class="row">
<div class="col-sm-12">
<br />
<label for="authTokens"><h4>API Clients</h4></label><span class="pull-right"><button type="button" class="btn btn-default" id="create_client">Create Client</button> <button type="button" class="btn btn-danger" id="revoke_all_clients">Revoke All</button></span>
<label for="authTokens"><h4>API Clients</h4></label><span class="pull-right"><button type="button" class="btn btn-default" id="create_authToken">Create Auth Token</button> <button type="button" class="btn btn-danger" id="revoke_all_authTokens">Revoke All</button></span>
<div id="authTokens" style="overflow-y: auto; max-height: 400px;">
<ul class="list-group" id="apiClientList">
@if (Model.AuthTokens.Any())
@ -41,5 +45,34 @@ @@ -41,5 +45,34 @@
</div>
</div>
</div>
-->
<div class="row">
<div class="col-sm-12">
<h2>Clients</h2>
<hr />
</div>
</div>
<div class="row">
<div class="col-sm-12">
<br />
<label for="clients"><h4>Clients</h4></label><span class="pull-right"><button type="button" class="btn btn-default" id="create_client">Create Client</button></span>
<div id="clients" style="overflow-y: auto; max-height: 400px;">
<ul class="list-group" id="clientList">
@if (Model.Clients.Any())
{
foreach (ClientViewModel client in Model.Clients)
{
@await Html.PartialAsync("Settings/ClientView", client)
}
}
else
{
<li class="list-group-item text-center" id="noClients">No Clients</li>
}
</ul>
</div>
</div>
</div>
<bundle src="js/user.settings.accessTokenSettings.min.js" append-version="true"></bundle>
<bundle src="js/user.settings.developer.min.js" append-version="true"></bundle>

8
Teknik/Areas/User/Views/User/Settings/Settings.cshtml

@ -14,14 +14,18 @@ @@ -14,14 +14,18 @@
<!--left col-->
<div class="panel panel-default">
<ul class="list-group">
<div class="panel-heading text-center"><strong>Settings</strong></div>
<div class="panel-heading text-center"><strong>Personal Settings</strong></div>
<a href="@Url.SubRouteUrl("user", "User.ProfileSettings")" class="list-group-item @(Model.Page == "Profile" ? "active" : string.Empty)">Profile</a>
<a href="@Url.SubRouteUrl("user", "User.AccountSettings")" class="list-group-item @(Model.Page == "Account" ? "active" : string.Empty)">Account</a>
<a href="@Url.SubRouteUrl("user", "User.SecuritySettings")" class="list-group-item @(Model.Page == "Security" ? "active" : string.Empty)">Security</a>
<a href="@Url.SubRouteUrl("user", "User.InviteSettings")" class="list-group-item @(Model.Page == "Invite" ? "active" : string.Empty)">Invite Codes</a>
<a href="@Url.SubRouteUrl("user", "User.BlogSettings")" class="list-group-item @(Model.Page == "Blog" ? "active" : string.Empty)">Blogging</a>
<a href="@Url.SubRouteUrl("user", "User.UploadSettings")" class="list-group-item @(Model.Page == "Upload" ? "active" : string.Empty)">Uploads</a>
<a href="@Url.SubRouteUrl("user", "User.APIClientSettings")" class="list-group-item @(Model.Page == "APIClients" ? "active" : string.Empty)">API Clients</a>
</ul>
</div>
<div class="panel panel-default">
<ul class="list-group">
<a href="@Url.SubRouteUrl("user", "User.DeveloperSettings")" class="list-group-item @(Model.Page == "Developer" ? "active" : string.Empty)">Developer Settings</a>
</ul>
</div>
</div><!--/col-2-->

1005
Teknik/Data/Migrations/20180624191511_UserLoginInfo.Designer.cs generated

File diff suppressed because it is too large Load Diff

40
Teknik/Data/Migrations/20180624191511_UserLoginInfo.cs

@ -1,40 +0,0 @@ @@ -1,40 +0,0 @@
using System;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
namespace Teknik.Data.Migrations
{
public partial class UserLoginInfo : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "UserLogins",
columns: table => new
{
LoginInfoId = table.Column<int>(nullable: false)
.Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
LoginProvider = table.Column<string>(nullable: true),
ProviderDisplayName = table.Column<string>(nullable: true),
ProviderKey = table.Column<string>(nullable: true),
UserId = table.Column<int>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_UserLogins", x => x.LoginInfoId);
table.ForeignKey(
name: "FK_UserLogins_Users_UserId",
column: x => x.UserId,
principalTable: "Users",
principalColumn: "UserId",
onDelete: ReferentialAction.Cascade);
});
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "UserLogins");
}
}
}

789
Teknik/Data/Migrations/20181018071735_IdentityAuth.Designer.cs generated

@ -1,789 +0,0 @@ @@ -1,789 +0,0 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Teknik.Data;
namespace Teknik.Data.Migrations
{
[DbContext(typeof(TeknikEntities))]
[Migration("20181018071735_IdentityAuth")]
partial class IdentityAuth
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "2.2.0-preview2-35157")
.HasAnnotation("Relational:MaxIdentifierLength", 128)
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
modelBuilder.Entity("Teknik.Areas.Blog.Models.Blog", b =>
{
b.Property<int>("BlogId")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<int>("UserId");
b.HasKey("BlogId");
b.HasIndex("UserId");
b.ToTable("Blogs");
});
modelBuilder.Entity("Teknik.Areas.Blog.Models.BlogPost", b =>
{
b.Property<int>("BlogPostId")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<string>("Article");
b.Property<int>("BlogId");
b.Property<DateTime>("DateEdited");
b.Property<DateTime>("DatePosted");
b.Property<DateTime>("DatePublished");
b.Property<bool>("Published");
b.Property<bool>("System");
b.Property<string>("Title");
b.HasKey("BlogPostId");
b.HasIndex("BlogId");
b.ToTable("BlogPosts");
});
modelBuilder.Entity("Teknik.Areas.Blog.Models.BlogPostComment", b =>
{
b.Property<int>("BlogPostCommentId")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<string>("Article");
b.Property<int>("BlogPostId");
b.Property<DateTime>("DateEdited");
b.Property<DateTime>("DatePosted");
b.Property<int?>("UserId");
b.HasKey("BlogPostCommentId");
b.HasIndex("BlogPostId");
b.HasIndex("UserId");
b.ToTable("BlogPostComments");
});
modelBuilder.Entity("Teknik.Areas.Blog.Models.BlogPostTag", b =>
{
b.Property<int>("BlogPostTagId")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<int>("BlogPostId");
b.Property<string>("Description");
b.Property<string>("Name");
b.HasKey("BlogPostTagId");
b.HasIndex("BlogPostId");
b.ToTable("BlogPostTags");
});
modelBuilder.Entity("Teknik.Areas.Contact.Models.Contact", b =>
{
b.Property<int>("ContactId")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<DateTime>("DateAdded");
b.Property<string>("Email");
b.Property<string>("Message");
b.Property<string>("Name");
b.Property<string>("Subject");
b.HasKey("ContactId");
b.ToTable("Contact");
});
modelBuilder.Entity("Teknik.Areas.Paste.Models.Paste", b =>
{
b.Property<int>("PasteId")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<int>("BlockSize");
b.Property<string>("Content");
b.Property<DateTime>("DatePosted");
b.Property<DateTime?>("ExpireDate");
b.Property<string>("HashedPassword")
.HasAnnotation("CaseSensitive", true);
b.Property<bool>("Hide");
b.Property<string>("IV")
.HasAnnotation("CaseSensitive", true);
b.Property<string>("Key")
.HasAnnotation("CaseSensitive", true);
b.Property<int>("KeySize");
b.Property<int>("MaxViews");
b.Property<string>("Syntax");
b.Property<string>("Title");
b.Property<string>("Url")
.HasAnnotation("CaseSensitive", true);
b.Property<int?>("UserId");
b.Property<int>("Views");
b.HasKey("PasteId");
b.HasIndex("UserId");
b.ToTable("Pastes");
});
modelBuilder.Entity("Teknik.Areas.Podcast.Models.Podcast", b =>
{
b.Property<int>("PodcastId")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<DateTime>("DateEdited");
b.Property<DateTime>("DatePosted");
b.Property<DateTime>("DatePublished");
b.Property<string>("Description");
b.Property<int>("Episode");
b.Property<bool>("Published");
b.Property<string>("Title");
b.HasKey("PodcastId");
b.ToTable("Podcasts");
});
modelBuilder.Entity("Teknik.Areas.Podcast.Models.PodcastComment", b =>
{
b.Property<int>("PodcastCommentId")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<string>("Article");
b.Property<DateTime>("DateEdited");
b.Property<DateTime>("DatePosted");
b.Property<int>("PodcastId");
b.Property<int>("UserId");
b.HasKey("PodcastCommentId");
b.HasIndex("PodcastId");