Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions Code/SimpleAuthentication.ExtraProviders/LinkedIn/UserInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using Newtonsoft.Json;

namespace SimpleAuthentication.ExtraProviders.LinkedIn
{
internal class UserInfo
{
[JsonProperty(PropertyName = "emailAddress")]
public string Email { get; set; }

[JsonProperty(PropertyName = "formattedName")]
public string Name { get; set; }

[JsonProperty(PropertyName = "id")]
public string Id { get; set; }

[JsonProperty(PropertyName = "pictureUrl")]
public string AvatarUrl { get; set; }
}
}
85 changes: 85 additions & 0 deletions Code/SimpleAuthentication.ExtraProviders/LinkedInProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Newtonsoft.Json;
using SimpleAuthentication.Core;
using SimpleAuthentication.Core.Providers.OAuth.V20;
using SimpleAuthentication.ExtraProviders.LinkedIn;

namespace SimpleAuthentication.ExtraProviders
{
public class LinkedInProvider : OAuth20Provider
{
public LinkedInProvider(ProviderParams providerParams)
: base(providerParams)
{
ScopeSeparator = " ";
}

#region OAuth20Provider Implementation

public override string Name
{
get { return "LinkedIn"; }
}

protected override IEnumerable<string> DefaultScopes
{
get { return new[] { "r_basicprofile", "r_emailaddress" }; }
}

protected override Uri AccessTokenUri
{
get { return new Uri("https://www.linkedin.com/uas/oauth2/accessToken"); }
}

public override async Task<RedirectToAuthenticateSettings> GetRedirectToAuthenticateSettingsAsync(
Uri callbackUrl)
{
if (callbackUrl == null)
{
throw new ArgumentNullException("callbackUrl");
}

var providerAuthenticationUrl = new Uri("https://www.linkedin.com/uas/oauth2/authorization");
return GetRedirectToAuthenticateSettings(callbackUrl, providerAuthenticationUrl);
}

protected override AccessToken MapAccessTokenContentToAccessToken(string content)
{
return MapAccessTokenContentToAccessTokenForSomeJson(content);
}

protected override Uri UserInformationUri(AccessToken accessToken)
{
var userInformationUrl = string.Format("https://api.linkedin.com/v1/people/~:(id,formatted-name,email-address,picture-url)?oauth2_access_token={0}&format=json",
accessToken.Token);
return new Uri(userInformationUrl);
}

protected override string UserAgent
{
get { return "SimpleAuthentication-App"; }
}

protected override UserInformation GetUserInformationFromContent(string content)
{
if (string.IsNullOrWhiteSpace(content))
{
throw new ArgumentNullException("content");
}

var userInfo = JsonConvert.DeserializeObject<UserInfo>(content);

return new UserInformation
{
Id = userInfo.Id,
Name = userInfo.Name,
Email = userInfo.Email,
Picture = userInfo.AvatarUrl,
};
}

#endregion
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,13 @@
<Compile Include="..\Shared Assembly Info\SharedAssemblyInfo.cs">
<Link>Properties\SharedAssemblyInfo.cs</Link>
</Compile>
<Compile Include="LinkedInProvider.cs" />
<Compile Include="Instagram\Data.cs" />
<Compile Include="GitHubProvider.cs" />
<Compile Include="GitHub\UserInfo.cs" />
<Compile Include="InstagramProvider.cs" />
<Compile Include="Instagram\UserInfo.cs" />
<Compile Include="LinkedIn\UserInfo.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Net.Http;
using System.Threading.Tasks;
using Shouldly;
using SimpleAuthentication.Core;
using SimpleAuthentication.ExtraProviders;
using Xunit;

namespace SimpleAuthentication.Tests.ExtraProviders
{
public class LinkedInProviderFacts
{
public class GetRedirectToAuthenticateSettingsAsyncFacts
{
[Fact]
public async Task
GivenARedirectUrl_GetRedirectToAuthenticateSettingsAsync_ReturnsARedirectToAuthenticateSettings()
{
// Arrange.
var providerParams = new ProviderParams("zdskjhf&*^65sdfh/.<>\\sdf",
"szdkjhg&^%178~/.,<>\\[]{}sdsf sd df s");
var provider = new LinkedInProvider(providerParams);
var callbackUri = new Uri("http://www.mysite.com/pew/pew?provider=linkedin");

// Act.
var result = await provider.GetRedirectToAuthenticateSettingsAsync(callbackUri);

// Assert.
result.State.ShouldNotBe(null);
result.RedirectUri.AbsoluteUri.ShouldBe(
string.Format(
"https://www.linkedin.com/uas/oauth2/authorization?client_id=zdskjhf%26%2A%5E65sdfh%2F.%3C%3E%5Csdf&redirect_uri=http%3A%2F%2Fwww.mysite.com%2Fpew%2Fpew%3Fprovider%3Dlinkedin&response_type=code&scope=r_basicprofile%20r_emailaddress&state={0}",
result.State));
}
}

public class AuthenticateClientAsyncFacts
{
[Fact]
public async Task GivenAValidResponse_AuthenticateClientAsync_ReturnsAnAuthenticatedClient()
{
// Arrange.
var providerParams = new ProviderParams("zdskjhf&*^65sdfh/.<>\\sdf",
"szdkjhg&^%178~/.,<>\\[]{}sdsf sd df s");
var provider = new LinkedInProvider(providerParams);
const string stateKey = "state";
const string state = "adyi#&(*,./,.!~` uhj97&^*&shdgf\\//////\\dsf";
var querystring = new Dictionary<string, string>
{
{stateKey, state},
{"code", "4/P7q7W91a-oMsCeLvIaQm6bTrgtp7"}
};
var redirectUrl = new Uri("http://www.mywebsite.com/go/here/please");
var accessTokenJson = File.ReadAllText("Sample Data\\LinkedIn-AccessToken-Content.json");
var accessTokenResponse = FakeHttpMessageHandler.GetStringHttpResponseMessage(accessTokenJson);
var userInformationJson = File.ReadAllText("Sample Data\\LinkedIn-UserInfoResult-Content.json");
var userInformationResponse = FakeHttpMessageHandler.GetStringHttpResponseMessage(userInformationJson);
HttpClientFactory.MessageHandler = new FakeHttpMessageHandler(
new Dictionary<string, HttpResponseMessage>
{
{"https://www.linkedin.com/uas/oauth2/accessToken", accessTokenResponse},
{"https://api.linkedin.com/v1/people/~:(id,formatted-name,email-address,picture-url)?oauth2_access_token=fb2e77d.47a0479900504cb3ab4a1f626d174d2d&format=json", userInformationResponse}
});

// Act.
var result = await provider.AuthenticateClientAsync(querystring, state, redirectUrl);

// Assert.
result.AccessToken.Token.ShouldBe("fb2e77d.47a0479900504cb3ab4a1f626d174d2d");
result.UserInformation.Email.ShouldBe("[email protected]");
result.UserInformation.Name.ShouldBe("Flint Fireforge");
result.UserInformation.UserName.ShouldBe(null);
result.UserInformation.Id.ShouldBe("1468815002");
result.UserInformation.Picture.ShouldBe("https://media.licdn.com/mpr/mprx/anonymousUser");
result.AccessToken.ExpiresOn.ShouldBeGreaterThan(DateTime.Now);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"expires_in": 5184000,
"access_token": "fb2e77d.47a0479900504cb3ab4a1f626d174d2d"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"emailAddress": "[email protected]",
"formattedName": "Flint Fireforge",
"id": "1468815002",
"pictureUrl": "https://media.licdn.com/mpr/mprx/anonymousUser"
}
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@
</ItemGroup>
<ItemGroup>
<Compile Include="ExtraProviders\GitHubProviderFacts.cs" />
<Compile Include="ExtraProviders\LinkedInProviderFacts.cs" />
<Compile Include="ExtraProviders\InstagramProviderFacts.cs" />
<Compile Include="FakeHttpMessageHandler.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
Expand All @@ -80,6 +81,9 @@
<Compile Include="WebSites\SimpleAuthenticationModuleFacts.cs" />
</ItemGroup>
<ItemGroup>
<None Include="Sample Data\LinkedIn-AccessToken-Content.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<Content Include="Sample Data\GitHub-AccessToken-Content.txt">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
Expand All @@ -89,6 +93,9 @@
<None Include="Sample Data\Instagram-UserInfoResult-Content.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Sample Data\LinkedIn-UserInfoResult-Content.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Sample Data\GitHub-UserInfoResult-Content.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
Expand Down
4 changes: 4 additions & 0 deletions Samples/SimpleAuthentication.Sample.NancyAuto/Bootstrapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,15 @@ protected override void ApplicationStartup(TinyIoCContainer container, IPipeline
var instagramProvider = new InstagramProvider(
new ProviderParams("76310ae10207427f878c23001dc10d2a",
"b43c02181f754debac0cf4e38b59fd7f"));
var linkedInProvider = new LinkedInProvider(
new ProviderParams("Insert-Public-Api-Key",
"Insert-Secret-Api-Key"));

var authenticationProviderFactory = new AuthenticationProviderFactory(new ConfigService());

authenticationProviderFactory.AddProvider(gitHubProvider);
authenticationProviderFactory.AddProvider(instagramProvider);
authenticationProviderFactory.AddProvider(linkedInProvider);
}

protected override void ConfigureRequestContainer(TinyIoCContainer container, NancyContext context)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@
<hr/>
<fieldset title="Extra Providers">
<legend>Extra Providers</legend>
<a href="/authenticate/github"><img src="/Content/github_32.png" alt="Login with your GitHub account."</a>
<a href="/authenticate/instagram"><img src="/Content/instagram_32.png" alt="Login with your Instagram account."</a>
<a href="/authenticate/github"><img src="/Content/github_32.png" alt="Login with your GitHub account."/></a>
<a href="/authenticate/instagram"><img src="/Content/instagram_32.png" alt="Login with your Instagram account."/></a>
<a href="/authenticate/linkedin"><img src="/Content/linkedin_32.png" alt="Login with your LinkedIn account."/></a>
</fieldset>

<hr/>
Expand Down