mirror of
https://git.teknik.io/Teknikode/Teknik.git
synced 2023-08-02 14:16:22 +02:00
- Added Basic Auth handling inside the auth attributes
- Removed global auth attribute and moved it to each controller - Added new auth information to the API help doc - Finished UI for managing auth tokens - Added breadcrumbs to the help pages
This commit is contained in:
parent
41e4dca194
commit
cbe5078b9d
@ -10,7 +10,7 @@ namespace Teknik
|
||||
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
|
||||
{
|
||||
//filters.Add(new HandleErrorAttribute());
|
||||
filters.Add(new TeknikAuthorizeAttribute());
|
||||
//filters.Add(new TeknikAuthorizeAttribute());
|
||||
filters.Add(new RequireHttpsAttribute());
|
||||
}
|
||||
}
|
||||
|
@ -9,9 +9,11 @@ using Teknik.Areas.Upload;
|
||||
using Teknik.Controllers;
|
||||
using Teknik.Utilities;
|
||||
using Teknik.Models;
|
||||
using Teknik.Attributes;
|
||||
|
||||
namespace Teknik.Areas.API.Controllers
|
||||
{
|
||||
[TeknikAuthorize]
|
||||
public class APIController : DefaultController
|
||||
{
|
||||
private TeknikEntities db = new TeknikEntities();
|
||||
|
@ -17,9 +17,11 @@ using Teknik.Filters;
|
||||
using Teknik.Areas.API.Models;
|
||||
using Teknik.Areas.Users.Models;
|
||||
using Teknik.Areas.Users.Utility;
|
||||
using Teknik.Attributes;
|
||||
|
||||
namespace Teknik.Areas.API.Controllers
|
||||
{
|
||||
[TeknikAuthorize(AuthType.Basic)]
|
||||
public class APIv1Controller : DefaultController
|
||||
{
|
||||
private TeknikEntities db = new TeknikEntities();
|
||||
@ -27,7 +29,7 @@ namespace Teknik.Areas.API.Controllers
|
||||
[AllowAnonymous]
|
||||
public ActionResult Index()
|
||||
{
|
||||
return Redirect(Url.SubRouteUrl("help", "Help.Topic", new { topic = "API" }));
|
||||
return Redirect(Url.SubRouteUrl("help", "Help.API"));
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
@ -120,9 +122,9 @@ namespace Teknik.Areas.API.Controllers
|
||||
if (upload != null)
|
||||
{
|
||||
// Associate this with the user if they provided an auth key
|
||||
if (!string.IsNullOrEmpty(model.authToken))
|
||||
if (User.Identity.IsAuthenticated)
|
||||
{
|
||||
User foundUser = UserHelper.GetUserFromToken(db, model.authToken);
|
||||
User foundUser = UserHelper.GetUser(db, User.Identity.Name);
|
||||
if (foundUser != null)
|
||||
{
|
||||
upload.UserId = foundUser.UserId;
|
||||
@ -183,10 +185,10 @@ namespace Teknik.Areas.API.Controllers
|
||||
{
|
||||
Paste.Models.Paste paste = PasteHelper.CreatePaste(model.code, model.title, model.syntax, model.expireUnit, model.expireLength, model.password, model.hide);
|
||||
|
||||
// Associate this with the user if they provided an auth key
|
||||
if (!string.IsNullOrEmpty(model.authToken))
|
||||
// Associate this with the user if they are logged in
|
||||
if (User.Identity.IsAuthenticated)
|
||||
{
|
||||
User foundUser = UserHelper.GetUserFromToken(db, model.authToken);
|
||||
User foundUser = UserHelper.GetUser(db, User.Identity.Name);
|
||||
if (foundUser != null)
|
||||
{
|
||||
paste.UserId = foundUser.UserId;
|
||||
@ -228,10 +230,10 @@ namespace Teknik.Areas.API.Controllers
|
||||
{
|
||||
ShortenedUrl newUrl = Shortener.Shortener.ShortenUrl(model.url, Config.ShortenerConfig.UrlLength);
|
||||
|
||||
// Associate this with the user if they provided an auth key
|
||||
if (!string.IsNullOrEmpty(model.authToken))
|
||||
// Associate this with the user if they are logged in
|
||||
if (User.Identity.IsAuthenticated)
|
||||
{
|
||||
User foundUser = UserHelper.GetUserFromToken(db, model.authToken);
|
||||
User foundUser = UserHelper.GetUser(db, User.Identity.Name);
|
||||
if (foundUser != null)
|
||||
{
|
||||
newUrl.UserId = foundUser.UserId;
|
||||
|
@ -9,15 +9,9 @@ namespace Teknik.Areas.API.Models
|
||||
{
|
||||
public bool doNotTrack { get; set; }
|
||||
|
||||
public string username { get; set; }
|
||||
|
||||
public string authToken { get; set; }
|
||||
|
||||
public APIv1BaseModel()
|
||||
{
|
||||
doNotTrack = false;
|
||||
username = string.Empty;
|
||||
authToken = string.Empty;
|
||||
}
|
||||
}
|
||||
}
|
@ -4,11 +4,13 @@ using System.Linq;
|
||||
using System.Web;
|
||||
using System.Web.Mvc;
|
||||
using Teknik.Areas.About.ViewModels;
|
||||
using Teknik.Attributes;
|
||||
using Teknik.Controllers;
|
||||
using Teknik.Filters;
|
||||
|
||||
namespace Teknik.Areas.About.Controllers
|
||||
{
|
||||
[TeknikAuthorize]
|
||||
public class AboutController : DefaultController
|
||||
{
|
||||
[AllowAnonymous]
|
||||
|
@ -14,9 +14,11 @@ using Teknik.Controllers;
|
||||
using Teknik.Filters;
|
||||
using Teknik.Utilities;
|
||||
using Teknik.Models;
|
||||
using Teknik.Attributes;
|
||||
|
||||
namespace Teknik.Areas.Blog.Controllers
|
||||
{
|
||||
[TeknikAuthorize]
|
||||
public class BlogController : DefaultController
|
||||
{
|
||||
private TeknikEntities db = new TeknikEntities();
|
||||
|
@ -10,9 +10,11 @@ using Teknik.Areas.Contact.Models;
|
||||
using Teknik.Models;
|
||||
using System.Text;
|
||||
using Teknik.Filters;
|
||||
using Teknik.Attributes;
|
||||
|
||||
namespace Teknik.Areas.Contact.Controllers
|
||||
{
|
||||
[TeknikAuthorize]
|
||||
public class ContactController : DefaultController
|
||||
{
|
||||
private TeknikEntities db = new TeknikEntities();
|
||||
|
@ -3,12 +3,14 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Web;
|
||||
using System.Web.Mvc;
|
||||
using Teknik.Attributes;
|
||||
using Teknik.Controllers;
|
||||
using Teknik.Filters;
|
||||
using Teknik.Utilities;
|
||||
|
||||
namespace Teknik.Areas.Dev.Controllers
|
||||
{
|
||||
[TeknikAuthorize]
|
||||
public class DevController : DefaultController
|
||||
{
|
||||
[TrackPageView]
|
||||
|
@ -10,9 +10,11 @@ using Teknik.Controllers;
|
||||
using Teknik.Filters;
|
||||
using Teknik.Utilities;
|
||||
using Teknik.Logging;
|
||||
using Teknik.Attributes;
|
||||
|
||||
namespace Teknik.Areas.Error.Controllers
|
||||
{
|
||||
[TeknikAuthorize]
|
||||
public class ErrorController : DefaultController
|
||||
{
|
||||
[TrackPageView]
|
||||
@ -69,7 +71,33 @@ namespace Teknik.Areas.Error.Controllers
|
||||
|
||||
return View("~/Areas/Error/Views/Error/General.cshtml", model);
|
||||
}
|
||||
|
||||
|
||||
[AllowAnonymous]
|
||||
public ActionResult Http401(Exception exception)
|
||||
{
|
||||
ViewBag.Title = "401 - " + Config.Title;
|
||||
ViewBag.Description = "Unauthorized";
|
||||
|
||||
if (Response != null)
|
||||
{
|
||||
Response.StatusCode = 401;
|
||||
Response.TrySkipIisCustomErrors = true;
|
||||
}
|
||||
|
||||
string errorMessage = "Unauthorized";
|
||||
if (Request != null && Request.Url != null)
|
||||
{
|
||||
errorMessage += " for page: " + Request.Url.AbsoluteUri;
|
||||
}
|
||||
|
||||
Logger.WriteEntry(LogLevel.Error, errorMessage, exception);
|
||||
|
||||
ErrorViewModel model = new ErrorViewModel();
|
||||
model.Exception = exception;
|
||||
|
||||
return View("~/Areas/Error/Views/Error/Http401.cshtml", model);
|
||||
}
|
||||
|
||||
[AllowAnonymous]
|
||||
public ActionResult Http403(Exception exception)
|
||||
{
|
||||
|
28
Teknik/Areas/Error/Views/Error/Http401.cshtml
Normal file
28
Teknik/Areas/Error/Views/Error/Http401.cshtml
Normal file
@ -0,0 +1,28 @@
|
||||
@model Teknik.Areas.Error.ViewModels.ErrorViewModel
|
||||
|
||||
@using Teknik.Utilities
|
||||
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="error-template text-center">
|
||||
<h2>401 Unauthorized</h2>
|
||||
<div class="error-details">
|
||||
The request has not been applied because it lacks valid authentication credentials for the target resource.
|
||||
</div>
|
||||
<br />
|
||||
<div class="error-actions">
|
||||
<a href="@Url.SubRouteUrl("www", "Home.Index")" class="btn btn-primary btn-lg">
|
||||
<span class="glyphicon glyphicon-home"></span>
|
||||
Take Me Home
|
||||
</a>
|
||||
<a href="@Url.SubRouteUrl("contact", "Contact.Index")" class="btn btn-default btn-lg">
|
||||
<span class="glyphicon glyphicon-envelope"></span>
|
||||
Contact Support
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -4,11 +4,13 @@ using System.Linq;
|
||||
using System.Web;
|
||||
using System.Web.Mvc;
|
||||
using Teknik.Areas.Help.ViewModels;
|
||||
using Teknik.Attributes;
|
||||
using Teknik.Controllers;
|
||||
using Teknik.Filters;
|
||||
|
||||
namespace Teknik.Areas.Help.Controllers
|
||||
{
|
||||
[TeknikAuthorize]
|
||||
public class HelpController : DefaultController
|
||||
{
|
||||
// GET: Help/Help
|
||||
|
@ -5,6 +5,10 @@
|
||||
@Styles.Render("~/Content/help");
|
||||
|
||||
<div class="container">
|
||||
<ol class="breadcrumb">
|
||||
<li><a href="@Url.SubRouteUrl("help", "Help.Index")">Help Index</a></li>
|
||||
<li class="active"><a href="#">API</a></li>
|
||||
</ol>
|
||||
<div class="row api">
|
||||
<div class="col-sm-4">
|
||||
<h3><b>API Versions</b></h3>
|
||||
@ -13,7 +17,7 @@
|
||||
<ul class="list-unstyled">
|
||||
<li><a href="@Url.SubRouteUrl("help", "Help.API", new { version = "v1", service = "Upload" })">Upload Service</a></li>
|
||||
<li><a href="@Url.SubRouteUrl("help", "Help.API", new { version = "v1", service = "Paste" })">Paste Service</a></li>
|
||||
<li><a href="@Url.SubRouteUrl("help", "Help.API", new { version = "v1", service = "Shorten" })">Url Shortening Service</a></li>
|
||||
<li><a href="@Url.SubRouteUrl("help", "Help.API", new { version = "v1", service = "Shorten" })">Url Shortening</a></li>
|
||||
</ul>
|
||||
</p>
|
||||
</div>
|
||||
@ -26,6 +30,22 @@
|
||||
<br />
|
||||
The general API calls can be summarized as follows: <code>@Url.SubRouteUrl("api", "Api.Index")v@(Model.Config.ApiConfig.Version)/<b>Service</b>/<b>Action</b></code>
|
||||
</p>
|
||||
<h3>Basic Authentication</h3>
|
||||
<p>
|
||||
For some services, you may be required to authenticate to access them, or to associate generated content with your account. In all cases, the same authentication method is used.
|
||||
<br />
|
||||
<br />
|
||||
<h4>Generating Authentication Tokens</h4>
|
||||
To generate a new authentication token, navigate to your <a href="@Url.SubRouteUrl("user", "User.Settings")">user profile</a> and click the <b>Security</b> tab. There, you can manage your existing tokens and generate new ones.
|
||||
<br />
|
||||
<br />
|
||||
<h4>Using the Authentication Tokens</h4>
|
||||
To use Basic Authentication with your token, simply send the username and token.
|
||||
<br />
|
||||
<br />
|
||||
For example, if you're accessing the API via cURL, the following command would associate the create paste with your account. Just replace <code><username></code> with your Teknik username and <code><token></code> with your token.
|
||||
<pre><code>$ curl -u <username>:<token> --data code="Test" @Url.SubRouteUrl("api", "API.v1.Paste")</code></pre>
|
||||
</p>
|
||||
<h3>Responses</h3>
|
||||
<p>
|
||||
All responses are returned as json. The returned json can contain any of the following sections.
|
||||
|
@ -6,12 +6,45 @@
|
||||
@Styles.Render("~/Content/help");
|
||||
|
||||
<div class="container">
|
||||
<ol class="breadcrumb">
|
||||
<li><a href="@Url.SubRouteUrl("help", "Help.Index")">Help Index</a></li>
|
||||
<li><a href="@Url.SubRouteUrl("help", "Help.API", new { version = string.Empty, service = string.Empty })">API</a></li>
|
||||
<li>v1</li>
|
||||
<li class="active"><a href="#">Paste Service</a></li>
|
||||
</ol>
|
||||
<div class="row api">
|
||||
<h2><b>Paste Service</b></h2>
|
||||
<hr>
|
||||
<p>This is a description of the API commands available for the Paste service.</p>
|
||||
<h3>Submit a Paste</h3>
|
||||
<pre><code>POST @Url.SubRouteUrl("api", "API.v1.Paste")</code></pre>
|
||||
<h4>Headers</h4>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Value</th>
|
||||
<th>Default Value</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<code>Authorize</code>
|
||||
</td>
|
||||
<td>
|
||||
<code>Basic <i>Encoding.Base64</i>(<username>:<token>)</code>
|
||||
</td>
|
||||
<td>
|
||||
<var>NULL</var>
|
||||
</td>
|
||||
<td>
|
||||
Basic Authentication to associate the paste with your account.
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h4>Parameters</h4>
|
||||
<table>
|
||||
<thead>
|
||||
|
@ -5,12 +5,45 @@
|
||||
@Styles.Render("~/Content/help");
|
||||
|
||||
<div class="container">
|
||||
<ol class="breadcrumb">
|
||||
<li><a href="@Url.SubRouteUrl("help", "Help.Index")">Help Index</a></li>
|
||||
<li><a href="@Url.SubRouteUrl("help", "Help.API", new { version = string.Empty, service = string.Empty })">API</a></li>
|
||||
<li>v1</li>
|
||||
<li class="active"><a href="#">Url Shortening</a></li>
|
||||
</ol>
|
||||
<div class="row api">
|
||||
<h2><b>Url Shortening Service</b></h2>
|
||||
<hr>
|
||||
<p>This is a description of the API commands available for the Url Shortening service.</p>
|
||||
<h3>Shorten a Url</h3>
|
||||
<pre><code>POST @Url.SubRouteUrl("api", "API.v1.Shortener")</code></pre>
|
||||
<h4>Headers</h4>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Value</th>
|
||||
<th>Default Value</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<code>Authorize</code>
|
||||
</td>
|
||||
<td>
|
||||
<code>Basic <i>Encoding.Base64</i>(<username>:<token>)</code>
|
||||
</td>
|
||||
<td>
|
||||
<var>NULL</var>
|
||||
</td>
|
||||
<td>
|
||||
Basic Authentication to associate the shortened url with your account.
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h4>Parameters</h4>
|
||||
<table>
|
||||
<thead>
|
||||
|
@ -5,12 +5,45 @@
|
||||
@Styles.Render("~/Content/help");
|
||||
|
||||
<div class="container">
|
||||
<ol class="breadcrumb">
|
||||
<li><a href="@Url.SubRouteUrl("help", "Help.Index")">Help Index</a></li>
|
||||
<li><a href="@Url.SubRouteUrl("help", "Help.API", new { version = string.Empty, service = string.Empty })">API</a></li>
|
||||
<li>v1</li>
|
||||
<li class="active"><a href="#">Upload Service</a></li>
|
||||
</ol>
|
||||
<div class="row api">
|
||||
<h2><b>Upload Service</b></h2>
|
||||
<hr>
|
||||
<p>This is a description of the API commands available for the Upload service.</p>
|
||||
<h3>Upload a File</h3>
|
||||
<pre><code>POST @Url.SubRouteUrl("api", "API.v1.Upload")</code></pre>
|
||||
<h4>Headers</h4>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Value</th>
|
||||
<th>Default Value</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<code>Authorize</code>
|
||||
</td>
|
||||
<td>
|
||||
<code>Basic <i>Encoding.Base64</i>(<username>:<token>)</code>
|
||||
</td>
|
||||
<td>
|
||||
<var>NULL</var>
|
||||
</td>
|
||||
<td>
|
||||
Basic Authentication to associate the upload with your account.
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h4>Parameters</h4>
|
||||
<table>
|
||||
<thead>
|
||||
|
@ -1,6 +1,12 @@
|
||||
@model Teknik.Areas.Help.ViewModels.HelpViewModel
|
||||
|
||||
@using Teknik.Utilities
|
||||
|
||||
<div class="container">
|
||||
<ol class="breadcrumb">
|
||||
<li><a href="@Url.SubRouteUrl("help", "Help.Index")">Help Index</a></li>
|
||||
<li class="active"><a href="#">Blogging</a></li>
|
||||
</ol>
|
||||
<div class="row">
|
||||
<h2><b>Blogging</b></h2>
|
||||
<hr>
|
||||
|
@ -1,6 +1,12 @@
|
||||
@model Teknik.Areas.Help.ViewModels.HelpViewModel
|
||||
|
||||
@using Teknik.Utilities
|
||||
|
||||
<div class="container">
|
||||
<ol class="breadcrumb">
|
||||
<li><a href="@Url.SubRouteUrl("help", "Help.Index")">Help Index</a></li>
|
||||
<li class="active"><a href="#">Git Repositories</a></li>
|
||||
</ol>
|
||||
<div class="row">
|
||||
<h2><b>Git</b></h2>
|
||||
<hr>
|
||||
|
@ -1,6 +1,12 @@
|
||||
@model Teknik.Areas.Help.ViewModels.HelpViewModel
|
||||
|
||||
@using Teknik.Utilities
|
||||
|
||||
<div class="container">
|
||||
<ol class="breadcrumb">
|
||||
<li><a href="@Url.SubRouteUrl("help", "Help.Index")">Help Index</a></li>
|
||||
<li class="active"><a href="#">IRC Network</a></li>
|
||||
</ol>
|
||||
<div class="row">
|
||||
<h2><b>IRC</b></h2>
|
||||
<hr>
|
||||
|
@ -3,6 +3,10 @@
|
||||
@using Teknik.Utilities
|
||||
|
||||
<div class="container">
|
||||
<ol class="breadcrumb">
|
||||
<li><a href="@Url.SubRouteUrl("help", "Help.Index")">Help Index</a></li>
|
||||
<li class="active"><a href="#">Mail Server</a></li>
|
||||
</ol>
|
||||
<div class="row">
|
||||
<h2><b>Mail</b></h2>
|
||||
<hr>
|
||||
|
@ -1,6 +1,12 @@
|
||||
@model Teknik.Areas.Help.ViewModels.HelpViewModel
|
||||
|
||||
@using Teknik.Utilities
|
||||
|
||||
<div class="container">
|
||||
<ol class="breadcrumb">
|
||||
<li><a href="@Url.SubRouteUrl("help", "Help.Index")">Help Index</a></li>
|
||||
<li class="active"><a href="#">Markdown</a></li>
|
||||
</ol>
|
||||
<div class="row">
|
||||
<h2>Markdown Formatting</h2>
|
||||
<p><a href="http://daringfireball.net/projects/markdown/" target="_blank">Markdown</a> turns plain text formatting into fancy HTML formatting.</p>
|
||||
|
@ -1,6 +1,12 @@
|
||||
@model Teknik.Areas.Help.ViewModels.HelpViewModel
|
||||
|
||||
@using Teknik.Utilities
|
||||
|
||||
<div class="container">
|
||||
<ol class="breadcrumb">
|
||||
<li><a href="@Url.SubRouteUrl("help", "Help.Index")">Help Index</a></li>
|
||||
<li class="active"><a href="#">Mumble Chat Server</a></li>
|
||||
</ol>
|
||||
<div class="row">
|
||||
<h2><b>Mumble</b></h2>
|
||||
<hr>
|
||||
|
@ -1,6 +1,12 @@
|
||||
@model Teknik.Areas.Help.ViewModels.HelpViewModel
|
||||
|
||||
@using Teknik.Utilities
|
||||
|
||||
<div class="container">
|
||||
<ol class="breadcrumb">
|
||||
<li><a href="@Url.SubRouteUrl("help", "Help.Index")">Help Index</a></li>
|
||||
<li class="active"><a href="#">RSS Feeds</a></li>
|
||||
</ol>
|
||||
<div class="row">
|
||||
<h2><b>RSS</b></h2>
|
||||
<hr>
|
||||
|
@ -1,6 +1,12 @@
|
||||
@model Teknik.Areas.Help.ViewModels.HelpViewModel
|
||||
|
||||
@using Teknik.Utilities
|
||||
|
||||
<div class="container">
|
||||
<ol class="breadcrumb">
|
||||
<li><a href="@Url.SubRouteUrl("help", "Help.Index")">Help Index</a></li>
|
||||
<li class="active"><a href="#">Tools</a></li>
|
||||
</ol>
|
||||
<div class="row">
|
||||
<h2 class="text-center"><b>Tools Utilizing Teknik Services</b></h2>
|
||||
<hr>
|
||||
|
@ -3,6 +3,10 @@
|
||||
@using Teknik.Utilities
|
||||
|
||||
<div class="container">
|
||||
<ol class="breadcrumb">
|
||||
<li><a href="@Url.SubRouteUrl("help", "Help.Index")">Help Index</a></li>
|
||||
<li class="active"><a href="#">Uploads</a></li>
|
||||
</ol>
|
||||
<div class="row">
|
||||
<div class="col-sm-10 col-sm-offset-1">
|
||||
<h2 class="text-center"><b>Uploads</b></h2>
|
||||
@ -14,7 +18,7 @@
|
||||
When you using the web interface for uploads, the file is loaded using the javascript <a href="https://developer.mozilla.org/en-US/docs/Web/API/FileReader">FileReader</a> API.
|
||||
</p>
|
||||
<p>
|
||||
Once the file is completely loaded into the buffer, a key and iv are generated by a random string generator to create a key that is <b>@Model.Config.UploadConfig.KeySize Bytes</b> and an iv (Block Size) that is <b>@Model.Config.UploadConfig.BlockSize Bytes</b>.
|
||||
Once the file is completely loaded into the buffer, a key and iv are generated by a random string generator to create a key that is <b>@Model.Config.UploadConfig.KeySize Bytes</b> and an iv (Block Size) that is <b>@Model.Config.UploadConfig.BlockSize Bytes</b>.
|
||||
Then the file buffer, key, and iv are passed into a Web Worker for encryption.
|
||||
</p>
|
||||
<p>
|
||||
|
@ -10,9 +10,11 @@ using Teknik.Controllers;
|
||||
using Teknik.Utilities;
|
||||
using Teknik.Models;
|
||||
using Teknik.Filters;
|
||||
using Teknik.Attributes;
|
||||
|
||||
namespace Teknik.Areas.Home.Controllers
|
||||
{
|
||||
[TeknikAuthorize]
|
||||
public class HomeController : DefaultController
|
||||
{
|
||||
// GET: Home/Home
|
||||
|
@ -14,9 +14,11 @@ using Teknik.Controllers;
|
||||
using Teknik.Filters;
|
||||
using Teknik.Utilities;
|
||||
using Teknik.Models;
|
||||
using Teknik.Attributes;
|
||||
|
||||
namespace Teknik.Areas.Paste.Controllers
|
||||
{
|
||||
[TeknikAuthorize]
|
||||
public class PasteController : DefaultController
|
||||
{
|
||||
private TeknikEntities db = new TeknikEntities();
|
||||
|
@ -8,6 +8,7 @@ using System.Web.Mvc;
|
||||
using Teknik.Areas.Podcast.Models;
|
||||
using Teknik.Areas.Podcast.ViewModels;
|
||||
using Teknik.Areas.Users.Utility;
|
||||
using Teknik.Attributes;
|
||||
using Teknik.Controllers;
|
||||
using Teknik.Filters;
|
||||
using Teknik.Models;
|
||||
@ -15,6 +16,7 @@ using Teknik.Utilities;
|
||||
|
||||
namespace Teknik.Areas.Podcast.Controllers
|
||||
{
|
||||
[TeknikAuthorize]
|
||||
public class PodcastController : DefaultController
|
||||
{
|
||||
private TeknikEntities db = new TeknikEntities();
|
||||
|
@ -4,11 +4,13 @@ using System.Linq;
|
||||
using System.Web;
|
||||
using System.Web.Mvc;
|
||||
using Teknik.Areas.Privacy.ViewModels;
|
||||
using Teknik.Attributes;
|
||||
using Teknik.Controllers;
|
||||
using Teknik.Filters;
|
||||
|
||||
namespace Teknik.Areas.Privacy.Controllers
|
||||
{
|
||||
[TeknikAuthorize]
|
||||
public class PrivacyController : DefaultController
|
||||
{
|
||||
// GET: Privacy/Privacy
|
||||
|
@ -11,9 +11,11 @@ using Teknik.Controllers;
|
||||
using Teknik.Filters;
|
||||
using Teknik.Utilities;
|
||||
using Teknik.Models;
|
||||
using Teknik.Attributes;
|
||||
|
||||
namespace Teknik.Areas.RSS.Controllers
|
||||
{
|
||||
[TeknikAuthorize(AuthType.Basic)]
|
||||
public class RSSController : DefaultController
|
||||
{
|
||||
private TeknikEntities db = new TeknikEntities();
|
||||
|
@ -6,6 +6,7 @@ using System.Web.Mvc;
|
||||
using Teknik.Areas.Shortener.Models;
|
||||
using Teknik.Areas.Shortener.ViewModels;
|
||||
using Teknik.Areas.Users.Utility;
|
||||
using Teknik.Attributes;
|
||||
using Teknik.Controllers;
|
||||
using Teknik.Filters;
|
||||
using Teknik.Models;
|
||||
@ -13,6 +14,7 @@ using Teknik.Utilities;
|
||||
|
||||
namespace Teknik.Areas.Shortener.Controllers
|
||||
{
|
||||
[TeknikAuthorize]
|
||||
public class ShortenerController : DefaultController
|
||||
{
|
||||
private TeknikEntities db = new TeknikEntities();
|
||||
|
@ -5,11 +5,13 @@ using System.Net;
|
||||
using System.Web;
|
||||
using System.Web.Mvc;
|
||||
using Teknik.Areas.Stream.ViewModels;
|
||||
using Teknik.Attributes;
|
||||
using Teknik.Controllers;
|
||||
using Teknik.Filters;
|
||||
|
||||
namespace Teknik.Areas.Stream.Controllers
|
||||
{
|
||||
[TeknikAuthorize]
|
||||
public class StreamController : DefaultController
|
||||
{
|
||||
[TrackPageView]
|
||||
|
@ -4,11 +4,13 @@ using System.Linq;
|
||||
using System.Web;
|
||||
using System.Web.Mvc;
|
||||
using Teknik.Areas.TOS.ViewModels;
|
||||
using Teknik.Attributes;
|
||||
using Teknik.Controllers;
|
||||
using Teknik.Filters;
|
||||
|
||||
namespace Teknik.Areas.TOS.Controllers
|
||||
{
|
||||
[TeknikAuthorize]
|
||||
public class TOSController : DefaultController
|
||||
{
|
||||
// GET: Privacy/Privacy
|
||||
|
@ -5,12 +5,14 @@ using System.Web;
|
||||
using System.Web.Mvc;
|
||||
using Teknik.Areas.Transparency.Models;
|
||||
using Teknik.Areas.Transparency.ViewModels;
|
||||
using Teknik.Attributes;
|
||||
using Teknik.Controllers;
|
||||
using Teknik.Filters;
|
||||
using Teknik.Models;
|
||||
|
||||
namespace Teknik.Areas.Transparency.Controllers
|
||||
{
|
||||
[TeknikAuthorize]
|
||||
public class TransparencyController : DefaultController
|
||||
{
|
||||
private TeknikEntities db = new TeknikEntities();
|
||||
|
@ -17,9 +17,11 @@ using Teknik.Controllers;
|
||||
using Teknik.Filters;
|
||||
using Teknik.Utilities;
|
||||
using Teknik.Models;
|
||||
using Teknik.Attributes;
|
||||
|
||||
namespace Teknik.Areas.Upload.Controllers
|
||||
{
|
||||
[TeknikAuthorize]
|
||||
public class UploadController : DefaultController
|
||||
{
|
||||
private TeknikEntities db = new TeknikEntities();
|
||||
|
@ -15,9 +15,11 @@ using Teknik.Filters;
|
||||
using QRCoder;
|
||||
using TwoStepsAuthenticator;
|
||||
using System.Drawing;
|
||||
using Teknik.Attributes;
|
||||
|
||||
namespace Teknik.Areas.Users.Controllers
|
||||
{
|
||||
[TeknikAuthorize]
|
||||
public class UserController : DefaultController
|
||||
{
|
||||
private static readonly UsedCodesManager usedCodesManager = new UsedCodesManager();
|
||||
@ -100,7 +102,16 @@ namespace Teknik.Areas.Users.Controllers
|
||||
model.UserID = user.UserId;
|
||||
model.Username = user.Username;
|
||||
model.TrustedDeviceCount = user.TrustedDevices.Count;
|
||||
model.AuthTokens = user.AuthTokens.ToList();
|
||||
model.AuthTokens = new List<AuthTokenViewModel>();
|
||||
foreach (AuthToken token in user.AuthTokens)
|
||||
{
|
||||
AuthTokenViewModel tokenModel = new AuthTokenViewModel();
|
||||
tokenModel.AuthTokenId = token.AuthTokenId;
|
||||
tokenModel.Name = token.Name;
|
||||
tokenModel.LastDateUsed = token.LastDateUsed;
|
||||
|
||||
model.AuthTokens.Add(tokenModel);
|
||||
}
|
||||
|
||||
model.UserSettings = user.UserSettings;
|
||||
model.SecuritySettings = user.SecuritySettings;
|
||||
@ -788,7 +799,7 @@ namespace Teknik.Areas.Users.Controllers
|
||||
User user = UserHelper.GetUser(db, User.Identity.Name);
|
||||
if (user != null)
|
||||
{
|
||||
string newTokenStr = UserHelper.GenerateAuthToken(Config, user.Username);
|
||||
string newTokenStr = UserHelper.GenerateAuthToken(db, user.Username);
|
||||
|
||||
if (!string.IsNullOrEmpty(newTokenStr))
|
||||
{
|
||||
@ -796,11 +807,16 @@ namespace Teknik.Areas.Users.Controllers
|
||||
token.UserId = user.UserId;
|
||||
token.HashedToken = SHA256.Hash(newTokenStr);
|
||||
token.Name = name;
|
||||
token.LastDateUsed = DateTime.Now;
|
||||
|
||||
db.AuthTokens.Add(token);
|
||||
db.SaveChanges();
|
||||
return Json(new { result = newTokenStr });
|
||||
|
||||
AuthTokenViewModel model = new AuthTokenViewModel();
|
||||
model.AuthTokenId = token.AuthTokenId;
|
||||
model.Name = token.Name;
|
||||
model.LastDateUsed = token.LastDateUsed;
|
||||
|
||||
return Json(new { result = new { token = newTokenStr, html = PartialView("~/Areas/User/Views/User/AuthToken.cshtml", model).RenderToString() } });
|
||||
}
|
||||
return Json(new { error = "Unable to generate Auth Token" });
|
||||
}
|
||||
@ -811,5 +827,93 @@ namespace Teknik.Areas.Users.Controllers
|
||||
return Json(new { error = ex.GetFullMessage(true) });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[ValidateAntiForgeryToken]
|
||||
public ActionResult RevokeAllTokens()
|
||||
{
|
||||
try
|
||||
{
|
||||
User user = UserHelper.GetUser(db, User.Identity.Name);
|
||||
if (user != null)
|
||||
{
|
||||
user.AuthTokens.Clear();
|
||||
List<AuthToken> foundTokens = db.AuthTokens.Where(d => d.UserId == user.UserId).ToList();
|
||||
if (foundTokens != null)
|
||||
{
|
||||
foreach (AuthToken token in foundTokens)
|
||||
{
|
||||
db.AuthTokens.Remove(token);
|
||||
}
|
||||
}
|
||||
db.Entry(user).State = EntityState.Modified;
|
||||
db.SaveChanges();
|
||||
|
||||
return Json(new { result = true });
|
||||
}
|
||||
return Json(new { error = "User does not exist" });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Json(new { error = ex.GetFullMessage(true) });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[ValidateAntiForgeryToken]
|
||||
public ActionResult EditTokenName(int tokenId, string name)
|
||||
{
|
||||
try
|
||||
{
|
||||
User user = UserHelper.GetUser(db, User.Identity.Name);
|
||||
if (user != null)
|
||||
{
|
||||
AuthToken foundToken = db.AuthTokens.Where(d => d.UserId == user.UserId && d.AuthTokenId == tokenId).FirstOrDefault();
|
||||
if (foundToken != null)
|
||||
{
|
||||
foundToken.Name = name;
|
||||
db.Entry(foundToken).State = EntityState.Modified;
|
||||
db.SaveChanges();
|
||||
|
||||
return Json(new { result = new { name = name } });
|
||||
}
|
||||
return Json(new { error = "Authentication Token does not exist" });
|
||||
}
|
||||
return Json(new { error = "User does not exist" });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Json(new { error = ex.GetFullMessage(true) });
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost]
|
||||
[ValidateAntiForgeryToken]
|
||||
public ActionResult DeleteToken(int tokenId)
|
||||
{
|
||||
try
|
||||
{
|
||||
User user = UserHelper.GetUser(db, User.Identity.Name);
|
||||
if (user != null)
|
||||
{
|
||||
AuthToken foundToken = db.AuthTokens.Where(d => d.UserId == user.UserId && d.AuthTokenId == tokenId).FirstOrDefault();
|
||||
if (foundToken != null)
|
||||
{
|
||||
db.AuthTokens.Remove(foundToken);
|
||||
user.AuthTokens.Remove(foundToken);
|
||||
db.Entry(user).State = EntityState.Modified;
|
||||
db.SaveChanges();
|
||||
|
||||
return Json(new { result = true });
|
||||
}
|
||||
return Json(new { error = "Authentication Token does not exist" });
|
||||
}
|
||||
return Json(new { error = "User does not exist" });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Json(new { error = ex.GetFullMessage(true) });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -20,6 +20,6 @@ namespace Teknik.Areas.Users.Models
|
||||
[CaseSensitive]
|
||||
public string HashedToken { get; set; }
|
||||
|
||||
public DateTime LastDateUsed { get; set; }
|
||||
public DateTime? LastDateUsed { get; set; }
|
||||
}
|
||||
}
|
@ -102,11 +102,21 @@
|
||||
success: function (response) {
|
||||
if (response.result) {
|
||||
bootbox.dialog({
|
||||
closeButton: false,
|
||||
buttons: {
|
||||
close: {
|
||||
label: 'Close',
|
||||
className: 'btn-primary',
|
||||
callback: function () {
|
||||
if ($('#noAuthTokens')) {
|
||||
$('#noAuthTokens').remove();
|
||||
}
|
||||
$('#authTokenList').append(response.result.html);
|
||||
}
|
||||
}
|
||||
},
|
||||
title: "Authentication Token",
|
||||
message: '<label for="authToken">Make sure to copy your new personal access token now.<br />You won\'t be able to see it again!</label><input type="text" class="form-control" id="authToken" onClick="this.select();" value="' + response.result + '">',
|
||||
callback: function () {
|
||||
window.location.reload();
|
||||
}
|
||||
message: '<label for="authToken">Make sure to copy your new personal access token now.<br />You won\'t be able to see it again!</label><input type="text" class="form-control" id="authToken" onClick="this.select();" value="' + response.result.token + '">',
|
||||
});
|
||||
}
|
||||
else {
|
||||
@ -135,7 +145,7 @@
|
||||
data: AddAntiForgeryToken({}),
|
||||
success: function (response) {
|
||||
if (response.result) {
|
||||
window.location.reload();
|
||||
$('#authTokenList').html('<li class="list-group-item text-center" id="noAuthTokens">No Authentication Tokens</li>');
|
||||
}
|
||||
else {
|
||||
errorMsg = response;
|
||||
@ -299,4 +309,63 @@
|
||||
});
|
||||
return false;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function editAuthToken(authTokenId) {
|
||||
bootbox.prompt("Specify a new name for this Auth Token", function (result) {
|
||||
if (result) {
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: editTokenNameURL,
|
||||
data: AddAntiForgeryToken({ tokenId: authTokenId, name: result }),
|
||||
success: function (response) {
|
||||
if (response.result) {
|
||||
$('#authTokenName_' + authTokenId).html(response.result.name);
|
||||
}
|
||||
else {
|
||||
errorMsg = response;
|
||||
if (response.error) {
|
||||
errorMsg = response.error;
|
||||
if (response.error.message) {
|
||||
errorMsg = response.error.message;
|
||||
}
|
||||
}
|
||||
$("#top_msg").css('display', 'inline', 'important');
|
||||
$("#top_msg").html('<div class="alert alert-danger alert-dismissable"><button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button>' + errorMsg + '</div>');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function deleteAuthToken(authTokenId) {
|
||||
bootbox.confirm("Are you sure you want to revoke this auth token?<br /><br />This is <b>irreversable</b> and all applications using this token will stop working.", function (result) {
|
||||
if (result) {
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: deleteTokenURL,
|
||||
data: AddAntiForgeryToken({ tokenId: authTokenId }),
|
||||
success: function (response) {
|
||||
if (response.result) {
|
||||
$('#authToken_' + authTokenId).remove();
|
||||
if ($('#authTokenList li').length <= 0) {
|
||||
$('#authTokenList').html('<li class="list-group-item text-center" id="noAuthTokens">No Authentication Tokens</li>');
|
||||
}
|
||||
}
|
||||
else {
|
||||
errorMsg = response;
|
||||
if (response.error) {
|
||||
errorMsg = response.error;
|
||||
if (response.error.message) {
|
||||
errorMsg = response.error.message;
|
||||
}
|
||||
}
|
||||
$("#top_msg").css('display', 'inline', 'important');
|
||||
$("#top_msg").html('<div class="alert alert-danger alert-dismissable"><button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button>' + errorMsg + '</div>');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
@ -134,15 +134,27 @@ namespace Teknik.Areas.Users.Utility
|
||||
}
|
||||
}
|
||||
|
||||
public static string GenerateAuthToken(Config config, string username)
|
||||
public static string GenerateAuthToken(TeknikEntities db, string username)
|
||||
{
|
||||
try
|
||||
{
|
||||
username = username.ToLower();
|
||||
byte[] hashBytes = SHA384.Hash(username, StringHelper.RandomString(24));
|
||||
string hash = hashBytes.ToHex();
|
||||
bool validToken = false;
|
||||
string token = string.Empty;
|
||||
while (!validToken)
|
||||
{
|
||||
username = username.ToLower();
|
||||
byte[] hashBytes = SHA384.Hash(username, StringHelper.RandomString(24));
|
||||
token = hashBytes.ToHex();
|
||||
|
||||
return hash;
|
||||
// Make sure it isn't a duplicate
|
||||
string hashedToken = SHA256.Hash(token);
|
||||
if (!db.AuthTokens.Where(t => t.HashedToken == hashedToken).Any())
|
||||
{
|
||||
validToken = true;
|
||||
}
|
||||
}
|
||||
|
||||
return token;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@ -228,11 +240,14 @@ namespace Teknik.Areas.Users.Utility
|
||||
return user;
|
||||
}
|
||||
|
||||
public static User GetUserFromToken(TeknikEntities db, string token)
|
||||
public static User GetUserFromToken(TeknikEntities db, string username, string token)
|
||||
{
|
||||
string hashedToken = SHA256.Hash(token);
|
||||
User foundUser = db.Users.FirstOrDefault(u => u.AuthTokens.Select(a => a.HashedToken).Contains(hashedToken));
|
||||
return foundUser;
|
||||
if (token != null && !string.IsNullOrEmpty(username))
|
||||
{
|
||||
string hashedToken = SHA256.Hash(token);
|
||||
return db.Users.FirstOrDefault(u => u.AuthTokens.Select(a => a.HashedToken).Contains(hashedToken) && u.Username == username);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static bool UserExists(TeknikEntities db, string username)
|
||||
@ -263,6 +278,25 @@ namespace Teknik.Areas.Users.Utility
|
||||
}
|
||||
}
|
||||
|
||||
public static void UpdateTokenLastUsed(TeknikEntities db, string username, string token, DateTime lastUsed)
|
||||
{
|
||||
User foundUser = GetUser(db, username);
|
||||
if (foundUser != null)
|
||||
{
|
||||
string hashedToken = SHA256.Hash(token);
|
||||
List<AuthToken> tokens = foundUser.AuthTokens.Where(t => t.HashedToken == hashedToken).ToList();
|
||||
if (tokens != null)
|
||||
{
|
||||
foreach (AuthToken foundToken in tokens)
|
||||
{
|
||||
foundToken.LastDateUsed = lastUsed;
|
||||
db.Entry(foundToken).State = EntityState.Modified;
|
||||
}
|
||||
db.SaveChanges();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static bool UserPasswordCorrect(TeknikEntities db, Config config, User user, string password)
|
||||
{
|
||||
try
|
||||
@ -276,6 +310,59 @@ namespace Teknik.Areas.Users.Utility
|
||||
}
|
||||
}
|
||||
|
||||
public static bool UserTokenCorrect(TeknikEntities db, string username, string token)
|
||||
{
|
||||
User foundUser = GetUserFromToken(db, username, token);
|
||||
if (foundUser != null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool UserHasRoles(TeknikEntities db, User user, params string[] roles)
|
||||
{
|
||||
bool hasRole = true;
|
||||
if (user != null)
|
||||
{
|
||||
// Check if they have the role specified
|
||||
if (roles.Any())
|
||||
{
|
||||
foreach (string role in roles)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(role))
|
||||
{
|
||||
if (user.Groups.Where(g => g.Roles.Where(r => role == r.Name).Any()).Any())
|
||||
{
|
||||
// They have the role!
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// They don't have this role, so let's reset the hasRole
|
||||
hasRole = false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Only set this if we haven't failed once already
|
||||
hasRole &= true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// No roles to check, so they pass!
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
hasRole = false;
|
||||
}
|
||||
return hasRole;
|
||||
}
|
||||
|
||||
public static void TransferUser(TeknikEntities db, Config config, User user, string password)
|
||||
{
|
||||
try
|
||||
|
17
Teknik/Areas/User/ViewModels/AuthTokenViewModel.cs
Normal file
17
Teknik/Areas/User/ViewModels/AuthTokenViewModel.cs
Normal file
@ -0,0 +1,17 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Web;
|
||||
using Teknik.ViewModels;
|
||||
|
||||
namespace Teknik.Areas.Users.ViewModels
|
||||
{
|
||||
public class AuthTokenViewModel : ViewModelBase
|
||||
{
|
||||
public int AuthTokenId { get; set; }
|
||||
|
||||
public string Name { get; set; }
|
||||
|
||||
public DateTime? LastDateUsed { get; set; }
|
||||
}
|
||||
}
|
@ -15,7 +15,7 @@ namespace Teknik.Areas.Users.ViewModels
|
||||
|
||||
public int TrustedDeviceCount { get; set; }
|
||||
|
||||
public List<AuthToken> AuthTokens { get; set; }
|
||||
public List<AuthTokenViewModel> AuthTokens { get; set; }
|
||||
|
||||
public UserSettings UserSettings { get; set; }
|
||||
|
||||
|
17
Teknik/Areas/User/Views/User/AuthToken.cshtml
Normal file
17
Teknik/Areas/User/Views/User/AuthToken.cshtml
Normal file
@ -0,0 +1,17 @@
|
||||
@model Teknik.Areas.Users.ViewModels.AuthTokenViewModel
|
||||
|
||||
<li class="list-group-item" id="authToken_@Model.AuthTokenId">
|
||||
<div class="btn-group btn-group-sm pull-right" role="group" aria-label="...">
|
||||
<button type="button" class="btn btn-default" onclick="editAuthToken(@Model.AuthTokenId)">Edit</button>
|
||||
<button type="button" class="btn btn-danger text-danger" onclick="deleteAuthToken(@Model.AuthTokenId)">Delete</button>
|
||||
</div>
|
||||
<h4 class="list-group-item-heading" id="authTokenName_@Model.AuthTokenId">@Model.Name</h4>
|
||||
@if (Model.LastDateUsed.HasValue)
|
||||
{
|
||||
<p class="list-group-item-text">Last Used on <i><time datetime="@Model.LastDateUsed.Value.ToString("s")">@Model.LastDateUsed.Value.ToString("MMMM dd, yyyy hh:mm tt")</time></i></p>
|
||||
}
|
||||
else
|
||||
{
|
||||
<p class="list-group-item-text">Last Used <i>Never</i></p>
|
||||
}
|
||||
</li>
|
@ -1,7 +1,7 @@
|
||||
@model Teknik.Areas.Users.ViewModels.SettingsViewModel
|
||||
|
||||
@using Teknik.Utilities
|
||||
@using Teknik.Areas.Users.Models
|
||||
@using Teknik.Areas.Users.ViewModels
|
||||
|
||||
<script>
|
||||
var homeUrl = '@Url.SubRouteUrl("www", "Home.Index")';
|
||||
@ -12,7 +12,7 @@
|
||||
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 editTokenName = '@Url.SubRouteUrl("user", "User.Action", new { action = "EditTokenName" })';
|
||||
var editTokenNameURL = '@Url.SubRouteUrl("user", "User.Action", new { action = "EditTokenName" })';
|
||||
var deleteTokenURL = '@Url.SubRouteUrl("user", "User.Action", new { action = "DeleteToken" })';
|
||||
</script>
|
||||
|
||||
@ -181,26 +181,19 @@
|
||||
</div>
|
||||
<div class="col-sm-8">
|
||||
<br />
|
||||
<label for="authCodes"><h4>Authentication Tokens</h4></label><span class="pull-right"><button type="button" class="btn btn-default" id="generate_token">Generate Token</button> <button type="button" class="btn btn-danger" id="revoke_all_tokens">Revoke All</button></span>
|
||||
<div id="authCodes" style="overflow-y: auto; max-height: 400px;">
|
||||
<ul class="list-group">
|
||||
<label for="authTokens"><h4>Authentication Tokens</h4></label><span class="pull-right"><button type="button" class="btn btn-default" id="generate_token">Generate Token</button> <button type="button" class="btn btn-danger" id="revoke_all_tokens">Revoke All</button></span>
|
||||
<div id="authTokens" style="overflow-y: auto; max-height: 400px;">
|
||||
<ul class="list-group" id="authTokenList">
|
||||
@if (Model.AuthTokens.Any())
|
||||
{
|
||||
foreach (AuthToken token in Model.AuthTokens)
|
||||
foreach (AuthTokenViewModel token in Model.AuthTokens)
|
||||
{
|
||||
<li class="list-group-item">
|
||||
<div class="btn-group btn-group-sm pull-right" role="group" aria-label="...">
|
||||
<button type="button" class="btn btn-default">Edit</button>
|
||||
<button type="button" class="btn btn-danger text-danger">Delete</button>
|
||||
</div>
|
||||
<h4 class="list-group-item-heading" id="update_auth_token_name">@token.Name</h4>
|
||||
<p class="list-group-item-text">Last Used on <time datetime="@token.LastDateUsed.ToString("s")">@token.LastDateUsed.ToString("MMMM dd, yyyy hh:mm tt")</time></p>
|
||||
</li>
|
||||
@Html.Partial("AuthToken", token)
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
<li class="list-group-item text-center">No Auth Codes</li>
|
||||
<li class="list-group-item text-center" id="noAuthTokens">No Authentication Tokens</li>
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
|
@ -3,10 +3,12 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Web;
|
||||
using System.Web.Mvc;
|
||||
using Teknik.Attributes;
|
||||
using Teknik.Controllers;
|
||||
|
||||
namespace Teknik.Areas.Vault.Controllers
|
||||
{
|
||||
[TeknikAuthorize]
|
||||
public class VaultController : DefaultController
|
||||
{
|
||||
[AllowAnonymous]
|
||||
|
@ -7,12 +7,33 @@ using System.Web.Routing;
|
||||
using Teknik.Areas.Error.Controllers;
|
||||
using Teknik.Utilities;
|
||||
using Teknik.Areas.Users.Controllers;
|
||||
using Teknik.Models;
|
||||
using Teknik.Areas.Users.Utility;
|
||||
using Teknik.Areas.Users.Models;
|
||||
using Teknik.Configuration;
|
||||
|
||||
namespace Teknik.Attributes
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.All, AllowMultiple = false)]
|
||||
public enum AuthType
|
||||
{
|
||||
Basic,
|
||||
Forms
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.All, AllowMultiple = true)]
|
||||
public class TeknikAuthorizeAttribute : AuthorizeAttribute
|
||||
{
|
||||
private AuthType m_AuthType { get; set; }
|
||||
|
||||
public TeknikAuthorizeAttribute() : this(AuthType.Forms)
|
||||
{
|
||||
}
|
||||
|
||||
public TeknikAuthorizeAttribute(AuthType authType)
|
||||
{
|
||||
m_AuthType = authType;
|
||||
}
|
||||
|
||||
public override void OnAuthorization(AuthorizationContext filterContext)
|
||||
{
|
||||
if (filterContext == null)
|
||||
@ -36,7 +57,7 @@ namespace Teknik.Attributes
|
||||
return;
|
||||
|
||||
// Check the users auth
|
||||
if (base.AuthorizeCore(filterContext.HttpContext))
|
||||
if (AuthorizeCore(filterContext.HttpContext))
|
||||
{
|
||||
// ** IMPORTANT **
|
||||
// Since we're performing authorization at the action level, the authorization code runs
|
||||
@ -54,7 +75,7 @@ namespace Teknik.Attributes
|
||||
}
|
||||
else if (!filterContext.HttpContext.User.Identity.IsAuthenticated)
|
||||
{
|
||||
this.HandleUnauthorizedRequest(filterContext);
|
||||
HandleUnauthorizedRequest(filterContext);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -63,34 +84,119 @@ namespace Teknik.Attributes
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool AuthorizeCore(HttpContextBase httpContext)
|
||||
{
|
||||
switch (m_AuthType)
|
||||
{
|
||||
case AuthType.Basic:
|
||||
#region Basic Authentication
|
||||
// Let's see if they have an auth token
|
||||
if (httpContext.Request != null)
|
||||
{
|
||||
if (httpContext.Request.Headers.HasKeys())
|
||||
{
|
||||
string auth = httpContext.Request.Headers["Authorization"];
|
||||
if (!string.IsNullOrEmpty(auth))
|
||||
{
|
||||
string[] parts = auth.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
string type = string.Empty;
|
||||
string value = string.Empty;
|
||||
if (parts.Length > 0)
|
||||
{
|
||||
type = parts[0].ToLower();
|
||||
}
|
||||
if (parts.Length > 1)
|
||||
{
|
||||
value = parts[1];
|
||||
}
|
||||
|
||||
using (TeknikEntities entities = new TeknikEntities())
|
||||
{
|
||||
// Get the user information based on the auth type
|
||||
switch (type)
|
||||
{
|
||||
case "basic":
|
||||
KeyValuePair<string, string> authCreds = StringHelper.ParseBasicAuthHeader(value);
|
||||
bool validToken = UserHelper.UserTokenCorrect(entities, authCreds.Key, authCreds.Value);
|
||||
|
||||
if (validToken)
|
||||
{
|
||||
User user = UserHelper.GetUserFromToken(entities, authCreds.Key, authCreds.Value);
|
||||
return UserHelper.UserHasRoles(entities, user, Roles);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
return false;
|
||||
case AuthType.Forms:
|
||||
return base.AuthorizeCore(httpContext);
|
||||
default:
|
||||
return base.AuthorizeCore(httpContext);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
|
||||
{
|
||||
// auth failed, redirect to login page
|
||||
var request = filterContext.HttpContext.Request;
|
||||
string redirectUrl = (request.Url != null) ? filterContext.HttpContext.Request.Url.AbsoluteUri.ToString() : string.Empty;
|
||||
|
||||
var userController = new UserController();
|
||||
if (userController != null)
|
||||
ActionResult result = new HttpUnauthorizedResult();
|
||||
switch (m_AuthType)
|
||||
{
|
||||
filterContext.Result = userController.Login(redirectUrl);
|
||||
return;
|
||||
case AuthType.Basic:
|
||||
Config config = Config.Load();
|
||||
filterContext.HttpContext.Response.Clear();
|
||||
filterContext.HttpContext.Response.AddHeader("WWW-Authenticate", String.Format("Basic realm=\"{0}\"", config.Title));
|
||||
filterContext.HttpContext.Response.StatusCode = 401;
|
||||
filterContext.HttpContext.Response.TrySkipIisCustomErrors = true;
|
||||
filterContext.HttpContext.Response.SuppressFormsAuthenticationRedirect = true;
|
||||
result = new JsonResult() { Data = new { error = new { type = 401, message = "Unauthorized" } }, JsonRequestBehavior = JsonRequestBehavior.AllowGet };
|
||||
break;
|
||||
case AuthType.Forms:
|
||||
var userController = new UserController();
|
||||
if (userController != null)
|
||||
{
|
||||
// auth failed, redirect to login page
|
||||
var request = filterContext.HttpContext.Request;
|
||||
string redirectUrl = (request.Url != null) ? filterContext.HttpContext.Request.Url.AbsoluteUri.ToString() : string.Empty;
|
||||
|
||||
result = userController.Login(redirectUrl);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
filterContext.Result = new HttpUnauthorizedResult();
|
||||
|
||||
filterContext.Result = result;
|
||||
}
|
||||
|
||||
protected void HandleInvalidAuthRequest(AuthorizationContext filterContext)
|
||||
{
|
||||
// auth failed, redirect to login page
|
||||
var request = filterContext.HttpContext.Request;
|
||||
string redirectUrl = (request.Url != null) ? filterContext.HttpContext.Request.Url.AbsoluteUri.ToString() : string.Empty;
|
||||
|
||||
var errorController = new ErrorController();
|
||||
if (errorController != null)
|
||||
ActionResult result = new HttpUnauthorizedResult();
|
||||
switch (m_AuthType)
|
||||
{
|
||||
filterContext.Result = errorController.Http403(new Exception("Not Authorized"));
|
||||
return;
|
||||
case AuthType.Basic:
|
||||
filterContext.HttpContext.Response.Clear();
|
||||
filterContext.HttpContext.Response.StatusCode = 403;
|
||||
filterContext.HttpContext.Response.TrySkipIisCustomErrors = true;
|
||||
filterContext.HttpContext.Response.SuppressFormsAuthenticationRedirect = true;
|
||||
result = new JsonResult() { Data = new { error = new { type = 403, message = "Not Authorized" } }, JsonRequestBehavior = JsonRequestBehavior.AllowGet };
|
||||
break;
|
||||
case AuthType.Forms:
|
||||
var errorController = new ErrorController();
|
||||
if (errorController != null)
|
||||
{
|
||||
result = errorController.Http403(new Exception("Not Authorized"));
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
filterContext.Result = new HttpUnauthorizedResult();
|
||||
|
||||
filterContext.Result = result;
|
||||
}
|
||||
|
||||
private void CacheValidateHandler(HttpContext context, object data, ref HttpValidationStatus validationStatus)
|
||||
|
@ -13,6 +13,8 @@ using Teknik.Areas.Error.Controllers;
|
||||
using System.Web.Helpers;
|
||||
using System.Diagnostics;
|
||||
using Teknik.Utilities;
|
||||
using System.Text;
|
||||
using Teknik.Areas.Users.Utility;
|
||||
|
||||
namespace Teknik
|
||||
{
|
||||
@ -65,37 +67,89 @@ namespace Teknik
|
||||
|
||||
protected void Application_PostAuthenticateRequest(Object sender, EventArgs e)
|
||||
{
|
||||
if (FormsAuthentication.CookiesSupported == true)
|
||||
// We support both Auth Tokens and Cookie Authentication
|
||||
|
||||
// Username and Roles for the current user
|
||||
string username = string.Empty;
|
||||
List<string> roles = new List<string>();
|
||||
|
||||
bool hasAuthToken = false;
|
||||
if (Request != null)
|
||||
{
|
||||
if (Request.Headers.HasKeys())
|
||||
{
|
||||
string auth = Request.Headers["Authorization"];
|
||||
if (!string.IsNullOrEmpty(auth))
|
||||
{
|
||||
string[] parts = auth.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
string type = string.Empty;
|
||||
string value = string.Empty;
|
||||
if (parts.Length > 0)
|
||||
{
|
||||
type = parts[0].ToLower();
|
||||
}
|
||||
if (parts.Length > 1)
|
||||
{
|
||||
value = parts[1];
|
||||
}
|
||||
|
||||
using (TeknikEntities entities = new TeknikEntities())
|
||||
{
|
||||
// Get the user information based on the auth type
|
||||
switch (type)
|
||||
{
|
||||
case "basic":
|
||||
KeyValuePair<string, string> authCreds = StringHelper.ParseBasicAuthHeader(value);
|
||||
|
||||
bool tokenValid = UserHelper.UserTokenCorrect(entities, authCreds.Key, authCreds.Value);
|
||||
if (tokenValid)
|
||||
{
|
||||
// it's valid, so let's update it's Last Used date
|
||||
UserHelper.UpdateTokenLastUsed(entities, authCreds.Key, authCreds.Value, DateTime.Now);
|
||||
|
||||
// Set the username
|
||||
username = authCreds.Key;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (FormsAuthentication.CookiesSupported == true && !hasAuthToken)
|
||||
{
|
||||
if (Request.Cookies[FormsAuthentication.FormsCookieName] != null)
|
||||
{
|
||||
//let us take out the username now
|
||||
string username = FormsAuthentication.Decrypt(Request.Cookies[FormsAuthentication.FormsCookieName].Value).Name;
|
||||
List<string> roles = new List<string>();
|
||||
username = FormsAuthentication.Decrypt(Request.Cookies[FormsAuthentication.FormsCookieName].Value).Name;
|
||||
}
|
||||
}
|
||||
|
||||
using (TeknikEntities entities = new TeknikEntities())
|
||||
// Create the new user if we found one from the supplied auth info
|
||||
if (!string.IsNullOrEmpty(username))
|
||||
{
|
||||
using (TeknikEntities entities = new TeknikEntities())
|
||||
{
|
||||
User user = UserHelper.GetUser(entities, username);
|
||||
|
||||
// Grab all their roles
|
||||
foreach (Group grp in user.Groups)
|
||||
{
|
||||
User user = entities.Users.SingleOrDefault(u => u.Username == username);
|
||||
|
||||
if (user != null)
|
||||
foreach (Role role in grp.Roles)
|
||||
{
|
||||
foreach (Group grp in user.Groups)
|
||||
if (!roles.Contains(role.Name))
|
||||
{
|
||||
foreach (Role role in grp.Roles)
|
||||
{
|
||||
if (!roles.Contains(role.Name))
|
||||
{
|
||||
roles.Add(role.Name);
|
||||
}
|
||||
}
|
||||
roles.Add(role.Name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Let us set the Pricipal with our user specific details
|
||||
HttpContext.Current.User = new System.Security.Principal.GenericPrincipal(
|
||||
new System.Security.Principal.GenericIdentity(username, "Forms"), roles.ToArray());
|
||||
}
|
||||
|
||||
HttpContext.Current.User = new System.Security.Principal.GenericPrincipal(
|
||||
new System.Security.Principal.GenericIdentity(username, "Forms"), roles.ToArray());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -240,6 +240,7 @@
|
||||
<Compile Include="Areas\User\Models\SecuritySettings.cs" />
|
||||
<Compile Include="Areas\User\Models\AuthToken.cs" />
|
||||
<Compile Include="Areas\User\Models\TrustedDevice.cs" />
|
||||
<Compile Include="Areas\User\ViewModels\AuthTokenViewModel.cs" />
|
||||
<Compile Include="Areas\User\ViewModels\TwoFactorViewModel.cs" />
|
||||
<Compile Include="Attributes\TeknikAuthorizeAttribute.cs" />
|
||||
<Compile Include="Filters\CORSActionFilter.cs" />
|
||||
@ -567,6 +568,8 @@
|
||||
<Content Include="Fonts\fontawesome-webfont.woff" />
|
||||
<Content Include="Fonts\fontawesome-webfont.ttf" />
|
||||
<Content Include="Fonts\fontawesome-webfont.eot" />
|
||||
<Content Include="Areas\User\Views\User\AuthToken.cshtml" />
|
||||
<Content Include="Areas\Error\Views\Error\Http401.cshtml" />
|
||||
<None Include="Properties\PublishProfiles\Teknik Dev.pubxml" />
|
||||
<None Include="Properties\PublishProfiles\Teknik Production.pubxml" />
|
||||
<None Include="Scripts\jquery-2.1.4.intellisense.js" />
|
||||
|
@ -17,6 +17,5 @@ namespace Teknik.Utilities
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -85,5 +85,28 @@ namespace Teknik.Utilities
|
||||
// Return formatted number with suffix
|
||||
return readable.ToString("0.### ") + suffix;
|
||||
}
|
||||
|
||||
public static KeyValuePair<string, string> ParseBasicAuthHeader(string value)
|
||||
{
|
||||
return ParseBasicAuthHeader(value, Encoding.UTF8);
|
||||
}
|
||||
|
||||
public static KeyValuePair<string, string> ParseBasicAuthHeader(string value, Encoding encoding)
|
||||
{
|
||||
KeyValuePair<string, string> result = new KeyValuePair<string, string>();
|
||||
|
||||
if (!string.IsNullOrEmpty(value))
|
||||
{
|
||||
byte[] rawVal = Convert.FromBase64String(value);
|
||||
string stringVal = encoding.GetString(rawVal);
|
||||
string[] parts = stringVal.Split(new char[] { ':' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
if (parts.Length > 1)
|
||||
{
|
||||
result = new KeyValuePair<string, string>(parts[0], parts[1]);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
@ -123,6 +123,7 @@
|
||||
<Compile Include="StringExtensions.cs" />
|
||||
<Compile Include="StringHelper.cs" />
|
||||
<Compile Include="UrlExtensions.cs" />
|
||||
<Compile Include="ViewExtensions.cs" />
|
||||
<Compile Include="WebClientExtension.cs">
|
||||
<SubType>Component</SubType>
|
||||
</Compile>
|
||||
|
45
Utilities/Utilities/ViewExtensions.cs
Normal file
45
Utilities/Utilities/ViewExtensions.cs
Normal file
@ -0,0 +1,45 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web;
|
||||
using System.Web.Mvc;
|
||||
using System.Web.UI;
|
||||
|
||||
namespace Teknik.Utilities
|
||||
{
|
||||
public static class ViewExtensions
|
||||
{
|
||||
public static string RenderToString(this PartialViewResult partialView)
|
||||
{
|
||||
var httpContext = HttpContext.Current;
|
||||
|
||||
if (httpContext == null)
|
||||
{
|
||||
throw new NotSupportedException("An HTTP context is required to render the partial view to a string");
|
||||
}
|
||||
|
||||
var controllerName = httpContext.Request.RequestContext.RouteData.Values["controller"].ToString();
|
||||
|
||||
var controller = (ControllerBase)ControllerBuilder.Current.GetControllerFactory().CreateController(httpContext.Request.RequestContext, controllerName);
|
||||
|
||||
var controllerContext = new ControllerContext(httpContext.Request.RequestContext, controller);
|
||||
|
||||
var view = ViewEngines.Engines.FindPartialView(controllerContext, partialView.ViewName).View;
|
||||
|
||||
var sb = new StringBuilder();
|
||||
|
||||
using (var sw = new StringWriter(sb))
|
||||
{
|
||||
using (var tw = new HtmlTextWriter(sw))
|
||||
{
|
||||
view.Render(new ViewContext(controllerContext, view, partialView.ViewData, partialView.TempData, tw), tw);
|
||||
}
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user