Skip to content
Open
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 RedirectToAuthenticateSettings GetRedirectToAuthenticateSettings(
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,82 @@
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 WorldDomination.Net.Http;
using Xunit;

namespace SimpleAuthentication.Tests.ExtraProviders
{
public class LinkedInProviderFacts
{
public class GetRedirectToAuthenticateSettingsAsyncFacts
{
[Fact]
public async Task
GivenARedirectUrl_GetRedirectToAuthenticateSettings_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 = provider.GetRedirectToAuthenticateSettings(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 @@ -109,6 +109,7 @@
<Compile Include="ConfigServiceFacts.cs" />
<Compile Include="ExtraProviders\GitHubProviderFacts.cs" />
<Compile Include="ExtraProviders\InstagramProviderFacts.cs" />
<Compile Include="ExtraProviders\LinkedInProviderFacts.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Providers\FacebookProviderFacts.cs" />
<Compile Include="Providers\FakeProviderFacts.cs" />
Expand Down Expand Up @@ -138,6 +139,12 @@
<None Include="Sample Data\Instagram-UserInfoResult-Content.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="Sample Data\LinkedIn-AccessToken-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
3 changes: 2 additions & 1 deletion Samples/SimpleAuthentication.Sample.Nancy/Bootstrapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ protected override void ApplicationStartup(TinyIoCContainer container, IPipeline
var additionalProviderTypes = new List<Type>
{
typeof (GitHubProvider),
typeof (InstagramProvider)
typeof (InstagramProvider),
typeof (LinkedInProvider)
};
var providerScanner = new ProviderScanner(additionalProviderTypes);
container.Register<IProviderScanner, ProviderScanner>(providerScanner);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
<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/linkedin"><img src="/Content/linkedin_32.png" alt="Login with your LinkedIn account."/></a>
</fieldset>

<hr/>
Expand Down
1 change: 1 addition & 0 deletions Samples/SimpleAuthentication.Sample.Nancy/Web.config
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
<add key="sa.WindowsLive" value="key:00000000400ED488;secret:OAc-A5hoXE0eOolc6aczF2xvnq5sLfRr" />
<add key="sa.GitHub" value="key:9403c7920a82689969d1;secret:e5b3807c7c97466634bdf21ddf9a179485f1fe60" />
<add key="sa.Instagram" value="key:76310ae10207427f878c23001dc10d2a;secret:b43c02181f754debac0cf4e38b59fd7f" />
<add key="sa.LinkedIn" value="key:Insert-key-here;secret:Insert-secret-here" />
<add key="webPages:Enabled" value="false" /></appSettings>

<system.web>
Expand Down