1
0
mirror of https://github.com/tomasvarg/OwinWebApiTest.git synced 2026-03-01 08:28:49 +00:00

Added comments & README

This commit is contained in:
Tomas Varga 2018-01-31 13:25:14 +01:00
parent 1fdd29c01a
commit 1528581705
10 changed files with 130 additions and 40 deletions

View File

@ -0,0 +1,34 @@
using Microsoft.Owin;
using Microsoft.Owin.FileSystems;
using Microsoft.Owin.StaticFiles;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Web;
namespace OwinWebApiTest
{
/**
* Configuration of the static file serving options
*/
public static class FileServerConfig
{
public static FileServerOptions Create(PathString pathString, string dir)
{
string appRoot = AppDomain.CurrentDomain.BaseDirectory;
var fileSystem = new PhysicalFileSystem(Path.Combine(appRoot, dir));
var options = new FileServerOptions
{
RequestPath = pathString,
EnableDefaultFiles = true,
FileSystem = fileSystem
};
options.StaticFileOptions.FileSystem = fileSystem;
options.StaticFileOptions.ServeUnknownFileTypes = true;
return options;
}
}
}

View File

@ -5,13 +5,13 @@ using System.Web.Http;
namespace OwinWebApiTest namespace OwinWebApiTest
{ {
/**
* Configuration of the web service options.
*/
public static class WebApiConfig public static class WebApiConfig
{ {
public static HttpConfiguration Register(HttpConfiguration config) public static HttpConfiguration Register(HttpConfiguration config)
{ {
// Web API configuration and services
// Web API routes
config.MapHttpAttributeRoutes(); config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute( config.Routes.MapHttpRoute(
@ -20,6 +20,15 @@ namespace OwinWebApiTest
defaults: new { id = RouteParameter.Optional } defaults: new { id = RouteParameter.Optional }
); );
config.Routes.MapHttpRoute(
name: "DetailApi",
routeTemplate: "api/{controller}/{resource}/{id}"
);
// require authorization for all the entrypoints
// can be overriden by [AllowAnonymous] attribute (at the controller/service level)
config.Filters.Add(new AuthorizeAttribute());
return config; return config;
} }
} }

View File

@ -1,8 +1,8 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<title></title> <title>Documentation</title>
<meta charset="utf-8" /> <meta charset="utf-8" />
</head> </head>
<body> <body>
<h1>Documentation</h1> <h1>Documentation</h1>

View File

@ -155,6 +155,7 @@
<Content Include="Web\index.html" /> <Content Include="Web\index.html" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="App_Start\FileServerConfig.cs" />
<Compile Include="App_Start\WebApiConfig.cs" /> <Compile Include="App_Start\WebApiConfig.cs" />
<Compile Include="Controllers\ItemsController.cs" /> <Compile Include="Controllers\ItemsController.cs" />
<Compile Include="Models\CasServiceValidationResponse.cs" /> <Compile Include="Models\CasServiceValidationResponse.cs" />

View File

@ -1,6 +1,6 @@
using Microsoft.Owin.Security.OAuth; using Microsoft.Owin.Infrastructure;
using Microsoft.Owin.Security.OAuth;
using Microsoft.Owin.Security; using Microsoft.Owin.Security;
using Microsoft.Owin.Infrastructure;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using Newtonsoft.Json; using Newtonsoft.Json;
using System.Collections.Generic; using System.Collections.Generic;
@ -13,7 +13,12 @@ using OwinWebApiTest.Models;
namespace OwinWebApiTest.Providers namespace OwinWebApiTest.Providers
{ {
/**
* CAS Single Sign On Authorization Provider.
*
* Authenticates user against CAS server login and grants authorization;
* provides authenticated user with access_token and AccessControl dataset.
*/
public class CasAuthorizationServerProvider : OAuthAuthorizationServerProvider public class CasAuthorizationServerProvider : OAuthAuthorizationServerProvider
{ {
private static string casValidationUrl; private static string casValidationUrl;
@ -26,13 +31,30 @@ namespace OwinWebApiTest.Providers
serviceUser = ConfigurationManager.AppSettings["ServiceUser"]; serviceUser = ConfigurationManager.AppSettings["ServiceUser"];
} }
/**
* Client authentication
*
* Superseded by CAS authentication but is required, so just validate
*/
public override async Task ValidateClientAuthentication( public override async Task ValidateClientAuthentication(
OAuthValidateClientAuthenticationContext context) OAuthValidateClientAuthenticationContext context)
{ {
// required but as we're not using client auth just validate & move on...
await Task.FromResult(context.Validated()); await Task.FromResult(context.Validated());
} }
/**
* Performs CAS ticket validation & grants authorization
*
* Expected params (POST method):
* "ticket" (provided to the client by the CAS server on a successful login)
* "service" (application url - against which the login attempt was performed)
* "grant_type=password"
*
* See TokenEndpointPath in Startup.cs for the autorization entry point URL
*
* If successful, grants authorization and returns client a response with
* a valid "access_token", "username" and (app-specific) "AccessControl" dataset
*/
public override async Task GrantResourceOwnerCredentials( public override async Task GrantResourceOwnerCredentials(
OAuthGrantResourceOwnerCredentialsContext context) OAuthGrantResourceOwnerCredentialsContext context)
{ {
@ -40,7 +62,7 @@ namespace OwinWebApiTest.Providers
if (string.IsNullOrEmpty(args["ticket"]) || string.IsNullOrEmpty(args["service"])) { if (string.IsNullOrEmpty(args["ticket"]) || string.IsNullOrEmpty(args["service"])) {
context.Rejected(); context.Rejected();
context.SetError("invalid_grant", "No CAS ticket or service URL sent."); context.SetError("invalid_grant", "No CAS ticket or service URL sent");
return; return;
} }
@ -57,6 +79,7 @@ namespace OwinWebApiTest.Providers
return; return;
} }
// once the CAS auth is done, gather additional data about the user (app-specific)
//var acda = new AccessControlDA(); //var acda = new AccessControlDA();
//var ac = acda.GetAccessControl(res.success.user); //var ac = acda.GetAccessControl(res.success.user);
var ac = new { userId = res.success.user, canRead = true, canSave = true }; var ac = new { userId = res.success.user, canRead = true, canSave = true };
@ -69,9 +92,10 @@ namespace OwinWebApiTest.Providers
ClaimsIdentity identity = new ClaimsIdentity(context.Options.AuthenticationType); ClaimsIdentity identity = new ClaimsIdentity(context.Options.AuthenticationType);
identity.AddClaim(new Claim(ClaimTypes.Name, res.success.user)); identity.AddClaim(new Claim(ClaimTypes.Name, res.success.user));
identity.AddClaim(new Claim(ClaimTypes.Role, "Admin")); identity.AddClaim(new Claim(ClaimTypes.Role, "User"));
// Identity info will be encoded into an Access ticket as a result of this call: // To add app-specific data to the access token response use AuthenticationProperties
// as below, for plain access token response identity will be encoded into it this way:
//context.Validated(identity); //context.Validated(identity);
var props = new AuthenticationProperties(new Dictionary<string, string> { var props = new AuthenticationProperties(new Dictionary<string, string> {
@ -83,7 +107,9 @@ namespace OwinWebApiTest.Providers
context.Validated(ticket); context.Validated(ticket);
} }
// needed to get the custom props as a part of the token-granted response /**
* Necessary to add the AuthenticationProperties to the AuthenticationTicket
*/
public override Task TokenEndpoint(OAuthTokenEndpointContext context) public override Task TokenEndpoint(OAuthTokenEndpointContext context)
{ {
foreach (KeyValuePair<string, string> property in context.Properties.Dictionary) foreach (KeyValuePair<string, string> property in context.Properties.Dictionary)
@ -94,6 +120,9 @@ namespace OwinWebApiTest.Providers
return Task.FromResult<object>(null); return Task.FromResult<object>(null);
} }
/**
* Validates CAS ticket received from the frontend against the CAS server
*/
private async Task<CasServiceValidationResponse> ValidateCasTicket(string ticket, string service) private async Task<CasServiceValidationResponse> ValidateCasTicket(string ticket, string service)
{ {
var requestUri = WebUtilities.AddQueryString(casValidationUrl, new Dictionary<string, string>() { var requestUri = WebUtilities.AddQueryString(casValidationUrl, new Dictionary<string, string>() {
@ -108,6 +137,9 @@ namespace OwinWebApiTest.Providers
} }
} }
/**
* Sends the CAS ticket validation request and gets relevant data from a response
*/
public async Task<CasServiceValidationResponse> GetCasServiceValidationAsync( public async Task<CasServiceValidationResponse> GetCasServiceValidationAsync(
HttpClient client, string requestUri) HttpClient client, string requestUri)
{ {

View File

@ -1,13 +1,10 @@
using System; using System;
using System.Configuration; using System.Configuration;
using System.IO;
using System.Threading.Tasks; using System.Threading.Tasks;
using Owin; using Owin;
using Microsoft.Owin; using Microsoft.Owin;
using Microsoft.Owin.Cors; using Microsoft.Owin.Cors;
using Microsoft.Owin.Security.OAuth; using Microsoft.Owin.Security.OAuth;
using Microsoft.Owin.FileSystems;
using Microsoft.Owin.StaticFiles;
using System.Web.Http; using System.Web.Http;
using System.Net; using System.Net;
@ -17,6 +14,9 @@ using OwinWebApiTest.Providers;
namespace OwinWebApiTest namespace OwinWebApiTest
{ {
/**
* Application entry point.
*/
public class Startup public class Startup
{ {
public void Configuration(IAppBuilder app) public void Configuration(IAppBuilder app)
@ -46,30 +46,15 @@ namespace OwinWebApiTest
ServicePointManager.ServerCertificateValidationCallback += ServicePointManager.ServerCertificateValidationCallback +=
(sender, cert, chain, sslPolicyErrors) => true; (sender, cert, chain, sslPolicyErrors) => true;
// configure web root
string webDir = ConfigurationManager.AppSettings["WebDirectory"]; string webDir = ConfigurationManager.AppSettings["WebDirectory"];
if (string.IsNullOrEmpty(webDir)) webDir = "Web"; if (string.IsNullOrEmpty(webDir)) webDir = "Web";
app.UseFileServer(GetFileServerOptions(PathString.Empty, webDir)); app.UseFileServer(FileServerConfig.Create(PathString.Empty, webDir));
// configure web root for /doc url
string docDir = ConfigurationManager.AppSettings["DocDirectory"]; string docDir = ConfigurationManager.AppSettings["DocDirectory"];
if (string.IsNullOrEmpty(docDir)) docDir = "Doc"; if (string.IsNullOrEmpty(docDir)) docDir = "Doc";
app.UseFileServer(GetFileServerOptions(new PathString("/doc"), docDir)); app.UseFileServer(FileServerConfig.Create(new PathString("/doc"), docDir));
}
private FileServerOptions GetFileServerOptions(PathString pathString, string dir)
{
string appRoot = AppDomain.CurrentDomain.BaseDirectory;
var fileSystem = new PhysicalFileSystem(Path.Combine(appRoot, dir));
var options = new FileServerOptions
{
RequestPath = pathString,
EnableDefaultFiles = true,
FileSystem = fileSystem
};
options.StaticFileOptions.FileSystem = fileSystem;
options.StaticFileOptions.ServeUnknownFileTypes = false;
return options;
} }
} }
} }

View File

@ -3,6 +3,10 @@
<!-- For more information on using web.config transformation visit http://go.microsoft.com/fwlink/?LinkId=125889 --> <!-- For more information on using web.config transformation visit http://go.microsoft.com/fwlink/?LinkId=125889 -->
<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform"> <configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">
<appSettings>
<add key="CasHost" value="https://10.0.0.13:8443"
xdt:Transform="SetAttributes" xdt:Locator="Match(key)" />
</appSettings>
<!-- <!--
In the example below, the "SetAttributes" transform will change the value of In the example below, the "SetAttributes" transform will change the value of
"connectionString" to use "ReleaseSQLServer" only when the "Match" locator "connectionString" to use "ReleaseSQLServer" only when the "Match" locator

View File

@ -10,8 +10,8 @@
<!-- For more information on Entity Framework configuration, visit http://go.microsoft.com/fwlink/?LinkID=237468 --></configSections> <!-- For more information on Entity Framework configuration, visit http://go.microsoft.com/fwlink/?LinkID=237468 --></configSections>
<appSettings> <appSettings>
<add key="AppName" value="Owin WebApi Auth Test" /> <add key="AppName" value="Owin WebApi Auth Test" />
<add key="AppVersion" value="0.1.0" /> <add key="AppVersion" value="1.0.0" />
<add key="CasHost" value="https://10.0.0.13:8443" /> <add key="CasHost" value="https://localhost:8443" />
<add key="CasValidationPath" value="/cas/serviceValidate" /> <add key="CasValidationPath" value="/cas/serviceValidate" />
<add key="AccessTokenLifetimeHours" value="10" /> <add key="AccessTokenLifetimeHours" value="10" />
<!-- <!--

View File

@ -1,11 +1,13 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<title></title> <title>An Application - API Server</title>
<meta charset="utf-8" /> <meta charset="utf-8" />
</head> </head>
<body> <body>
<h1>Application</h1> <h1>An Application - API Server</h1>
<p>This is server part of the application, client not started or running elsewhere.</p>
<p>Please contact your administrator to point you the right direction.</p>
<script src="app.js" type="text/javascript"></script> <script src="app.js" type="text/javascript"></script>
</body> </body>
</html> </html>

23
README.md Normal file
View File

@ -0,0 +1,23 @@
# Owin WebApi Auth Test
ASP.NET OWIN WebAPI setup with CAS authorization and static file serving.
See [`Startup.cs`](./OwinWebApiTest/Startup.cs) and
[`Providers/CasAuthorizationServerProvider.cs`](./OwinWebApiTest/Providers/CasAuthorizationServerProvider.cs)
files for the most relevant stuff.
## Configuration
Options configurable in project's `Web.config`:
- `appSettings/CasHost`: URL of the CAS authentication server (required)
- `appSettings/CasValidationPath`: CAS token validation URL path
(required; for case another protocol version desired)
- `appSettings/AccessTokenLifetimeHours`: login expiration in hours (optional, default = 10)
- `appSettings/WebDirectory`: directory from which the client app is served (optional, default = 'Web')
- `appSettings/DocDirectory`: directory from which the documentation is served (optional, default = 'Doc')
- `appSettings/ServiceUser`: bypass ticket authentication, authorize this user (optional)
A [CAS](https://apereo.github.io/cas) server like
[cas-gradle-overlay-template](https://github.com/apereo/cas-gradle-overlay-template)
expected on `appSettings/CasHost` URL.