1
0
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:
Uncled1023 2017-01-21 21:13:25 -08:00
parent 41e4dca194
commit cbe5078b9d
50 changed files with 869 additions and 96 deletions

View File

@ -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());
}
}

View File

@ -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();

View File

@ -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;

View File

@ -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;
}
}
}

View File

@ -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]

View File

@ -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();

View File

@ -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();

View File

@ -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]

View File

@ -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)
{

View 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>

View File

@ -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

View File

@ -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>&lt;username&gt;</code> with your Teknik username and <code>&lt;token&gt;</code> with your token.
<pre><code>$ curl -u &lt;username&gt;:&lt;token&gt; --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.

View File

@ -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>(&lt;username&gt;:&lt;token&gt;)</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>

View File

@ -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>(&lt;username&gt;:&lt;token&gt;)</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>

View File

@ -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>(&lt;username&gt;:&lt;token&gt;)</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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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

View File

@ -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();

View File

@ -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();

View File

@ -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

View File

@ -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();

View File

@ -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();

View File

@ -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]

View File

@ -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

View File

@ -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();

View File

@ -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();

View File

@ -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) });
}
}
}
}

View File

@ -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; }
}
}

View File

@ -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">&times;</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">&times;</button>' + errorMsg + '</div>');
}
}
});
}
});
}

View File

@ -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

View 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; }
}
}

View File

@ -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; }

View 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>

View File

@ -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>

View File

@ -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]

View File

@ -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)

View File

@ -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());
}
}

View File

@ -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" />

View File

@ -17,6 +17,5 @@ namespace Teknik.Utilities
}
return null;
}
}
}

View File

@ -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;
}
}
}

View File

@ -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>

View 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();
}
}
}