forked from webapibook/issuetracker
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
adds support for basic (client+server) and bearer tokens (only client…
… side)
- Loading branch information
Showing
18 changed files
with
679 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,6 +2,9 @@ | |
[Bb]in/ | ||
[Oo]bj/ | ||
|
||
# Secret Credentials Folder | ||
SecretCredentials/ | ||
|
||
# mstest test results | ||
TestResults | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
29 changes: 29 additions & 0 deletions
29
src/WebApiBook.IssueTrackerApi/Infrastructure/BasicAuthnClientHandler.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Net.Http; | ||
using System.Net.Http.Headers; | ||
using System.Threading.Tasks; | ||
using System.Web.Mvc; | ||
|
||
namespace WebApiBook.IssueTrackerApi.Infrastructure | ||
{ | ||
public class BasicAuthnClientHandler : DelegatingHandler | ||
{ | ||
private readonly Func<HttpRequestMessage, Task<BasicCredentials>> _prov; | ||
|
||
public BasicAuthnClientHandler(Func<HttpRequestMessage, Task<BasicCredentials>> prov) | ||
{ | ||
_prov = prov; | ||
} | ||
|
||
protected override async Task<HttpResponseMessage> | ||
SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken) | ||
{ | ||
var creds = await _prov(request); | ||
request.Headers.Authorization = new AuthenticationHeaderValue( | ||
"Basic", creds.ToCredentialString()); | ||
return await base.SendAsync(request, cancellationToken); | ||
} | ||
} | ||
} |
64 changes: 64 additions & 0 deletions
64
src/WebApiBook.IssueTrackerApi/Infrastructure/BasicAuthnServerHandler.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
using System; | ||
using System.Net; | ||
using System.Net.Http; | ||
using System.Net.Http.Headers; | ||
using System.Security.Claims; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
using System.Web; | ||
|
||
namespace WebApiBook.IssueTrackerApi.Infrastructure | ||
{ | ||
public class BasicAuthnServerHandler : DelegatingHandler | ||
{ | ||
private readonly string _realm; | ||
private readonly Func<BasicCredentials, Task<bool>> _val; | ||
private readonly Func<HttpRequestMessage, Task<BasicCredentials>> _prov; | ||
|
||
public BasicAuthnServerHandler(string realm, Func<BasicCredentials, Task<bool>> val) | ||
{ | ||
_realm = realm; | ||
_val = val; | ||
} | ||
|
||
protected override async Task<HttpResponseMessage> | ||
SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken) | ||
{ | ||
BasicCredentials creds; | ||
if (request.Headers.Authorization != null | ||
&& request.Headers.Authorization.Scheme.Equals("Basic", StringComparison.InvariantCultureIgnoreCase) | ||
&& (creds = BasicCredentials.TryParse(request.Headers.Authorization.Parameter)) != null) | ||
{ | ||
if(!await _val(creds)) | ||
{ | ||
return UnAuthorizedResponseMessage(); | ||
} | ||
SetPrincipal(request, creds); | ||
return await base.SendAsync(request, cancellationToken); | ||
} | ||
var resp = new HttpResponseMessage(HttpStatusCode.Unauthorized); | ||
resp.Headers.WwwAuthenticate.Add( | ||
new AuthenticationHeaderValue("Basic", string.Format("realm={0}", _realm))); | ||
return resp; | ||
} | ||
|
||
private static void SetPrincipal(HttpRequestMessage request, BasicCredentials creds) | ||
{ | ||
var princ = new ClaimsPrincipal(new ClaimsIdentity( | ||
new Claim[] {new Claim(ClaimTypes.NameIdentifier, creds.Username),} | ||
)); | ||
Thread.CurrentPrincipal = princ; | ||
if(HttpContext.Current != null) | ||
{ | ||
HttpContext.Current.User = princ; | ||
} | ||
} | ||
|
||
private HttpResponseMessage UnAuthorizedResponseMessage() | ||
{ | ||
var resp = new HttpResponseMessage(HttpStatusCode.Unauthorized); | ||
resp.Headers.WwwAuthenticate.Add(new AuthenticationHeaderValue("Basic",string.Format("realm= {0}",_realm))); | ||
return resp; | ||
} | ||
} | ||
} |
42 changes: 42 additions & 0 deletions
42
src/WebApiBook.IssueTrackerApi/Infrastructure/BasicCredentials.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
using System; | ||
using System.Text; | ||
|
||
namespace WebApiBook.IssueTrackerApi.Infrastructure | ||
{ | ||
public class BasicCredentials | ||
{ | ||
public string Username { get; private set; } | ||
public string Password { get; private set; } | ||
|
||
public BasicCredentials(string username, string password) | ||
{ | ||
Username = username; | ||
Password = password; | ||
} | ||
|
||
public static BasicCredentials TryParse(string credentials) | ||
{ | ||
string pair; | ||
try | ||
{ | ||
pair = Encoding.ASCII.GetString(Convert.FromBase64String(credentials)); | ||
} | ||
catch (FormatException) | ||
{ | ||
return null; | ||
} | ||
catch (ArgumentException) | ||
{ | ||
return null; | ||
} | ||
var ix = pair.IndexOf(':'); | ||
return ix == -1 ? null : new BasicCredentials(pair.Substring(0, ix), pair.Substring(ix + 1)); | ||
} | ||
|
||
public string ToCredentialString() | ||
{ | ||
return Convert.ToBase64String( | ||
Encoding.ASCII.GetBytes(Username + ':' + Password)); | ||
} | ||
} | ||
} |
27 changes: 27 additions & 0 deletions
27
src/WebApiBook.IssueTrackerApi/Infrastructure/BearerAuthnClientHandler.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Net.Http; | ||
using System.Net.Http.Headers; | ||
using System.Threading.Tasks; | ||
using System.Web; | ||
|
||
namespace WebApiBook.IssueTrackerApi.Infrastructure | ||
{ | ||
public class BearerAuthnClientHandler : DelegatingHandler | ||
{ | ||
private readonly Func<HttpRequestMessage, Task<BearerToken>> _prov; | ||
|
||
public BearerAuthnClientHandler(Func<HttpRequestMessage, Task<BearerToken>> prov) | ||
{ | ||
_prov = prov; | ||
} | ||
|
||
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken) | ||
{ | ||
var token = await _prov(request); | ||
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer",token.Value); | ||
return await base.SendAsync(request, cancellationToken); | ||
} | ||
} | ||
} |
12 changes: 12 additions & 0 deletions
12
src/WebApiBook.IssueTrackerApi/Infrastructure/BearerToken.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
namespace WebApiBook.IssueTrackerApi.Infrastructure | ||
{ | ||
public class BearerToken | ||
{ | ||
public string Value { get; private set; } | ||
|
||
public BearerToken(string value) | ||
{ | ||
Value = value; | ||
} | ||
} | ||
} |
78 changes: 78 additions & 0 deletions
78
src/WebApiBook.IssueTrackerApi/Infrastructure/GitHubTokenClient.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Net; | ||
using System.Net.Http; | ||
using System.Threading.Tasks; | ||
using System.Web; | ||
|
||
namespace WebApiBook.IssueTrackerApi.Infrastructure | ||
{ | ||
public class GitHubTokenClient | ||
{ | ||
private readonly string _clientId; | ||
private readonly string _clientSecret; | ||
|
||
public GitHubTokenClient(string clientId, string clientSecret) | ||
{ | ||
_clientId = clientId; | ||
_clientSecret = clientSecret; | ||
} | ||
|
||
public async Task<GitHubToken> GetBearerTokenAsync(BasicCredentials creds, string[] scopes) | ||
{ | ||
using(var client = new HttpClient(new BasicAuthnClientHandler(req => Task.FromResult(creds)) | ||
{ | ||
InnerHandler = new HttpClientHandler() | ||
})) | ||
{ | ||
var resp = await client.PostAsJsonAsync("https://api.github.com/authorizations", new TokenRequestModel | ||
{ | ||
scopes = scopes, | ||
client_id = _clientId, | ||
client_secret = _clientSecret | ||
}); | ||
resp.EnsureSuccessStatusCode(); | ||
var respModel = await resp.Content.ReadAsAsync<TokenResponseModel>(); | ||
return new GitHubToken(respModel.token, respModel.url); | ||
} | ||
} | ||
|
||
public async Task<HttpStatusCode> DeleteBearerTokenAsync(BasicCredentials creds, string url) | ||
{ | ||
using (var client = new HttpClient(new BasicAuthnClientHandler(req => Task.FromResult(creds)) | ||
{ | ||
InnerHandler = new HttpClientHandler() | ||
})) | ||
{ | ||
return (await client.DeleteAsync(url)).StatusCode; | ||
} | ||
} | ||
} | ||
|
||
public class GitHubToken | ||
{ | ||
public string Value { get; private set; } | ||
public string Uri { get; private set; } | ||
|
||
public GitHubToken(string value, string uri) | ||
{ | ||
Value = value; | ||
Uri = uri; | ||
} | ||
} | ||
|
||
public class TokenRequestModel | ||
{ | ||
public string[] scopes { get; set; } | ||
public string client_id { get; set; } | ||
public string client_secret { get; set; } | ||
} | ||
|
||
public class TokenResponseModel | ||
{ | ||
public int id { get; set; } | ||
public string url { get; set; } | ||
public string token { get; set; } | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
31 changes: 31 additions & 0 deletions
31
test/WebApiBook.IssueTrackerApi.IntegrationTests/GitHub/BasicAuthnClientHandlerTests.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
using System.Net; | ||
using System.Net.Http; | ||
using System.Threading.Tasks; | ||
using WebApiBook.IssueTrackerApi.Infrastructure; | ||
using WebApiBook.IssueTrackerApi.IntegrationTests.GitHub.SecretCredentials; | ||
using Xunit; | ||
|
||
namespace WebApiBook.IssueTrackerApi.IntegrationTests.GitHub | ||
{ | ||
public class BasicAuthnClientHandlerTests | ||
{ | ||
public class TheBasicAuthnClientHandler | ||
{ | ||
[Fact] | ||
public async Task ShouldAuthenticateOutboundGitHubRequests() | ||
{ | ||
const string username = TestSecretCredentials.UserName; | ||
const string password = TestSecretCredentials.Password; | ||
using(var client = new HttpClient( | ||
new BasicAuthnClientHandler(req => Task.FromResult(new BasicCredentials(username, password))) | ||
{ | ||
InnerHandler = new HttpClientHandler() | ||
})) | ||
{ | ||
var resp = await client.GetAsync("https://api.github.com/orgs/webapibook/issues"); | ||
Assert.Equal(HttpStatusCode.OK, resp.StatusCode); | ||
} | ||
} | ||
} | ||
} | ||
} |
43 changes: 43 additions & 0 deletions
43
test/WebApiBook.IssueTrackerApi.IntegrationTests/GitHub/GitHubTokenClientTests.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
using System.Net; | ||
using System.Net.Http; | ||
using System.Threading.Tasks; | ||
using WebApiBook.IssueTrackerApi.Infrastructure; | ||
using WebApiBook.IssueTrackerApi.IntegrationTests.GitHub.SecretCredentials; | ||
using Xunit; | ||
|
||
namespace WebApiBook.IssueTrackerApi.IntegrationTests.GitHub | ||
{ | ||
public class GitHubTokenClientTests | ||
{ | ||
public class TheGitHubTokenClient | ||
{ | ||
[Fact] | ||
public async Task ShouldCreateOAuthTokens() | ||
{ | ||
const string username = TestSecretCredentials.UserName; | ||
const string password = TestSecretCredentials.Password; | ||
const string clientId = TestSecretCredentials.ClientId; | ||
const string clientSecret = TestSecretCredentials.ClientSecret; | ||
var tokenClient = new GitHubTokenClient(clientId, clientSecret); | ||
var token = await tokenClient.GetBearerTokenAsync(new BasicCredentials(username, password), new string[] {"repo"}); | ||
try | ||
{ | ||
using ( | ||
var client = | ||
new HttpClient( | ||
new BearerAuthnClientHandler(req => Task.FromResult(new BearerToken(token.Value))) | ||
{ | ||
InnerHandler = new HttpClientHandler() | ||
})) | ||
{ | ||
var httpResp = await client.GetAsync("https://api.github.com/orgs/webapibook/issues"); | ||
Assert.Equal(HttpStatusCode.OK, httpResp.StatusCode); | ||
} | ||
}finally | ||
{ | ||
tokenClient.DeleteBearerTokenAsync(new BasicCredentials(username, password), token.Uri); | ||
} | ||
} | ||
} | ||
} | ||
} |
Oops, something went wrong.