diff --git a/src/Orchard.Specs/App.Config b/src/Orchard.Specs/App.Config index 65befcd64..a09cc094a 100644 --- a/src/Orchard.Specs/App.Config +++ b/src/Orchard.Specs/App.Config @@ -27,6 +27,10 @@ + + + + \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.OpenId/Constants/ActiveDirectoryFederationServices.cs b/src/Orchard.Web/Modules/Orchard.OpenId/Constants/ActiveDirectoryFederationServices.cs new file mode 100644 index 000000000..64eb14192 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.OpenId/Constants/ActiveDirectoryFederationServices.cs @@ -0,0 +1,7 @@ +namespace Orchard.OpenId.Constants { + public class ActiveDirectoryFederationServices { + public const string DefaultClientId = "xXxXxXxX-xXxX-xXxX-xXxX-xXxXxXxXxXxX"; + public const string DefaultMetadataAddress = "https://your-adfs-domain/adfs/.well-known/openid-configuration"; + public const string DefaultPostLogoutRedirectUri = "https://your-website/"; + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.OpenId/Constants/AzureActiveDirectory.cs b/src/Orchard.Web/Modules/Orchard.OpenId/Constants/AzureActiveDirectory.cs new file mode 100644 index 000000000..171ce8ad9 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.OpenId/Constants/AzureActiveDirectory.cs @@ -0,0 +1,5 @@ +namespace Orchard.OpenId.Constants { + public class AzureActiveDirectory { + public const string ObjectIdentifierKey = "http://schemas.microsoft.com/identity/claims/objectidentifier"; + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.OpenId/Constants/Facebook.cs b/src/Orchard.Web/Modules/Orchard.OpenId/Constants/Facebook.cs new file mode 100644 index 000000000..3504b78eb --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.OpenId/Constants/Facebook.cs @@ -0,0 +1,6 @@ +namespace Orchard.OpenId.Constants { + public class Facebook { + public const string DefaultAppId = "0000000000000000"; + public const string DefaultAppSecret = "xXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxX"; + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.OpenId/Constants/General.cs b/src/Orchard.Web/Modules/Orchard.OpenId/Constants/General.cs new file mode 100644 index 000000000..15dd6f6b8 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.OpenId/Constants/General.cs @@ -0,0 +1,9 @@ +namespace Orchard.OpenId.Constants { + public class General { + public const string AuthenticationErrorUrl = "~/Authentication/Error"; + public const string LogonCallbackUrl = "~/Users/Account/LogonCallback"; + public const string OpenIdOwinMiddlewarePriority = "10"; + public const string LocalIssuer = "LOCAL AUTHORITY"; + public const string FormsIssuer = "Forms"; + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.OpenId/Constants/Google.cs b/src/Orchard.Web/Modules/Orchard.OpenId/Constants/Google.cs new file mode 100644 index 000000000..55eae5a74 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.OpenId/Constants/Google.cs @@ -0,0 +1,6 @@ +namespace Orchard.OpenId.Constants { + public class Google { + public const string DefaultClientId = "000-000.apps.googleusercontent.com"; + public const string DefaultClientSecret = "x-xXxXxXxXxXxXxXxXxXxXxX"; + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.OpenId/Constants/Twitter.cs b/src/Orchard.Web/Modules/Orchard.OpenId/Constants/Twitter.cs new file mode 100644 index 000000000..8127f3720 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.OpenId/Constants/Twitter.cs @@ -0,0 +1,14 @@ +namespace Orchard.OpenId.Constants { + public class Twitter { + public const string DefaultConsumerKey = "xXxXxXxXxXxXxXxXxXxXxXxXx"; + public const string DefaultConsumerSecret = "xXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxXxX"; + + // Certificate Subject Key Identifier + public const string DefaultVeriSignClass3SecureServerCA_G2 = "A5EF0B11CEC04103A34A659048B21CE0572D7D47"; + public const string DefaultVeriSignClass3SecureServerCA_G3 = "0D445C165344C1827E1D20AB25F40163D8BE79A5"; + public const string DefaultVeriSignClass3PublicPrimaryCA_G5 = "7FD365A7C2DDECBBF03009F34339FA02AF333133"; + public const string DefaultSymantecClass3SecureServerCA_G4 = "39A55D933676616E73A761DFA16A7E59CDE66FAD"; + public const string DefaultDigiCertSHA2HighAssuranceServerCA = "5168FF90AF0207753CCCD9656462A212B859723B"; + public const string DefaultDigiCertHighAssuranceEVRootCA = "B13EC36903F8BF4701D498261A0802EF63642BC3"; + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.OpenId/Controllers/AccountController.cs b/src/Orchard.Web/Modules/Orchard.OpenId/Controllers/AccountController.cs new file mode 100644 index 000000000..01f64c7c6 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.OpenId/Controllers/AccountController.cs @@ -0,0 +1,162 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Web; +using System.Web.Mvc; +using Microsoft.Owin.Security; +using Microsoft.Owin.Security.Cookies; +using Microsoft.Owin.Security.OpenIdConnect; +using Orchard.Environment.Extensions; +using Orchard.Localization; +using Orchard.Logging; +using Orchard.Mvc.Extensions; +using Orchard.OpenId.Services; +using Orchard.Security; +using Orchard.Themes; +using Orchard.Users.Events; + +namespace Orchard.OpenId.Controllers +{ + [Themed] + [OrchardFeature("Orchard.OpenId")] + public class AccountController : Controller { + private readonly IEnumerable _openIdProviders; + private readonly IAuthenticationService _authenticationService; + private readonly IMembershipService _membershipService; + private readonly IOrchardServices _orchardServices; + private readonly IUserEventHandler _userEventHandler; + + public AccountController( + IEnumerable openIdProviders, + IAuthenticationService authenticationService, + IMembershipService membershipService, + IOrchardServices orchardServices, + IUserEventHandler userEventHandler) { + + _openIdProviders = openIdProviders; + _authenticationService = authenticationService; + _membershipService = membershipService; + _orchardServices = orchardServices; + _userEventHandler = userEventHandler; + + Logger = NullLogger.Instance; + T = NullLocalizer.Instance; + } + + public ILogger Logger { get; set; } + public Localizer T { get; set; } + + [HttpGet] + public ActionResult LogOn() { + if (Request.IsAuthenticated) { + return Redirect(Url.Content("~/")); + } + + return View(_openIdProviders); + } + + [HttpPost] + [AlwaysAccessible] + [ValidateInput(false)] + [SuppressMessage("Microsoft.Design", "CA1054:UriParametersShouldNotBeStrings", Justification = "Needs to take same parameter type as Controller.Redirect()")] + public ActionResult LogOn(string userNameOrEmail, string password, string returnUrl, bool rememberMe = false) { + _userEventHandler.LoggingIn(userNameOrEmail, password); + + var user = ValidateLogOn(userNameOrEmail, password); + if (!ModelState.IsValid) { + return View(_openIdProviders); + } + + var membershipSettings = _membershipService.GetSettings(); + if (user != null && + membershipSettings.EnableCustomPasswordPolicy && + membershipSettings.EnablePasswordExpiration && + _membershipService.PasswordIsExpired(user, membershipSettings.PasswordExpirationTimeInDays)) { + return RedirectToAction("ChangeExpiredPassword", new { username = user.UserName }); + } + + _authenticationService.SignIn(user, rememberMe); + _userEventHandler.LoggedIn(user); + + return this.RedirectLocal(returnUrl); + } + + public void Challenge(string openIdProvider) { + _userEventHandler.LoggingIn(openIdProvider, String.Empty); + + if (String.IsNullOrWhiteSpace(openIdProvider)) + openIdProvider = OpenIdConnectAuthenticationDefaults.AuthenticationType; + + if (Request.IsAuthenticated) { + Redirect(Url.Content("~/")); + return; + } + + var redirectUri = Url.Content(String.Concat(Constants.General.LogonCallbackUrl)); + + HttpContext.GetOwinContext().Authentication.Challenge(new AuthenticationProperties { RedirectUri = redirectUri }, openIdProvider); + } + + public RedirectResult LogOff(string openIdProvider) { + if (String.IsNullOrWhiteSpace(openIdProvider)) + openIdProvider = OpenIdConnectAuthenticationDefaults.AuthenticationType; + + HttpContext.GetOwinContext().Authentication.SignOut(openIdProvider, CookieAuthenticationDefaults.AuthenticationType); + _authenticationService.SignOut(); + + var loggedUser = _authenticationService.GetAuthenticatedUser(); + if (loggedUser != null) { + _userEventHandler.LoggedOut(loggedUser); + } + + return Redirect(Url.Content("~/")); + } + + public RedirectResult LogonCallback() { + var user = _authenticationService.GetAuthenticatedUser(); + _userEventHandler.LoggedIn(user); + + return Redirect(Url.Content("~/")); + } + + public ActionResult AccessDenied() { + var returnUrl = Request.QueryString["ReturnUrl"]; + var currentUser = _authenticationService.GetAuthenticatedUser(); + + if (currentUser == null) { + return RedirectToAction("Logon"); + } + + _userEventHandler.AccessDenied(currentUser); + + return View(); + } + + public ActionResult Error() { + return View(); + } + + private IUser ValidateLogOn(string userNameOrEmail, string password) { + bool validate = true; + + if (String.IsNullOrEmpty(userNameOrEmail)) { + ModelState.AddModelError("userNameOrEmail", T("You must specify a username or e-mail.")); + validate = false; + } + if (String.IsNullOrEmpty(password)) { + ModelState.AddModelError("password", T("You must specify a password.")); + validate = false; + } + + if (!validate) + return null; + + var user = _membershipService.ValidateUser(userNameOrEmail, password); + if (user == null) { + ModelState.AddModelError("password", T("The username or e-mail or password provided is incorrect.")); + } + + return user; + } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.OpenId/Handlers/ActiveDirectoryFederationServicesSettingsPartHandler.cs b/src/Orchard.Web/Modules/Orchard.OpenId/Handlers/ActiveDirectoryFederationServicesSettingsPartHandler.cs new file mode 100644 index 000000000..f31d39e53 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.OpenId/Handlers/ActiveDirectoryFederationServicesSettingsPartHandler.cs @@ -0,0 +1,17 @@ +using Orchard.ContentManagement.Handlers; +using Orchard.Environment.Extensions; +using Orchard.Localization; +using Orchard.OpenId.Models; + +namespace Orchard.OpenId.Handlers { + [OrchardFeature("Orchard.OpenId.ActiveDirectoryFederationServices")] + public class ActiveDirectoryFederationServicesSettingsPartHandler : ContentHandler { + public Localizer T { get; set; } + + public ActiveDirectoryFederationServicesSettingsPartHandler() { + T = NullLocalizer.Instance; + Filters.Add(new ActivatingFilter("Site")); + Filters.Add(new TemplateFilterForPart("ActiveDirectoryFederationServicesSettings", "Parts.ActiveDirectoryFederationServicesSettings", "OpenId")); + } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.OpenId/Handlers/AzureActiveDirectorySettingsPartHandler.cs b/src/Orchard.Web/Modules/Orchard.OpenId/Handlers/AzureActiveDirectorySettingsPartHandler.cs new file mode 100644 index 000000000..caad9960d --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.OpenId/Handlers/AzureActiveDirectorySettingsPartHandler.cs @@ -0,0 +1,18 @@ +using Orchard.OpenId.Models; +using Orchard.ContentManagement; +using Orchard.ContentManagement.Handlers; +using Orchard.Localization; +using Orchard.Environment.Extensions; + +namespace Orchard.OpenId.Handlers { + [OrchardFeature("Orchard.OpenId.AzureActiveDirectory")] + public class AzureActiveDirectorySettingsPartHandler : ContentHandler { + public Localizer T { get; set; } + + public AzureActiveDirectorySettingsPartHandler() { + T = NullLocalizer.Instance; + Filters.Add(new ActivatingFilter("Site")); + Filters.Add(new TemplateFilterForPart("AzureActiveDirectorySettings", "Parts.AzureActiveDirectorySettings", "OpenId")); + } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.OpenId/Handlers/FacebookSettingsPartHandler.cs b/src/Orchard.Web/Modules/Orchard.OpenId/Handlers/FacebookSettingsPartHandler.cs new file mode 100644 index 000000000..f4eb34969 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.OpenId/Handlers/FacebookSettingsPartHandler.cs @@ -0,0 +1,17 @@ +using Orchard.ContentManagement.Handlers; +using Orchard.Environment.Extensions; +using Orchard.Localization; +using Orchard.OpenId.Models; + +namespace Orchard.OpenId.Handlers { + [OrchardFeature("Orchard.OpenId.Facebook")] + public class FacebookSettingsPartHandler : ContentHandler { + public Localizer T { get; set; } + + public FacebookSettingsPartHandler() { + T = NullLocalizer.Instance; + Filters.Add(new ActivatingFilter("Site")); + Filters.Add(new TemplateFilterForPart("FacebookSettings", "Parts.FacebookSettings", "OpenId")); + } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.OpenId/Handlers/GoogleSettingsPartHandler.cs b/src/Orchard.Web/Modules/Orchard.OpenId/Handlers/GoogleSettingsPartHandler.cs new file mode 100644 index 000000000..7d8c7ebb2 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.OpenId/Handlers/GoogleSettingsPartHandler.cs @@ -0,0 +1,17 @@ +using Orchard.ContentManagement.Handlers; +using Orchard.Environment.Extensions; +using Orchard.Localization; +using Orchard.OpenId.Models; + +namespace Orchard.OpenId.Handlers { + [OrchardFeature("Orchard.OpenId.Google")] + public class GoogleSettingsPartHandler : ContentHandler { + public Localizer T { get; set; } + + public GoogleSettingsPartHandler() { + T = NullLocalizer.Instance; + Filters.Add(new ActivatingFilter("Site")); + Filters.Add(new TemplateFilterForPart("GoogleSettings", "Parts.GoogleSettings", "OpenId")); + } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.OpenId/Handlers/OpenIdSettingsPartHandler.cs b/src/Orchard.Web/Modules/Orchard.OpenId/Handlers/OpenIdSettingsPartHandler.cs new file mode 100644 index 000000000..4eb3fa18e --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.OpenId/Handlers/OpenIdSettingsPartHandler.cs @@ -0,0 +1,20 @@ +using Orchard.ContentManagement; +using Orchard.ContentManagement.Handlers; +using Orchard.Environment.Extensions; +using Orchard.Localization; + +namespace Orchard.OpenId.Handlers { + [OrchardFeature("Orchard.OpenId")] + public class OpenIdSettingsPartHandler : ContentHandler { + public Localizer T { get; set; } + + protected override void GetItemMetadata(GetContentItemMetadataContext context) { + if (context.ContentItem.ContentType != "Site") { + return; + } + + base.GetItemMetadata(context); + context.Metadata.EditorGroupInfo.Add(new GroupInfo(T("Open Id")) { Id = "OpenId" }); + } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.OpenId/Handlers/TwitterSettingsPartHandler.cs b/src/Orchard.Web/Modules/Orchard.OpenId/Handlers/TwitterSettingsPartHandler.cs new file mode 100644 index 000000000..952b9088e --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.OpenId/Handlers/TwitterSettingsPartHandler.cs @@ -0,0 +1,17 @@ +using Orchard.ContentManagement.Handlers; +using Orchard.Environment.Extensions; +using Orchard.Localization; +using Orchard.OpenId.Models; + +namespace Orchard.OpenId.Handlers { + [OrchardFeature("Orchard.OpenId.Twitter")] + public class TwitterSettingsPartHandler : ContentHandler { + public Localizer T { get; set; } + + public TwitterSettingsPartHandler() { + T = NullLocalizer.Instance; + Filters.Add(new ActivatingFilter("Site")); + Filters.Add(new TemplateFilterForPart("TwitterSettings", "Parts.TwitterSettings", "OpenId")); + } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.OpenId/Models/ActiveDirectoryFederationServicesSettingsPart.cs b/src/Orchard.Web/Modules/Orchard.OpenId/Models/ActiveDirectoryFederationServicesSettingsPart.cs new file mode 100644 index 000000000..9897ed049 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.OpenId/Models/ActiveDirectoryFederationServicesSettingsPart.cs @@ -0,0 +1,39 @@ +using System; +using Orchard.ContentManagement; +using Orchard.Environment.Extensions; + +namespace Orchard.OpenId.Models { + [OrchardFeature("Orchard.OpenId.ActiveDirectoryFederationServices")] + public class ActiveDirectoryFederationServicesSettingsPart : ContentPart { + + public string ClientId { + get { return this.Retrieve(x => x.ClientId, () => Constants.ActiveDirectoryFederationServices.DefaultClientId); } + set { this.Store(x => x.ClientId, value); } + } + + public string MetadataAddress { + get { return this.Retrieve(x => x.MetadataAddress, () => Constants.ActiveDirectoryFederationServices.DefaultMetadataAddress); } + set { this.Store(x => x.MetadataAddress, value); } + } + + public string PostLogoutRedirectUri { + get { return this.Retrieve(x => x.PostLogoutRedirectUri); } + set { this.Store(x => x.PostLogoutRedirectUri, value); } + } + + public bool IsValid { + get { + if (String.IsNullOrWhiteSpace(ClientId) || + String.CompareOrdinal(ClientId, Constants.ActiveDirectoryFederationServices.DefaultClientId) == 0 || + String.IsNullOrWhiteSpace(MetadataAddress) || + String.CompareOrdinal(MetadataAddress, Constants.ActiveDirectoryFederationServices.DefaultMetadataAddress) == 0 || + String.IsNullOrWhiteSpace(PostLogoutRedirectUri)) { + + return false; + } + + return true; + } + } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.OpenId/Models/AzureActiveDirectorySettingsPart.cs b/src/Orchard.Web/Modules/Orchard.OpenId/Models/AzureActiveDirectorySettingsPart.cs new file mode 100644 index 000000000..e332bb554 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.OpenId/Models/AzureActiveDirectorySettingsPart.cs @@ -0,0 +1,109 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Orchard.ContentManagement; +using Orchard.Environment.Extensions; +using SysEnvironment = System.Environment; + +namespace Orchard.OpenId.Models { + [OrchardFeature("Orchard.OpenId.AzureActiveDirectory")] + public class AzureActiveDirectorySettingsPart : ContentPart { + private const char ServiceResourceIdsSeprator = '='; + private const string ServiceResourceIdDefaultKey = "default"; + + public string Tenant { + get { return this.Retrieve(x => x.Tenant); } + set { this.Store(x => x.Tenant, value); } + } + + public string ADInstance { + get { return this.Retrieve(x => x.ADInstance, () => "https://login.microsoftonline.com/{0}"); } + set { this.Store(x => x.ADInstance, value); } + } + + public string ClientId { + get { return this.Retrieve(x => x.ClientId); } + set { this.Store(x => x.ClientId, value); } + } + + public string AppName { + get { return this.Retrieve(x => x.AppName); } + set { this.Store(x => x.AppName, value); } + } + + public string LogoutRedirectUri { + get { return this.Retrieve(x => x.LogoutRedirectUri); } + set { this.Store(x => x.LogoutRedirectUri, value); } + } + + public bool BearerAuthEnabled { + get { return this.Retrieve(x => x.BearerAuthEnabled); } + set { this.Store(x => x.BearerAuthEnabled, value); } + } + + public bool SSLEnabled { + get { return this.Retrieve(x => x.SSLEnabled); } + set { this.Store(x => x.SSLEnabled, value); } + } + + public bool AzureWebSiteProtectionEnabled { + get { return this.Retrieve(x => x.AzureWebSiteProtectionEnabled); } + set { this.Store(x => x.AzureWebSiteProtectionEnabled, value); } + } + + public string GraphApiUrl { + get { return this.Retrieve(x => x.GraphApiUrl, () => "https://graph.windows.net"); } + set { this.Store(x => x.GraphApiUrl, value); } + } + + public bool UseAzureGraphApi { + get { return this.Retrieve(x => x.UseAzureGraphApi); } + set { this.Store(x => x.UseAzureGraphApi, value); } + } + + public string ServiceResourceID { + get { return this.Retrieve(x => x.ServiceResourceID); } + set { this.Store(x => x.ServiceResourceID, value); } + } + + public string AppKey { + get { return this.Retrieve(x => x.AppKey); } + set { this.Store(x => x.AppKey, value); } + } + + public bool IsValid { + get { + if (String.IsNullOrWhiteSpace(Tenant) || + String.IsNullOrWhiteSpace(ClientId) || + String.IsNullOrWhiteSpace(LogoutRedirectUri) || + String.IsNullOrWhiteSpace(ServiceResourceID) || + String.IsNullOrWhiteSpace(AppKey)) { + + return false; + } + + return true; + } + } + + public Dictionary ServiceResourceIDs { + get { + return this + .Retrieve(x => x.ServiceResourceID) + .Split(SysEnvironment.NewLine.ToCharArray(), StringSplitOptions.RemoveEmptyEntries) + .ToDictionary( + resourceId => { + return resourceId.Contains(ServiceResourceIdsSeprator) ? + resourceId.Split(ServiceResourceIdsSeprator)[0] : + ServiceResourceIdDefaultKey; + }, + resourceId => { + return resourceId.Contains(ServiceResourceIdsSeprator) ? + resourceId.Split(ServiceResourceIdsSeprator)[1] : + resourceId; + } + ); + } + } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.OpenId/Models/FacebookSettingsPart.cs b/src/Orchard.Web/Modules/Orchard.OpenId/Models/FacebookSettingsPart.cs new file mode 100644 index 000000000..f4f84b155 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.OpenId/Models/FacebookSettingsPart.cs @@ -0,0 +1,33 @@ +using System; +using Orchard.ContentManagement; +using Orchard.Environment.Extensions; + +namespace Orchard.OpenId.Models { + [OrchardFeature("Orchard.OpenId.Facebook")] + public class FacebookSettingsPart : ContentPart { + + public string AppId { + get { return this.Retrieve(x => x.AppId); } + set { this.Store(x => x.AppId, value); } + } + + public string AppSecret { + get { return this.Retrieve(x => x.AppSecret); } + set { this.Store(x => x.AppSecret, value); } + } + + public bool IsValid { + get { + if (String.IsNullOrWhiteSpace(AppId) || + String.CompareOrdinal(AppId, Constants.Facebook.DefaultAppId) == 0 || + String.IsNullOrWhiteSpace(AppSecret) || + String.CompareOrdinal(AppSecret, Constants.Facebook.DefaultAppSecret) == 0) { + + return false; + } + + return true; + } + } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.OpenId/Models/GoogleSettingsPart.cs b/src/Orchard.Web/Modules/Orchard.OpenId/Models/GoogleSettingsPart.cs new file mode 100644 index 000000000..08656e223 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.OpenId/Models/GoogleSettingsPart.cs @@ -0,0 +1,39 @@ +using System; +using Orchard.ContentManagement; +using Orchard.Environment.Extensions; + +namespace Orchard.OpenId.Models { + [OrchardFeature("Orchard.OpenId.Google")] + public class GoogleSettingsPart : ContentPart { + + public string ClientId { + get { return this.Retrieve(x => x.ClientId, () => Constants.Google.DefaultClientId); } + set { this.Store(x => x.ClientId, value); } + } + + public string ClientSecret { + get { return this.Retrieve(x => x.ClientSecret, () => Constants.Google.DefaultClientSecret); } + set { this.Store(x => x.ClientSecret, value); } + } + + public string CallbackPath { + get { return this.Retrieve(x => x.CallbackPath, () => Constants.General.LogonCallbackUrl); } + set { this.Store(x => x.CallbackPath, value); } + } + + public bool IsValid { + get { + if (String.IsNullOrWhiteSpace(ClientId) || + String.CompareOrdinal(ClientId, Constants.Google.DefaultClientId) == 0 || + String.IsNullOrWhiteSpace(ClientSecret) || + String.CompareOrdinal(ClientId, Constants.Google.DefaultClientSecret) == 0 || + String.IsNullOrWhiteSpace(CallbackPath)) { + + return false; + } + + return true; + } + } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.OpenId/Models/TwitterSettingsPart.cs b/src/Orchard.Web/Modules/Orchard.OpenId/Models/TwitterSettingsPart.cs new file mode 100644 index 000000000..d15b45639 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.OpenId/Models/TwitterSettingsPart.cs @@ -0,0 +1,69 @@ +using System; +using Orchard.ContentManagement; +using Orchard.Environment.Extensions; + +namespace Orchard.OpenId.Models { + [OrchardFeature("Orchard.OpenId.Twitter")] + public class TwitterSettingsPart : ContentPart { + + public string ConsumerKey { + get { return this.Retrieve(x => x.ConsumerKey, () => Constants.Twitter.DefaultConsumerKey); } + set { this.Store(x => x.ConsumerKey, value); } + } + + public string ConsumerSecret { + get { return this.Retrieve(x => x.ConsumerSecret, () => Constants.Twitter.DefaultConsumerSecret); } + set { this.Store(x => x.ConsumerSecret, value); } + } + + public bool IsValid { + get { + if (String.IsNullOrWhiteSpace(ConsumerKey) || + String.CompareOrdinal(ConsumerKey, Constants.Twitter.DefaultConsumerKey) == 0 || + String.IsNullOrWhiteSpace(ConsumerSecret) || + String.CompareOrdinal(ConsumerSecret, Constants.Twitter.DefaultConsumerSecret) == 0) { + + return false; + } + + return true; + } + } + + public string VeriSignClass3SecureServerCA_G2 + { + get { return this.Retrieve(x => x.VeriSignClass3SecureServerCA_G2, () => Constants.Twitter.DefaultVeriSignClass3SecureServerCA_G2); } + set { this.Store(x => x.VeriSignClass3SecureServerCA_G2, value); } + } + + public string VeriSignClass3SecureServerCA_G3 + { + get { return this.Retrieve(x => x.VeriSignClass3SecureServerCA_G3, () => Constants.Twitter.DefaultVeriSignClass3SecureServerCA_G3); } + set { this.Store(x => x.VeriSignClass3SecureServerCA_G3, value); } + } + + public string VeriSignClass3PublicPrimaryCA_G5 + { + get { return this.Retrieve(x => x.VeriSignClass3PublicPrimaryCA_G5, () => Constants.Twitter.DefaultVeriSignClass3PublicPrimaryCA_G5); } + set { this.Store(x => x.VeriSignClass3PublicPrimaryCA_G5, value); } + } + + public string SymantecClass3SecureServerCA_G4 + { + get { return this.Retrieve(x => x.SymantecClass3SecureServerCA_G4, () => Constants.Twitter.DefaultSymantecClass3SecureServerCA_G4); } + set { this.Store(x => x.SymantecClass3SecureServerCA_G4, value); } + } + + public string DigiCertSHA2HighAssuranceServerCA + { + get { return this.Retrieve(x => x.DigiCertSHA2HighAssuranceServerCA, () => Constants.Twitter.DefaultDigiCertSHA2HighAssuranceServerCA); } + set { this.Store(x => x.DigiCertSHA2HighAssuranceServerCA, value); } + } + + public string DigiCertHighAssuranceEVRootCA + { + get { return this.Retrieve(x => x.DigiCertHighAssuranceEVRootCA, () => Constants.Twitter.DefaultDigiCertHighAssuranceEVRootCA); } + set { this.Store(x => x.DigiCertHighAssuranceEVRootCA, value); } + } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.OpenId/Module.txt b/src/Orchard.Web/Modules/Orchard.OpenId/Module.txt new file mode 100644 index 000000000..f137e2562 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.OpenId/Module.txt @@ -0,0 +1,38 @@ +Name: OpenId Connect +AntiForgery: enabled +Author: Tha'er Al-Ajlouni, Avertra Corp. (http://www.avertra.com) +Website: http://orchardproject.net +Version: 1.0 +OrchardVersion: 1.10.1 +Description: Enables Orchard to authenticate users using OpenId +Category: OpenId Providers +Features: + Orchard.OpenId: + Description: Enables Orchard to authenticate users using OpenId + Category: Authentication + Name: OpenId + Orchard.OpenId.Facebook: + Description: Enables Orchard to authenticate users using their Facebook Accounts + Category: Authentication + Name: Facebook + Dependencies: Orchard.OpenId + Orchard.OpenId.Google: + Description: Enables Orchard to authenticate users using their Google Accounts + Category: Authentication + Name: Google + Dependencies: Orchard.OpenId + Orchard.OpenId.Twitter: + Description: Enables Orchard to authenticate users using their Twitter Accounts + Category: Authentication + Name: Twitter + Dependencies: Orchard.OpenId + Orchard.OpenId.AzureActiveDirectory: + Description: Enables Orchard to authenticate users using their Azure AD Accounts + Category: Authentication + Name: Azure Active Directory (AAD) + Dependencies: Orchard.OpenId + Orchard.OpenId.ActiveDirectoryFederationServices: + Description: Enables Orchard to authenticate users using their ADFS Accounts + Category: Authentication + Name: Active Directory Federation Services (ADFS) + Dependencies: Orchard.OpenId \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.OpenId/Orchard.OpenId.csproj b/src/Orchard.Web/Modules/Orchard.OpenId/Orchard.OpenId.csproj new file mode 100644 index 000000000..b581c4332 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.OpenId/Orchard.OpenId.csproj @@ -0,0 +1,342 @@ + + + + + Debug + AnyCPU + 9.0.30729 + 2.0 + {42E217C1-E163-4B6B-9E8F-42BEE21B6896} + {349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc} + Library + Properties + Orchard.OpenId + Orchard.OpenId + v4.5.2 + false + + + 4.0 + + + false + + + + + + + + true + full + false + bin\ + DEBUG;TRACE + prompt + 4 + ..\..\..\OrchardBasicCorrectness.ruleset + false + + + pdbonly + true + bin\ + TRACE + prompt + 4 + AllRules.ruleset + false + + + + ..\..\..\packages\Iesi.Collections.4.0.0.4000\lib\net40\Iesi.Collections.dll + True + + + ..\..\..\packages\Microsoft.Azure.ActiveDirectory.GraphClient.2.1.1\lib\portable-net4+sl5+win+wpa+wp8\Microsoft.Azure.ActiveDirectory.GraphClient.dll + True + + + + ..\..\..\packages\Microsoft.Data.Edm.5.6.4\lib\net40\Microsoft.Data.Edm.dll + True + + + ..\..\..\packages\Microsoft.Data.OData.5.6.4\lib\net40\Microsoft.Data.OData.dll + True + + + ..\..\..\packages\Microsoft.Data.Services.Client.5.6.4\lib\net40\Microsoft.Data.Services.Client.dll + True + + + ..\..\..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.3.13.5\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.dll + True + + + ..\..\..\packages\Microsoft.IdentityModel.Clients.ActiveDirectory.3.13.5\lib\net45\Microsoft.IdentityModel.Clients.ActiveDirectory.Platform.dll + True + + + ..\..\..\packages\Microsoft.IdentityModel.Protocol.Extensions.1.0.2.206221351\lib\net45\Microsoft.IdentityModel.Protocol.Extensions.dll + True + + + ..\..\..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll + True + + + ..\..\..\packages\Microsoft.Owin.Host.SystemWeb.3.0.1\lib\net45\Microsoft.Owin.Host.SystemWeb.dll + True + + + ..\..\..\packages\Microsoft.Owin.Security.3.0.1\lib\net45\Microsoft.Owin.Security.dll + True + + + ..\..\..\packages\Microsoft.Owin.Security.ActiveDirectory.3.0.1\lib\net45\Microsoft.Owin.Security.ActiveDirectory.dll + True + + + ..\..\..\packages\Microsoft.Owin.Security.Cookies.3.0.1\lib\net45\Microsoft.Owin.Security.Cookies.dll + True + + + ..\..\..\packages\Microsoft.Owin.Security.Facebook.3.0.1\lib\net45\Microsoft.Owin.Security.Facebook.dll + True + + + ..\..\..\packages\Microsoft.Owin.Security.Google.3.0.1\lib\net45\Microsoft.Owin.Security.Google.dll + True + + + ..\..\..\packages\Microsoft.Owin.Security.Jwt.3.0.1\lib\net45\Microsoft.Owin.Security.Jwt.dll + True + + + ..\..\..\packages\Microsoft.Owin.Security.OAuth.3.0.1\lib\net45\Microsoft.Owin.Security.OAuth.dll + True + + + ..\..\..\packages\Microsoft.Owin.Security.OpenIdConnect.3.0.1\lib\net45\Microsoft.Owin.Security.OpenIdConnect.dll + True + + + ..\..\..\packages\Microsoft.Owin.Security.Twitter.3.0.1\lib\net45\Microsoft.Owin.Security.Twitter.dll + True + + + ..\..\..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll + True + + + ..\..\..\packages\Newtonsoft.Json.6.0.4\lib\net45\Newtonsoft.Json.dll + True + + + ..\..\..\packages\NHibernate.4.0.4.4000\lib\net40\NHibernate.dll + True + + + ..\..\..\packages\Owin.1.0\lib\net40\Owin.dll + True + + + + + 3.5 + + + + + ..\..\..\packages\System.IdentityModel.Tokens.Jwt.4.0.2.206221351\lib\net45\System.IdentityModel.Tokens.Jwt.dll + True + + + + + + ..\..\..\packages\System.Spatial.5.6.4\lib\net40\System.Spatial.dll + True + + + + + + + ..\..\..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.Helpers.dll + True + + + False + ..\..\..\packages\Microsoft.AspNet.Mvc.5.2.3\lib\net45\System.Web.Mvc.dll + + + + + ..\..\..\packages\Microsoft.AspNet.Razor.3.2.3\lib\net45\System.Web.Razor.dll + True + + + + ..\..\..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.dll + True + + + ..\..\..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.Deployment.dll + True + + + ..\..\..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.Razor.dll + True + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {2D1D92BB-4555-4CBE-8D0E-63563D6CE4C6} + Orchard.Framework + false + + + {9916839C-39FC-4CEB-A5AF-89CA7E87119F} + Orchard.Core + false + + + {d10ad48f-407d-4db5-a328-173ec7cb010f} + Orchard.Roles + + + {79AED36E-ABD0-4747-93D3-8722B042454B} + Orchard.Users + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 10.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + + + + + $(ProjectDir)\..\Manifests + + + + + + + + + + + + False + True + 45979 + / + + + False + True + http://orchard.codeplex.com + False + + + + + \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.OpenId/OwinMiddlewares/ActiveDirectoryFederationServices.cs b/src/Orchard.Web/Modules/Orchard.OpenId/OwinMiddlewares/ActiveDirectoryFederationServices.cs new file mode 100644 index 000000000..6107b1d5e --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.OpenId/OwinMiddlewares/ActiveDirectoryFederationServices.cs @@ -0,0 +1,53 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.Owin.Security.OpenIdConnect; +using Orchard.ContentManagement; +using Orchard.Environment.Extensions; +using Orchard.OpenId.Models; +using Orchard.Owin; +using Owin; + +namespace Orchard.OpenId.OwinMiddlewares { + [OrchardFeature("Orchard.OpenId.ActiveDirectoryFederationServices")] + public class ActiveDirectoryFederationServices : IOwinMiddlewareProvider { + private readonly IWorkContextAccessor _workContextAccessor; + + public ActiveDirectoryFederationServices(IWorkContextAccessor workContextAccessor) { + _workContextAccessor = workContextAccessor; + } + + public IEnumerable GetOwinMiddlewares() { + var settings = _workContextAccessor.GetContext().CurrentSite.As(); + + if (settings == null || !settings.IsValid) { + return Enumerable.Empty(); + } + + var openIdOptions = new OpenIdConnectAuthenticationOptions { + ClientId = settings.ClientId, + MetadataAddress = settings.MetadataAddress, + RedirectUri = settings.PostLogoutRedirectUri, + PostLogoutRedirectUri = settings.PostLogoutRedirectUri, + Notifications = new OpenIdConnectAuthenticationNotifications() + { + AuthenticationFailed = context => { + context.HandleResponse(); + context.Response.Redirect(Constants.General.AuthenticationErrorUrl); + + return Task.FromResult(0); + } + } + }; + + return new List { + new OwinMiddlewareRegistration { + Priority = Constants.General.OpenIdOwinMiddlewarePriority, + Configure = app => { + app.UseOpenIdConnectAuthentication(openIdOptions); + } + } + }; + } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.OpenId/OwinMiddlewares/AzureActiveDirectory.cs b/src/Orchard.Web/Modules/Orchard.OpenId/OwinMiddlewares/AzureActiveDirectory.cs new file mode 100644 index 000000000..bb8736d2a --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.OpenId/OwinMiddlewares/AzureActiveDirectory.cs @@ -0,0 +1,155 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Security.Claims; +using System.Threading.Tasks; +using System.Web; +using System.Web.Helpers; +using System.Web.WebPages; +using Microsoft.IdentityModel.Clients.ActiveDirectory; +using Microsoft.Owin.Security.DataProtection; +using Microsoft.Owin.Security.OpenIdConnect; +using Orchard.ContentManagement; +using Orchard.Environment.Extensions; +using Orchard.Logging; +using Orchard.OpenId.Models; +using Orchard.OpenId.Security; +using Orchard.OpenId.Services.AzureActiveDirectory; +using Orchard.Owin; +using Owin; +using LogLevel = Orchard.Logging.LogLevel; + +namespace Orchard.OpenId.OwinMiddlewares { + [OrchardFeature("Orchard.OpenId.AzureActiveDirectory")] + public class AzureActiveDirectory : IOwinMiddlewareProvider { + public ILogger Logger { get; set; } + + private readonly IWorkContextAccessor _workContextAccessor; + private readonly InMemoryCache _inMemoryCache; + private readonly IAzureActiveDirectoryService _azureActiveDirectoryService; + private string _azureGraphApiUri; + private string _azureGraphApiKey; + private string _azureClientId; + private string _azureTenant; + private string _azureAdInstance; + + public AzureActiveDirectory( + IWorkContextAccessor workContextAccessor, + IAzureActiveDirectoryService azureActiveDirectoryService, + InMemoryCache inMemoryCache) { + _workContextAccessor = workContextAccessor; + _azureActiveDirectoryService = azureActiveDirectoryService; + _inMemoryCache = inMemoryCache; + + Logger = NullLogger.Instance; + } + + public IEnumerable GetOwinMiddlewares() { + var settings = _workContextAccessor.GetContext().CurrentSite.As(); + var logoutRedirectUri = string.Empty; + var azureAppKey = string.Empty; + var azureWebSiteProtectionEnabled = false; + var azureUseAzureGraphApi = false; + + if (settings == null || !settings.IsValid) { + return Enumerable.Empty(); + } + + _azureClientId = settings.ClientId; + _azureTenant = settings.Tenant; + _azureAdInstance = settings.ADInstance; + _azureGraphApiUri = settings.GraphApiUrl; + logoutRedirectUri = settings.LogoutRedirectUri; + azureWebSiteProtectionEnabled = settings.AzureWebSiteProtectionEnabled; + azureAppKey = settings.AppKey; + azureUseAzureGraphApi = settings.UseAzureGraphApi; + + var authority = string.Format(CultureInfo.InvariantCulture, _azureAdInstance, _azureTenant); + var middlewares = new List(); + + AntiForgeryConfig.UniqueClaimTypeIdentifier = ClaimTypes.NameIdentifier; + + var openIdOptions = new OpenIdConnectAuthenticationOptions { + ClientId = _azureClientId, + Authority = authority, + PostLogoutRedirectUri = logoutRedirectUri, + Notifications = new OpenIdConnectAuthenticationNotifications() { + AuthorizationCodeReceived = (context) => { + var code = context.Code; + var credential = new ClientCredential(_azureClientId, azureAppKey); + _inMemoryCache.UserObjectId = context.AuthenticationTicket.Identity.FindFirst(Constants.AzureActiveDirectory.ObjectIdentifierKey).Value; + var authContext = new AuthenticationContext(authority, _inMemoryCache); + var result = authContext.AcquireTokenByAuthorizationCodeAsync(code, new Uri(HttpContext.Current.Request.Url.GetLeftPart(UriPartial.Path)), credential, _azureGraphApiUri).Result; + + return Task.FromResult(0); + }, + AuthenticationFailed = context => { + context.HandleResponse(); + context.Response.Redirect(Constants.General.AuthenticationErrorUrl); + + return Task.FromResult(0); + } + } + + }; + + if (azureWebSiteProtectionEnabled) { + middlewares.Add(new OwinMiddlewareRegistration { + Priority = "9", + Configure = app => { app.SetDataProtectionProvider(new MachineKeyProtectionProvider()); } + }); + } + + middlewares.Add(new OwinMiddlewareRegistration { + Priority = Constants.General.OpenIdOwinMiddlewarePriority, + Configure = app => { + app.UseOpenIdConnectAuthentication(openIdOptions); + } + }); + + if (azureUseAzureGraphApi) { + middlewares.Add(new OwinMiddlewareRegistration { + Priority = "11", + Configure = app => app.Use(async (context, next) => { + try { + if (_azureActiveDirectoryService.Token == null && _azureActiveDirectoryService.Token.IsEmpty()) { + RegenerateAzureGraphApiToken(); + } + else { + if (DateTimeOffset.Compare(DateTimeOffset.UtcNow, _azureActiveDirectoryService.TokenExpiresOn) > 0) { + RegenerateAzureGraphApiToken(); + } + } + } + catch (Exception ex) { + Logger.Log(LogLevel.Error, ex, "An error occurred generating azure api credential {0}", ex.Message); + } + + await next.Invoke(); + }) + }); + } + + return middlewares; + } + + private void RegenerateAzureGraphApiToken() { + var result = GetAuthContext().AcquireTokenAsync(_azureGraphApiUri, GetClientCredential()).Result; + + _azureActiveDirectoryService.TokenExpiresOn = result.ExpiresOn; + _azureActiveDirectoryService.Token = result.AccessToken; + _azureActiveDirectoryService.AzureTenant = _azureTenant; + } + + private ClientCredential GetClientCredential() { + return new ClientCredential(_azureClientId, _azureGraphApiKey); + } + + private AuthenticationContext GetAuthContext() { + var authority = string.Format(CultureInfo.InvariantCulture, _azureAdInstance, _azureTenant); + + return new AuthenticationContext(authority, false); + } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.OpenId/OwinMiddlewares/Facebook.cs b/src/Orchard.Web/Modules/Orchard.OpenId/OwinMiddlewares/Facebook.cs new file mode 100644 index 000000000..0b6cfdc06 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.OpenId/OwinMiddlewares/Facebook.cs @@ -0,0 +1,38 @@ +using System.Collections.Generic; +using System.Linq; +using Orchard.ContentManagement; +using Orchard.Environment.Extensions; +using Orchard.OpenId.Models; +using Orchard.Owin; +using Owin; + +namespace Orchard.OpenId.OwinMiddlewares { + [OrchardFeature("Orchard.OpenId.Facebook")] + public class Facebook : IOwinMiddlewareProvider { + private readonly IWorkContextAccessor _workContextAccessor; + + public Facebook(IWorkContextAccessor workContextAccessor) { + _workContextAccessor = workContextAccessor; + } + + public IEnumerable GetOwinMiddlewares() { + var settings = _workContextAccessor.GetContext().CurrentSite.As(); + + if (settings == null || !settings.IsValid) { + return Enumerable.Empty(); + } + + return new List { + new OwinMiddlewareRegistration { + Priority = Constants.General.OpenIdOwinMiddlewarePriority, + Configure = app => { + app.UseFacebookAuthentication( + appId: settings.AppId, + appSecret: settings.AppSecret + ); + } + } + }; + } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.OpenId/OwinMiddlewares/Google.cs b/src/Orchard.Web/Modules/Orchard.OpenId/OwinMiddlewares/Google.cs new file mode 100644 index 000000000..926cacc3a --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.OpenId/OwinMiddlewares/Google.cs @@ -0,0 +1,43 @@ +using System.Collections.Generic; +using System.Linq; +using Microsoft.Owin; +using Microsoft.Owin.Security.Google; +using Orchard.ContentManagement; +using Orchard.Environment.Extensions; +using Orchard.OpenId.Models; +using Orchard.Owin; +using Owin; + +namespace Orchard.OpenId.OwinMiddlewares { + [OrchardFeature("Orchard.OpenId.Google")] + public class Google : IOwinMiddlewareProvider { + private readonly IWorkContextAccessor _workContextAccessor; + + public Google(IWorkContextAccessor workContextAccessor) { + _workContextAccessor = workContextAccessor; + } + + public IEnumerable GetOwinMiddlewares() { + var settings = _workContextAccessor.GetContext().CurrentSite.As(); + + if (settings == null || !settings.IsValid) { + return Enumerable.Empty(); + } + + var authenticationOptions = new GoogleOAuth2AuthenticationOptions { + ClientId = settings.ClientId, + ClientSecret = settings.ClientSecret, + CallbackPath = new PathString(settings.CallbackPath) + }; + + return new List { + new OwinMiddlewareRegistration { + Priority = Constants.General.OpenIdOwinMiddlewarePriority, + Configure = app => { + app.UseGoogleAuthentication(authenticationOptions); + } + } + }; + } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.OpenId/OwinMiddlewares/OpenId.cs b/src/Orchard.Web/Modules/Orchard.OpenId/OwinMiddlewares/OpenId.cs new file mode 100644 index 000000000..d0e50f964 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.OpenId/OwinMiddlewares/OpenId.cs @@ -0,0 +1,32 @@ +using System.Collections.Generic; +using System.Security.Claims; +using System.Web.Helpers; +using Microsoft.Owin.Security; +using Microsoft.Owin.Security.Cookies; +using Orchard.Environment.Extensions; +using Orchard.Owin; +using Owin; + +namespace Orchard.OpenId.OwinMiddlewares { + [OrchardFeature("Orchard.OpenId")] + public class OpenId : IOwinMiddlewareProvider + { + public IEnumerable GetOwinMiddlewares() + { + var cookieOptions = new CookieAuthenticationOptions(); + var authenticationType = CookieAuthenticationDefaults.AuthenticationType; + + AntiForgeryConfig.UniqueClaimTypeIdentifier = ClaimTypes.NameIdentifier; + + return new List { + new OwinMiddlewareRegistration { + Priority = "9", + Configure = app => { + app.SetDefaultSignInAsAuthenticationType(authenticationType); + app.UseCookieAuthentication(cookieOptions); + } + } + }; + } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.OpenId/OwinMiddlewares/Twitter.cs b/src/Orchard.Web/Modules/Orchard.OpenId/OwinMiddlewares/Twitter.cs new file mode 100644 index 000000000..5f79f10ea --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.OpenId/OwinMiddlewares/Twitter.cs @@ -0,0 +1,51 @@ +using System.Collections.Generic; +using System.Linq; +using Microsoft.Owin.Security; +using Microsoft.Owin.Security.Twitter; +using Orchard.ContentManagement; +using Orchard.Environment.Extensions; +using Orchard.OpenId.Models; +using Orchard.Owin; +using Owin; + +namespace Orchard.OpenId.OwinMiddlewares { + [OrchardFeature("Orchard.OpenId.Twitter")] + public class Twitter : IOwinMiddlewareProvider { + private readonly IWorkContextAccessor _workContextAccessor; + + public Twitter(IWorkContextAccessor workContextAccessor) { + _workContextAccessor = workContextAccessor; + } + + public IEnumerable GetOwinMiddlewares() { + var settings = _workContextAccessor.GetContext().CurrentSite.As(); + + if (settings == null || !settings.IsValid) { + return Enumerable.Empty(); + } + + var twitterOptions = new TwitterAuthenticationOptions { + ConsumerKey = settings.ConsumerKey, + ConsumerSecret = settings.ConsumerSecret, + BackchannelCertificateValidator = new CertificateSubjectKeyIdentifierValidator(new[] + { + settings.VeriSignClass3SecureServerCA_G2, + settings.VeriSignClass3SecureServerCA_G3, + settings.VeriSignClass3PublicPrimaryCA_G5, + settings.SymantecClass3SecureServerCA_G4, + settings.DigiCertSHA2HighAssuranceServerCA, + settings.DigiCertHighAssuranceEVRootCA + }) + }; + + return new List { + new OwinMiddlewareRegistration { + Priority = Constants.General.OpenIdOwinMiddlewarePriority, + Configure = app => { + app.UseTwitterAuthentication(twitterOptions); + } + } + }; + } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.OpenId/Permissions.cs b/src/Orchard.Web/Modules/Orchard.OpenId/Permissions.cs new file mode 100644 index 000000000..6381675aa --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.OpenId/Permissions.cs @@ -0,0 +1,27 @@ +using System.Collections.Generic; +using Orchard.Environment.Extensions.Models; +using Orchard.Security.Permissions; + +namespace Orchard.OpenId { + public class Permissions : IPermissionProvider { + public static readonly Permission ManageOpenId = new Permission { Description = "Manage OpenId settings", Name = "ManageOpenId" }; + + public virtual Feature Feature { get; set; } + + public IEnumerable GetPermissions() { + return new[] { + ManageOpenId, + }; + } + + public IEnumerable GetDefaultStereotypes() { + return new[] { + new PermissionStereotype { + Name = "Administrator", + Permissions = new[] {ManageOpenId} + }, + }; + } + + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.OpenId/Properties/AssemblyInfo.cs b/src/Orchard.Web/Modules/Orchard.OpenId/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..cb1f7ed2e --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.OpenId/Properties/AssemblyInfo.cs @@ -0,0 +1,37 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Security; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Orchard.OpenId")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyProduct("Orchard")] +[assembly: AssemblyCopyright("")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("1bf62a51-6313-4204-bd2f-660c3cc8e3b9")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Revision and Build Numbers +// by using the '*' as shown below: + +[assembly: AssemblyVersion("1.10.1")] +[assembly: AssemblyFileVersion("1.10.1")] + diff --git a/src/Orchard.Web/Modules/Orchard.OpenId/Providers/ActiveDirectoryFederationServices.cs b/src/Orchard.Web/Modules/Orchard.OpenId/Providers/ActiveDirectoryFederationServices.cs new file mode 100644 index 000000000..a1e89d159 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.OpenId/Providers/ActiveDirectoryFederationServices.cs @@ -0,0 +1,52 @@ +using System; +using Orchard.ContentManagement; +using Orchard.Environment.Extensions; +using Orchard.OpenId.Models; +using Orchard.OpenId.Services; +using Orchard.Settings; + +namespace Orchard.OpenId.Providers { + [OrchardFeature("Orchard.OpenId.ActiveDirectoryFederationServices")] + public class ActiveDirectoryFederationServices : IOpenIdProvider { + private readonly IWorkContextAccessor _workContextAccessor; + + public ActiveDirectoryFederationServices( + IWorkContextAccessor workContextAccessor) { + + _workContextAccessor = workContextAccessor; + } + + public string AuthenticationType { + get { return "OpenIdConnect"; } + } + + public string Name { + get { return "ADFS"; } + } + + public string DisplayName { + get { return "Active Directory Federation Services"; } + } + + public bool IsValid { + get { return IsProviderValid(); } + } + + private bool IsProviderValid() { + try { + ActiveDirectoryFederationServicesSettingsPart settings; + ISite site; + + var scope = _workContextAccessor.GetContext(); + + site = scope.Resolve().GetSiteSettings(); + settings = site.As(); + + return (settings != null && settings.IsValid); + } + catch (Exception) { + return false; + } + } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.OpenId/Providers/AzureActiveDirectory.cs b/src/Orchard.Web/Modules/Orchard.OpenId/Providers/AzureActiveDirectory.cs new file mode 100644 index 000000000..13ee6124f --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.OpenId/Providers/AzureActiveDirectory.cs @@ -0,0 +1,52 @@ +using System; +using Orchard.ContentManagement; +using Orchard.Environment.Extensions; +using Orchard.OpenId.Models; +using Orchard.OpenId.Services; +using Orchard.Settings; + +namespace Orchard.OpenId.Providers { + [OrchardFeature("Orchard.OpenId.AzureActiveDirectory")] + public class AzureActiveDirectory : IOpenIdProvider { + private readonly IWorkContextAccessor _workContextAccessor; + + public AzureActiveDirectory( + IWorkContextAccessor workContextAccessor) { + + _workContextAccessor = workContextAccessor; + } + + public string AuthenticationType { + get { return "OpenIdConnect"; } + } + + public string Name { + get { return "AzureAD"; } + } + + public string DisplayName { + get { return "Azure Active Directory"; } + } + + public bool IsValid { + get { return IsProviderValid(); } + } + + private bool IsProviderValid() { + try { + AzureActiveDirectorySettingsPart settings; + ISite site; + + var scope = _workContextAccessor.GetContext(); + + site = scope.Resolve().GetSiteSettings(); + settings = site.As(); + + return (settings != null && settings.IsValid); + } + catch (Exception) { + return false; + } + } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.OpenId/Providers/Facebook.cs b/src/Orchard.Web/Modules/Orchard.OpenId/Providers/Facebook.cs new file mode 100644 index 000000000..018d985d0 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.OpenId/Providers/Facebook.cs @@ -0,0 +1,52 @@ +using System; +using Orchard.ContentManagement; +using Orchard.Environment.Extensions; +using Orchard.OpenId.Models; +using Orchard.OpenId.Services; +using Orchard.Settings; + +namespace Orchard.OpenId.Providers { + [OrchardFeature("Orchard.OpenId.Facebook")] + public class Facebook : IOpenIdProvider { + private readonly IWorkContextAccessor _workContextAccessor; + + public Facebook( + IWorkContextAccessor workContextAccessor) { + + _workContextAccessor = workContextAccessor; + } + + public string AuthenticationType { + get { return "Facebook"; } + } + + public string Name { + get { return "Facebook"; } + } + + public string DisplayName { + get { return "Facebook"; } + } + + public bool IsValid { + get { return IsProviderValid(); } + } + + private bool IsProviderValid() { + try { + FacebookSettingsPart settings; + ISite site; + + var scope = _workContextAccessor.GetContext(); + + site = scope.Resolve().GetSiteSettings(); + settings = site.As(); + + return (settings != null && settings.IsValid); + } + catch (Exception) { + return false; + } + } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.OpenId/Providers/Google.cs b/src/Orchard.Web/Modules/Orchard.OpenId/Providers/Google.cs new file mode 100644 index 000000000..0f1031bf0 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.OpenId/Providers/Google.cs @@ -0,0 +1,53 @@ +using System; +using Orchard.ContentManagement; +using Orchard.Environment.Extensions; +using Orchard.OpenId.Models; +using Orchard.OpenId.Services; +using Orchard.Settings; + +namespace Orchard.OpenId.Providers { + [OrchardFeature("Orchard.OpenId.Google")] + public class Google : IOpenIdProvider { + private readonly IWorkContextAccessor _workContextAccessor; + + public Google( + IWorkContextAccessor workContextAccessor, + ISiteService siteService) { + + _workContextAccessor = workContextAccessor; + } + + public string AuthenticationType { + get { return "Google"; } + } + + public string Name { + get { return "Google"; } + } + + public string DisplayName { + get { return "Google"; } + } + + public bool IsValid { + get { return IsProviderValid(); } + } + + private bool IsProviderValid() { + try { + GoogleSettingsPart settings; + ISite site; + + var scope = _workContextAccessor.GetContext(); + + site = scope.Resolve().GetSiteSettings(); + settings = site.As(); + + return (settings != null && settings.IsValid); + } + catch (Exception) { + return false; + } + } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.OpenId/Providers/Twitter.cs b/src/Orchard.Web/Modules/Orchard.OpenId/Providers/Twitter.cs new file mode 100644 index 000000000..2b998fbb0 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.OpenId/Providers/Twitter.cs @@ -0,0 +1,52 @@ +using System; +using Orchard.ContentManagement; +using Orchard.Environment.Extensions; +using Orchard.OpenId.Models; +using Orchard.OpenId.Services; +using Orchard.Settings; + +namespace Orchard.OpenId.Providers { + [OrchardFeature("Orchard.OpenId.Twitter")] + public class Twitter : IOpenIdProvider { + private readonly IWorkContextAccessor _workContextAccessor; + + public Twitter( + IWorkContextAccessor workContextAccessor) { + + _workContextAccessor = workContextAccessor; + } + + public string AuthenticationType { + get { return "Twitter"; } + } + + public string Name { + get { return "Twitter"; } + } + + public string DisplayName { + get { return "Twitter"; } + } + + public bool IsValid { + get { return IsProviderValid(); } + } + + private bool IsProviderValid() { + try { + TwitterSettingsPart settings; + ISite site; + + var scope = _workContextAccessor.GetContext(); + + site = scope.Resolve().GetSiteSettings(); + settings = site.As(); + + return (settings != null && settings.IsValid); + } + catch (Exception) { + return false; + } + } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.OpenId/ResourceManifest.cs b/src/Orchard.Web/Modules/Orchard.OpenId/ResourceManifest.cs new file mode 100644 index 000000000..670a40fcb --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.OpenId/ResourceManifest.cs @@ -0,0 +1,15 @@ +using Orchard.UI.Resources; + +namespace Orchard.OpenId +{ + public class ResourceManifest : IResourceManifestProvider + { + public void BuildManifests(ResourceManifestBuilder builder) + { + var manifest = builder.Add(); + + manifest.DefineStyle("TwitterAdmin").SetUrl("twitter-admin.css"); + manifest.DefineScript("TwitterAdmin").SetUrl("twitter-admin.js"); + } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.OpenId/Routes/OpenId.cs b/src/Orchard.Web/Modules/Orchard.OpenId/Routes/OpenId.cs new file mode 100644 index 000000000..4623ef48c --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.OpenId/Routes/OpenId.cs @@ -0,0 +1,83 @@ +using System.Collections.Generic; +using System.Linq; +using System.Web.Mvc; +using System.Web.Routing; +using Orchard.Mvc.Routes; +using Orchard.OpenId.Services; + +namespace Orchard.Azure.Authentication { + public class OpenIdRoutes : IRouteProvider { + private readonly IEnumerable _openIdProviders; + + public OpenIdRoutes(IEnumerable openIdProviders) { + _openIdProviders = openIdProviders; + } + + public void GetRoutes(ICollection routes) { + foreach (var route in GetRoutes()) { + routes.Add(route); + } + } + + public IEnumerable GetRoutes() { + if (IsAnyProviderSettingsValid() == false) + return Enumerable.Empty(); + + return new[] { + new RouteDescriptor { + Priority = 11, + Route = new Route( + "Users/Account/Challenge/{openIdProvider}", + new RouteValueDictionary { + {"area", "Orchard.OpenId"}, + {"controller", "Account"}, + {"action", "Challenge"} + }, + new RouteValueDictionary(), + new RouteValueDictionary { + {"area", "Orchard.OpenId"}, + {"controller", "Account"}, + {"action", "Challenge"} + }, + new MvcRouteHandler()) + }, + new RouteDescriptor { + Priority = 10, + Route = new Route( + "Users/Account/{action}", + new RouteValueDictionary { + {"area", "Orchard.OpenId"}, + {"controller", "Account"} + }, + new RouteValueDictionary(), + new RouteValueDictionary { + {"area", "Orchard.OpenId"}, + {"controller", "Account"} + }, + new MvcRouteHandler()) + }, + new RouteDescriptor { + Priority = 10, + Route = new Route( + "Authentication/Error/", + new RouteValueDictionary { + {"area", "Orchard.OpenId"}, + {"controller", "Account"}, + { "action", "Error" } + }, + new RouteValueDictionary(), + new RouteValueDictionary { + {"area", "Orchard.OpenId"}, + {"controller", "Account"}, + { "action", "Error" } + }, + new MvcRouteHandler()) + } + }; + } + + private bool IsAnyProviderSettingsValid() { + return _openIdProviders.Any(provider => provider.IsValid); + } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.OpenId/Scripts/Web.config b/src/Orchard.Web/Modules/Orchard.OpenId/Scripts/Web.config new file mode 100644 index 000000000..176fe49cd --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.OpenId/Scripts/Web.config @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/Orchard.Web/Modules/Orchard.OpenId/Scripts/twitter-admin.js b/src/Orchard.Web/Modules/Orchard.OpenId/Scripts/twitter-admin.js new file mode 100644 index 000000000..e4a25568d --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.OpenId/Scripts/twitter-admin.js @@ -0,0 +1,9 @@ +$(document).on('click', '#colExpControl', function () { + if ($('#colExpArea').css('display') == 'none') { + $('#colExpArea').show(300); + $('#colExpButton').html('-'); + } else { + $('#colExpArea').hide(300); + $('#colExpButton').html('+'); + } +}); \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.OpenId/Security/MachineKeyDataProtector.cs b/src/Orchard.Web/Modules/Orchard.OpenId/Security/MachineKeyDataProtector.cs new file mode 100644 index 000000000..6f19ce98a --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.OpenId/Security/MachineKeyDataProtector.cs @@ -0,0 +1,26 @@ +using System.Web.Security; +using Microsoft.Owin.Security.DataProtection; + +namespace Orchard.OpenId.Security { + public class MachineKeyProtectionProvider : IDataProtectionProvider { + public IDataProtector Create(params string[] purposes) { + return new MachineKeyDataProtector(purposes); + } + } + + public class MachineKeyDataProtector : IDataProtector { + private readonly string[] _purposes; + + public MachineKeyDataProtector(string[] purposes) { + _purposes = purposes; + } + + public byte[] Protect(byte[] userData) { + return MachineKey.Protect(userData, _purposes); + } + + public byte[] Unprotect(byte[] protectedData) { + return MachineKey.Unprotect(protectedData, _purposes); + } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.OpenId/Services/ActiveDirectoryFederationServices/MissingSettingsBanner.cs b/src/Orchard.Web/Modules/Orchard.OpenId/Services/ActiveDirectoryFederationServices/MissingSettingsBanner.cs new file mode 100644 index 000000000..2f2e0b2e9 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.OpenId/Services/ActiveDirectoryFederationServices/MissingSettingsBanner.cs @@ -0,0 +1,35 @@ +using System.Collections.Generic; +using System.Web.Mvc; +using Orchard.ContentManagement; +using Orchard.Environment.Extensions; +using Orchard.Localization; +using Orchard.OpenId.Models; +using Orchard.UI.Admin.Notification; +using Orchard.UI.Notify; + +namespace Orchard.Azure.Authentication.Services.ActiveDirectoryFederationServices { + [OrchardFeature("Orchard.OpenId.ActiveDirectoryFederationServices")] + public class MissingSettingsBanner : INotificationProvider { + private readonly IOrchardServices _orchardServices; + private readonly UrlHelper _urlHelper; + + public MissingSettingsBanner(IOrchardServices orchardServices, UrlHelper urlHelper) { + _orchardServices = orchardServices; + _urlHelper = urlHelper; + + T = NullLocalizer.Instance; + } + + public Localizer T { get; set; } + + public IEnumerable GetNotifications() { + var workContext = _orchardServices.WorkContext; + var settings = workContext.CurrentSite.As(); + + if (settings == null || !settings.IsValid) { + var url = _urlHelper.Action("OpenId", "Admin", new { Area = "Settings" }); + yield return new NotifyEntry { Message = T("The Active Directory Federation Services settings need to be configured.", url), Type = NotifyType.Warning }; + } + } + } +} diff --git a/src/Orchard.Web/Modules/Orchard.OpenId/Services/AzureActiveDirectory/AzureActiveDirectoryService.cs b/src/Orchard.Web/Modules/Orchard.OpenId/Services/AzureActiveDirectory/AzureActiveDirectoryService.cs new file mode 100644 index 000000000..3cb47f65f --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.OpenId/Services/AzureActiveDirectory/AzureActiveDirectoryService.cs @@ -0,0 +1,31 @@ +using System; +using System.Threading.Tasks; +using System.Web.WebPages; +using Microsoft.Azure.ActiveDirectory.GraphClient; +using Orchard.Environment.Extensions; + +namespace Orchard.OpenId.Services.AzureActiveDirectory { + [OrchardFeature("Orchard.OpenId.AzureActiveDirectory")] + public class AzureActiveDirectoryService : IAzureActiveDirectoryService { + public string Token { get; set; } + public DateTimeOffset TokenExpiresOn { get; set; } + public string AzureTenant { get; set; } + + public async Task AcquireTokenAsync() { + if (Token == null || Token.IsEmpty()) + { + throw new Exception("Authorization Required."); + } + return await Task.FromResult(Token); + } + + public ActiveDirectoryClient GetActiveDirectoryClient() { + var baseServiceUri = new Uri("https://graph.windows.net/"); + + var activeDirectoryClient = new ActiveDirectoryClient(new Uri(baseServiceUri, AzureTenant), + async () => await AcquireTokenAsync()); + + return activeDirectoryClient; + } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.OpenId/Services/AzureActiveDirectory/IAzureActiveDirectoryService.cs b/src/Orchard.Web/Modules/Orchard.OpenId/Services/AzureActiveDirectory/IAzureActiveDirectoryService.cs new file mode 100644 index 000000000..81b0a7243 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.OpenId/Services/AzureActiveDirectory/IAzureActiveDirectoryService.cs @@ -0,0 +1,13 @@ +using System; +using System.Threading.Tasks; +using Microsoft.Azure.ActiveDirectory.GraphClient; + +namespace Orchard.OpenId.Services.AzureActiveDirectory { + public interface IAzureActiveDirectoryService : ISingletonDependency { + string Token { get; set; } + DateTimeOffset TokenExpiresOn { get; set; } + string AzureTenant { get; set; } + Task AcquireTokenAsync(); + ActiveDirectoryClient GetActiveDirectoryClient(); + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.OpenId/Services/AzureActiveDirectory/InMemoryCache.cs b/src/Orchard.Web/Modules/Orchard.OpenId/Services/AzureActiveDirectory/InMemoryCache.cs new file mode 100644 index 000000000..2fc8c1411 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.OpenId/Services/AzureActiveDirectory/InMemoryCache.cs @@ -0,0 +1,75 @@ +using System; +using System.Collections.Concurrent; +using Microsoft.IdentityModel.Clients.ActiveDirectory; +using Orchard.Environment.Extensions; + +namespace Orchard.OpenId.Services.AzureActiveDirectory { + [OrchardFeature("Orchard.OpenId.AzureActiveDirectory")] + public class InMemoryCache : TokenCache, ISingletonDependency { + public InMemoryCache() { + _inMemoryTokenCache = new ConcurrentDictionary(); + + AfterAccess = AfterAccessNotification; + BeforeAccess = BeforeAccessNotification; + + Load(); + } + + private const string CacheIdSuffix = "_TokenCache"; + private static ConcurrentDictionary _inMemoryTokenCache; + private string _cacheId; + private string _userObjectId; + + public string UserObjectId { + get { + return _userObjectId; + } + set { + _userObjectId = value; + _cacheId = String.Concat(_userObjectId, CacheIdSuffix); + } + } + + public override void Clear() { + base.Clear(); + + if (String.IsNullOrWhiteSpace(_cacheId)) + return; + + byte[] oldData; + _inMemoryTokenCache.TryRemove(_cacheId, out oldData); + } + + private void Load() { + if (String.IsNullOrWhiteSpace(_cacheId)) + return; + + if (_inMemoryTokenCache.ContainsKey(_cacheId)) { + byte[] data; + _inMemoryTokenCache.TryGetValue(_cacheId, out data); + + if (data != default(byte[])) + Deserialize(data); + } + } + + private void Persist() { + if (String.IsNullOrWhiteSpace(_cacheId)) + return; + + HasStateChanged = false; + + _inMemoryTokenCache.AddOrUpdate(_cacheId, Serialize(), (key, current) => { return Serialize(); }); + } + + private void BeforeAccessNotification(TokenCacheNotificationArgs args) { + Load(); + } + + private void AfterAccessNotification(TokenCacheNotificationArgs args) { + if (HasStateChanged) { + Persist(); + } + } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.OpenId/Services/AzureActiveDirectory/MissingSettingsBanner.cs b/src/Orchard.Web/Modules/Orchard.OpenId/Services/AzureActiveDirectory/MissingSettingsBanner.cs new file mode 100644 index 000000000..48dd143c5 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.OpenId/Services/AzureActiveDirectory/MissingSettingsBanner.cs @@ -0,0 +1,36 @@ +using System.Collections.Generic; +using System.Web.Mvc; +using Orchard.ContentManagement; +using Orchard.Environment.Extensions; +using Orchard.Localization; +using Orchard.OpenId.Models; +using Orchard.UI.Admin.Notification; +using Orchard.UI.Notify; + +namespace Orchard.Azure.Authentication.Services.AzureActiveDirectory { + [OrchardFeature("Orchard.OpenId.AzureActiveDirectory")] + public class MissingSettingsBanner : INotificationProvider { + private readonly IOrchardServices _orchardServices; + private readonly UrlHelper _urlHelper; + + public MissingSettingsBanner(IOrchardServices orchardServices, UrlHelper urlHelper) + { + _orchardServices = orchardServices; + _urlHelper = urlHelper; + + T = NullLocalizer.Instance; + } + + public Localizer T { get; set; } + + public IEnumerable GetNotifications() { + var workContext = _orchardServices.WorkContext; + var azureSettings = workContext.CurrentSite.As(); + + if (azureSettings == null || !azureSettings.IsValid) { + var url = _urlHelper.Action("OpenId", "Admin", new { Area = "Settings" }); + yield return new NotifyEntry { Message = T("The Azure AD Authentication settings need to be configured.", url), Type = NotifyType.Warning }; + } + } + } +} diff --git a/src/Orchard.Web/Modules/Orchard.OpenId/Services/Facebook/MissingSettingsBanner.cs b/src/Orchard.Web/Modules/Orchard.OpenId/Services/Facebook/MissingSettingsBanner.cs new file mode 100644 index 000000000..011aac000 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.OpenId/Services/Facebook/MissingSettingsBanner.cs @@ -0,0 +1,36 @@ +using System.Collections.Generic; +using System.Web.Mvc; +using Orchard.ContentManagement; +using Orchard.Environment.Extensions; +using Orchard.Localization; +using Orchard.OpenId.Models; +using Orchard.UI.Admin.Notification; +using Orchard.UI.Notify; + +namespace Orchard.Azure.Authentication.Services.Facebook { + [OrchardFeature("Orchard.OpenId.Facebook")] + public class MissingSettingsBanner : INotificationProvider { + private readonly IOrchardServices _orchardServices; + private readonly UrlHelper _urlHelper; + + public MissingSettingsBanner(IOrchardServices orchardServices, UrlHelper urlHelper) + { + _orchardServices = orchardServices; + _urlHelper = urlHelper; + + T = NullLocalizer.Instance; + } + + public Localizer T { get; set; } + + public IEnumerable GetNotifications() { + var workContext = _orchardServices.WorkContext; + var settings = workContext.CurrentSite.As(); + + if (settings == null || !settings.IsValid) { + var url = _urlHelper.Action("OpenId", "Admin", new { Area = "Settings" }); + yield return new NotifyEntry { Message = T("The Facebook settings need to be configured.", url), Type = NotifyType.Warning }; + } + } + } +} diff --git a/src/Orchard.Web/Modules/Orchard.OpenId/Services/Google/MissingSettingsBanner.cs b/src/Orchard.Web/Modules/Orchard.OpenId/Services/Google/MissingSettingsBanner.cs new file mode 100644 index 000000000..6888a12e8 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.OpenId/Services/Google/MissingSettingsBanner.cs @@ -0,0 +1,36 @@ +using System.Collections.Generic; +using System.Web.Mvc; +using Orchard.ContentManagement; +using Orchard.Environment.Extensions; +using Orchard.Localization; +using Orchard.OpenId.Models; +using Orchard.UI.Admin.Notification; +using Orchard.UI.Notify; + +namespace Orchard.Azure.Authentication.Services.Google { + [OrchardFeature("Orchard.OpenId.Google")] + public class MissingSettingsBanner : INotificationProvider { + private readonly IOrchardServices _orchardServices; + private readonly UrlHelper _urlHelper; + + public MissingSettingsBanner(IOrchardServices orchardServices, UrlHelper urlHelper) + { + _orchardServices = orchardServices; + _urlHelper = urlHelper; + + T = NullLocalizer.Instance; + } + + public Localizer T { get; set; } + + public IEnumerable GetNotifications() { + var workContext = _orchardServices.WorkContext; + var settings = workContext.CurrentSite.As(); + + if (settings == null || !settings.IsValid) { + var url = _urlHelper.Action("OpenId", "Admin", new { Area = "Settings" }); + yield return new NotifyEntry { Message = T("The Google settings need to be configured.", url), Type = NotifyType.Warning }; + } + } + } +} diff --git a/src/Orchard.Web/Modules/Orchard.OpenId/Services/IOpenIdProvider.cs b/src/Orchard.Web/Modules/Orchard.OpenId/Services/IOpenIdProvider.cs new file mode 100644 index 000000000..25a8d48d0 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.OpenId/Services/IOpenIdProvider.cs @@ -0,0 +1,8 @@ +namespace Orchard.OpenId.Services { + public interface IOpenIdProvider : IDependency { + string AuthenticationType { get; } + string DisplayName { get; } + bool IsValid { get; } + string Name { get; } + } +} diff --git a/src/Orchard.Web/Modules/Orchard.OpenId/Services/OpenIdAuthenticationService.cs b/src/Orchard.Web/Modules/Orchard.OpenId/Services/OpenIdAuthenticationService.cs new file mode 100644 index 000000000..42d6942d0 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.OpenId/Services/OpenIdAuthenticationService.cs @@ -0,0 +1,117 @@ +using System.Collections.Generic; +using System.Linq; +using System.Web; +using System.Web.Security; +using Orchard.Environment.Configuration; +using Orchard.Environment.Extensions; +using Orchard.Mvc; +using Orchard.Security; +using Orchard.Security.Providers; +using Orchard.Services; + +namespace Orchard.OpenId.Services { + [OrchardFeature("Orchard.OpenId")] + public class OpenIdAuthenticationService : IAuthenticationService { + private readonly ShellSettings _settings; + private readonly IClock _clock; + private readonly IMembershipService _membershipService; + private readonly ISslSettingsProvider _sslSettingsProvider; + private readonly IHttpContextAccessor _httpContextAccessor; + private readonly IMembershipValidationService _membershipValidationService; + private readonly IEnumerable _openIdProviders; + + private IUser _localAuthenticationUser; + + IAuthenticationService _fallbackAuthenticationService; + private IAuthenticationService FallbackAuthenticationService { + get { + if (_fallbackAuthenticationService == null) + _fallbackAuthenticationService = new FormsAuthenticationService(_settings, _clock, _membershipService, _httpContextAccessor, _sslSettingsProvider, _membershipValidationService); + + return _fallbackAuthenticationService; + } + } + + public OpenIdAuthenticationService( + ShellSettings settings, + IClock clock, + IMembershipService membershipService, + ISslSettingsProvider sslSettingsProvider, + IHttpContextAccessor httpContextAccessor, + IMembershipValidationService membershipValidationService, + IEnumerable openIdProviders) { + + _httpContextAccessor = httpContextAccessor; + _membershipService = membershipService; + _settings = settings; + _clock = clock; + _sslSettingsProvider = sslSettingsProvider; + _membershipValidationService = membershipValidationService; + _openIdProviders = openIdProviders; + } + + public void SignIn(IUser user, bool createPersistentCookie) { + if (IsFallbackNeeded()) { + FallbackAuthenticationService.SignIn(user, createPersistentCookie); + } + } + + public void SignOut() { + if (IsFallbackNeeded()) { + FallbackAuthenticationService.SignOut(); + } + } + + public void SetAuthenticatedUserForRequest(IUser user) { + if (IsFallbackNeeded()) { + FallbackAuthenticationService.SetAuthenticatedUserForRequest(user); + } + } + + public IUser GetAuthenticatedUser() { + if (IsFallbackNeeded()) { + return FallbackAuthenticationService.GetAuthenticatedUser(); + } + + var user = _httpContextAccessor.Current().GetOwinContext().Authentication.User; + + if (!user.Identity.IsAuthenticated) { + return null; + } + + // In memory caching of sorts since this method gets called many times per request + if (_localAuthenticationUser != null) { + return _localAuthenticationUser; + } + + var userName = user.Identity.Name.Trim(); + + //Get the local user, if local user account doesn't exist, create it + var localUser = + _membershipService.GetUser(userName) ?? + _membershipService.CreateUser(new CreateUserParams( + userName, Membership.GeneratePassword(16, 1), userName, string.Empty, string.Empty, true + )); + + return _localAuthenticationUser = localUser; + } + + private bool IsLocalUser() { + var anyClaim = _httpContextAccessor.Current().GetOwinContext().Authentication.User.Claims.FirstOrDefault(); + + if (anyClaim == null || anyClaim.Issuer == Constants.General.LocalIssuer || anyClaim.Issuer == Constants.General.FormsIssuer) { + return true; + } + + return false; + } + + private bool IsAnyProviderSettingsValid() { + return _openIdProviders.Any(provider => provider.IsValid); + } + + private bool IsFallbackNeeded() { + return IsLocalUser() || !IsAnyProviderSettingsValid(); + } + } +} \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.OpenId/Services/Twitter/MissingSettingsBanner.cs b/src/Orchard.Web/Modules/Orchard.OpenId/Services/Twitter/MissingSettingsBanner.cs new file mode 100644 index 000000000..abb8600a2 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.OpenId/Services/Twitter/MissingSettingsBanner.cs @@ -0,0 +1,36 @@ +using System.Collections.Generic; +using System.Web.Mvc; +using Orchard.ContentManagement; +using Orchard.Environment.Extensions; +using Orchard.Localization; +using Orchard.OpenId.Models; +using Orchard.UI.Admin.Notification; +using Orchard.UI.Notify; + +namespace Orchard.Azure.Authentication.Services.Twitter { + [OrchardFeature("Orchard.OpenId.Twitter")] + public class MissingSettingsBanner : INotificationProvider { + private readonly IOrchardServices _orchardServices; + private readonly UrlHelper _urlHelper; + + public MissingSettingsBanner(IOrchardServices orchardServices, UrlHelper urlHelper) + { + _orchardServices = orchardServices; + _urlHelper = urlHelper; + + T = NullLocalizer.Instance; + } + + public Localizer T { get; set; } + + public IEnumerable GetNotifications() { + var workContext = _orchardServices.WorkContext; + var settings = workContext.CurrentSite.As(); + + if (settings == null || !settings.IsValid) { + var url = _urlHelper.Action("OpenId", "Admin", new { Area = "Settings" }); + yield return new NotifyEntry { Message = T("The Twitter settings need to be configured.", url), Type = NotifyType.Warning }; + } + } + } +} diff --git a/src/Orchard.Web/Modules/Orchard.OpenId/Styles/Web.config b/src/Orchard.Web/Modules/Orchard.OpenId/Styles/Web.config new file mode 100644 index 000000000..176fe49cd --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.OpenId/Styles/Web.config @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/Orchard.Web/Modules/Orchard.OpenId/Styles/twitter-admin.css b/src/Orchard.Web/Modules/Orchard.OpenId/Styles/twitter-admin.css new file mode 100644 index 000000000..ee2a4218c --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.OpenId/Styles/twitter-admin.css @@ -0,0 +1,26 @@ +.coll-exp-control { + width: 100%; + cursor: pointer; +} + +.coll-exp-button { + font-weight: bold; + font-weight: bold; + padding: 3px; + background: white; + color: black; + border-radius: 50%; + width: 24px; + height: 24px; +} + +.coll-exp-button:hover { + background: #f0f0f0; + color: black; +} + +.coll-exp-area { + display: none; + background: #f9f9f9; + padding: 3px 7px; +} diff --git a/src/Orchard.Web/Modules/Orchard.OpenId/Views/Account/AccessDenied.cshtml b/src/Orchard.Web/Modules/Orchard.OpenId/Views/Account/AccessDenied.cshtml new file mode 100644 index 000000000..b5adf7a78 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.OpenId/Views/Account/AccessDenied.cshtml @@ -0,0 +1,7 @@ +@model dynamic +
+

@Html.TitleForPage(T("Access Denied").ToString())

+
+
+

@T("You do not have permission to complete your request.")

+
\ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.OpenId/Views/Account/Error.cshtml b/src/Orchard.Web/Modules/Orchard.OpenId/Views/Account/Error.cshtml new file mode 100644 index 000000000..d4190517e --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.OpenId/Views/Account/Error.cshtml @@ -0,0 +1 @@ +@T("Oops, Something went wrong with your authentication!") diff --git a/src/Orchard.Web/Modules/Orchard.OpenId/Views/Account/Logon.cshtml b/src/Orchard.Web/Modules/Orchard.OpenId/Views/Account/Logon.cshtml new file mode 100644 index 000000000..d2e61b04c --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.OpenId/Views/Account/Logon.cshtml @@ -0,0 +1,39 @@ +@model IEnumerable +@using Orchard.OpenId.Services +@using Orchard.Utility.Extensions + +

Logon

+
+
+@using (Html.BeginFormAntiForgeryPost(Url.Action("LogOn", "Account", new { Area = "Orchard.Users", ReturnUrl = Request.QueryString["ReturnUrl"] }))) { + +} +
+
Or choose your OpenId account provider
+
+ +@foreach (var provider in Model) { + if (provider.IsValid) { + + @provider.DisplayName + + } +} diff --git a/src/Orchard.Web/Modules/Orchard.OpenId/Views/EditorTemplates/Parts.ActiveDirectoryFederationServicesSettings.cshtml b/src/Orchard.Web/Modules/Orchard.OpenId/Views/EditorTemplates/Parts.ActiveDirectoryFederationServicesSettings.cshtml new file mode 100644 index 000000000..5e2b8a4c2 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.OpenId/Views/EditorTemplates/Parts.ActiveDirectoryFederationServicesSettings.cshtml @@ -0,0 +1,20 @@ +@model Orchard.OpenId.Models.ActiveDirectoryFederationServicesSettingsPart +@using Orchard.OpenId + +

Active Directory Federation Services Settings

+
+ @Html.LabelFor(m => m.ClientId, T("Client Id")) + @Html.TextBoxFor(m => m.ClientId, new { @class = "text large" }) + @T("ADFS's Client Id obtained from your ADFS configuration (e.g. {0})", Constants.DefaultAdfsClientId) +
+
+ @Html.LabelFor(m => m.MetadataAddress, T("Metadata Address")) + @Html.TextBoxFor(m => m.MetadataAddress, new { @class = "text large" }) + @T("ADFS's Metadata Address url obtained from your ADFS configuration (e.g. {0})", Constants.DefaultAdfsMetadataAddress) +
+
+ @Html.LabelFor(m => m.PostLogoutRedirectUri, T("Post Logout Redirect Uri")) + @Html.TextBoxFor(m => m.PostLogoutRedirectUri, new { @class = "text large" }) + @T("ADFS's Post Logout Redirect url obtained from your ADFS configuration (e.g. {0})", Constants.DefaultAdfsPostLogoutRedirectUri) +
+
\ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.OpenId/Views/EditorTemplates/Parts.AzureActiveDirectorySettings.cshtml b/src/Orchard.Web/Modules/Orchard.OpenId/Views/EditorTemplates/Parts.AzureActiveDirectorySettings.cshtml new file mode 100644 index 000000000..e014536fb --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.OpenId/Views/EditorTemplates/Parts.AzureActiveDirectorySettings.cshtml @@ -0,0 +1,63 @@ +@model Orchard.OpenId.Models.AzureActiveDirectorySettingsPart + +

Azure Active Directory Settings

+
+ @Html.LabelFor(m => m.Tenant, T("Tenant")) + @Html.TextBoxFor(m => m.Tenant, new { @class = "text large" }) + @T("Azure Active Directory tenant (e.g. yoursite.onmicrosoft.com).") +
+
+ @Html.LabelFor(m => m.ADInstance, T("Active Directory Instance")) + @Html.TextBoxFor(m => m.ADInstance, new { @class = "text large" }) + @T("Default instance is https://login.microsoftonline.com/{your-tenant-name}") +
+
+ @Html.LabelFor(m => m.ClientId, T("App ID")) + @Html.TextBoxFor(m => m.ClientId, new { @class = "text large" }) +
+
+ @Html.LabelFor(m => m.AppName, T("App Name")) + @Html.TextBoxFor(m => m.AppName, new { @class = "text large" }) + @T("The application name you wish to give active directory login rights to.") +
+
+ @Html.LabelFor(m => m.LogoutRedirectUri, T("Logout Redirect")) + @Html.TextBoxFor(m => m.LogoutRedirectUri, new { @class = "text large" }) + @T("Redirect url after azure logout, default is http://localhost:30321/OrchardLocal/") +
+
+ @Html.LabelFor(m => m.ServiceResourceID, T("Service Resource ID")) + @Html.TextAreaFor(m => m.ServiceResourceID, new { @class = "text large" }) + + @T(@"If you have a single 'Service Resource ID' just write it down directly. + If you have multiple resources, enter each resource id on its own line, using key=value pairs. + Example: service1=https://yoursite.onmicrosoft.com/some-guid-for-service1") + +
+
+ @Html.LabelFor(m => m.AppKey, T("App Key")) + @Html.TextBoxFor(m => m.AppKey, new { @class = "text large" }) +
+
+ @Html.CheckBoxFor(m => m.BearerAuthEnabled) + +
+
+ @Html.CheckBoxFor(m => m.SSLEnabled) + +
+
+ @Html.CheckBoxFor(m => m.AzureWebSiteProtectionEnabled) + +
+
+ @Html.CheckBoxFor(m => m.UseAzureGraphApi) + + @T("Check this box to enable syncing Orchard Role membership to Azure Graph API Group Membership. This module will not create new Orchard Roles for you, but it will sync up user membership of existing Orchard Roles with AD Group membership for Role names that match a group name") +
+
+ @Html.LabelFor(m => m.GraphApiUrl, T("Graph API URL")) + @Html.TextBoxFor(m => m.GraphApiUrl, new { @class = "text large" }) + @T("Typically https://graph.windows.net") +
+
\ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.OpenId/Views/EditorTemplates/Parts.FacebookSettings.cshtml b/src/Orchard.Web/Modules/Orchard.OpenId/Views/EditorTemplates/Parts.FacebookSettings.cshtml new file mode 100644 index 000000000..b354e3daf --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.OpenId/Views/EditorTemplates/Parts.FacebookSettings.cshtml @@ -0,0 +1,15 @@ +@model Orchard.OpenId.Models.FacebookSettingsPart +@using Orchard.OpenId + +

Facebook Settings

+
+ @Html.LabelFor(m => m.AppId, T("App Id")) + @Html.TextBoxFor(m => m.AppId, new { @class = "text large" }) + @T("Facebook's App Id obtained from your facebook developer dashboard (e.g. {0})", Constants.DefaultFacebookAppId) +
+
+ @Html.LabelFor(m => m.AppSecret, T("App Secret")) + @Html.TextBoxFor(m => m.AppSecret, new { @class = "text large" }) + @T("Facebook's App Secret obtained from your facebook developer dashboard (e.g. {0})", Constants.DefaultFacebookAppSecret) +
+
\ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.OpenId/Views/EditorTemplates/Parts.GoogleSettings.cshtml b/src/Orchard.Web/Modules/Orchard.OpenId/Views/EditorTemplates/Parts.GoogleSettings.cshtml new file mode 100644 index 000000000..dfba84f83 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.OpenId/Views/EditorTemplates/Parts.GoogleSettings.cshtml @@ -0,0 +1,20 @@ +@model Orchard.OpenId.Models.GoogleSettingsPart +@using Orchard.OpenId + +

Google Settings

+
+ @Html.LabelFor(m => m.ClientId, T("Client Id")) + @Html.TextBoxFor(m => m.ClientId, new { @class = "text large" }) + @T("Google's Client Id obtained from your google dashboard (e.g. {0})", Constants.DefaultGoogleClientId) +
+
+ @Html.LabelFor(m => m.ClientSecret, T("Client Secret")) + @Html.TextBoxFor(m => m.ClientSecret, new { @class = "text large" }) + @T("Google's Client Secret obtained from your google dashboard (e.g. {0})", Constants.DefaultGoogleClientSecret) +
+
+ @Html.LabelFor(m => m.CallbackPath, T("Callback Path")) + @Html.TextBoxFor(m => m.CallbackPath, new { @class = "text large" }) + @T("Google's Callback Path obtained from your google dashboard (case sensitive). Recommended: {0}", Constants.LogonCallbackUrl) +
+
\ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.OpenId/Views/EditorTemplates/Parts.TwitterSettings.cshtml b/src/Orchard.Web/Modules/Orchard.OpenId/Views/EditorTemplates/Parts.TwitterSettings.cshtml new file mode 100644 index 000000000..d4d29806f --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.OpenId/Views/EditorTemplates/Parts.TwitterSettings.cshtml @@ -0,0 +1,48 @@ +@model Orchard.OpenId.Models.TwitterSettingsPart +@using Orchard.OpenId; +@{ + Style.Include("TwitterAdmin").AtHead(); + + Script.Require("jQuery"); + Script.Require("TwitterAdmin").AtFoot(); +} + +

@T("Twitter Settings")

+
+ @Html.LabelFor(m => m.ConsumerKey, T("Consumer Key")) + @Html.TextBoxFor(m => m.ConsumerKey, new { @class = "text large" }) + @T("Twitter's Consumer Key obtained from your twitter dashboard (e.g. {0})", Constants.DefaultTwitterConsumerKey) +
+
+ @Html.LabelFor(m => m.ConsumerSecret, T("Consumer Secret")) + @Html.TextBoxFor(m => m.ConsumerSecret, new { @class = "text large" }) + @T("Twitter's Consumer Secret obtained from your twitter dashboard (e.g. {0})", Constants.DefaultTwitterConsumerSecret) +
+
+
+ @T("Certificate Subject Key Identifiers") + +
+
+ @T("These settings rarely change, it is recommended to keep default values") + + @Html.LabelFor(m => m.VeriSignClass3SecureServerCA_G2, T("VeriSign Class3 Secure Server CA - G2")) + @Html.TextBoxFor(m => m.VeriSignClass3SecureServerCA_G2, new { @class = "text large" }) + + @Html.LabelFor(m => m.VeriSignClass3SecureServerCA_G3, T("VeriSign Class3 Secure Server CA - G3")) + @Html.TextBoxFor(m => m.VeriSignClass3SecureServerCA_G3, new { @class = "text large" }) + + @Html.LabelFor(m => m.VeriSignClass3PublicPrimaryCA_G5, T("VeriSign Class3 Secure Server CA - G5")) + @Html.TextBoxFor(m => m.VeriSignClass3PublicPrimaryCA_G5, new { @class = "text large" }) + + @Html.LabelFor(m => m.SymantecClass3SecureServerCA_G4, T("Symantec Class3 Secure Server CA - G4")) + @Html.TextBoxFor(m => m.SymantecClass3SecureServerCA_G4, new { @class = "text large" }) + + @Html.LabelFor(m => m.DigiCertSHA2HighAssuranceServerCA, T("DigiCert SHA2 High Assurance Server CA")) + @Html.TextBoxFor(m => m.DigiCertSHA2HighAssuranceServerCA, new { @class = "text large" }) + + @Html.LabelFor(m => m.DigiCertHighAssuranceEVRootCA, T("DigiCert High Assurance EV Root CA")) + @Html.TextBoxFor(m => m.DigiCertHighAssuranceEVRootCA, new { @class = "text large" }) +
+
+
\ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.OpenId/Views/User.cshtml b/src/Orchard.Web/Modules/Orchard.OpenId/Views/User.cshtml new file mode 100644 index 000000000..b4415b4fe --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.OpenId/Views/User.cshtml @@ -0,0 +1,16 @@ +
+ @if (WorkContext.CurrentUser != null) { + + + } + else { + + } +
\ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.OpenId/Web.config b/src/Orchard.Web/Modules/Orchard.OpenId/Web.config new file mode 100644 index 000000000..a672c0116 --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.OpenId/Web.config @@ -0,0 +1,110 @@ + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Orchard.Web/Modules/Orchard.OpenId/packages.config b/src/Orchard.Web/Modules/Orchard.OpenId/packages.config new file mode 100644 index 000000000..426d92fcb --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.OpenId/packages.config @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Orchard.Web/Modules/Orchard.OpenId/placement.info b/src/Orchard.Web/Modules/Orchard.OpenId/placement.info new file mode 100644 index 000000000..22375f7eb --- /dev/null +++ b/src/Orchard.Web/Modules/Orchard.OpenId/placement.info @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/Orchard.Web/Orchard.Web.csproj b/src/Orchard.Web/Orchard.Web.csproj index affa5891c..b61922943 100644 --- a/src/Orchard.Web/Orchard.Web.csproj +++ b/src/Orchard.Web/Orchard.Web.csproj @@ -58,12 +58,12 @@ ..\packages\log4net.2.0.3\lib\net40-full\log4net.dll True - - ..\packages\Microsoft.Owin.3.0.0\lib\net45\Microsoft.Owin.dll + + ..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll True - - ..\packages\Microsoft.Owin.Host.SystemWeb.3.0.0\lib\net45\Microsoft.Owin.Host.SystemWeb.dll + + ..\packages\Microsoft.Owin.Host.SystemWeb.3.0.1\lib\net45\Microsoft.Owin.Host.SystemWeb.dll True diff --git a/src/Orchard.Web/Web.config b/src/Orchard.Web/Web.config index 13876a268..498935318 100644 --- a/src/Orchard.Web/Web.config +++ b/src/Orchard.Web/Web.config @@ -58,7 +58,7 @@ - + @@ -233,6 +233,26 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/src/Orchard.Web/packages.config b/src/Orchard.Web/packages.config index 58584142c..2d86de373 100644 --- a/src/Orchard.Web/packages.config +++ b/src/Orchard.Web/packages.config @@ -5,8 +5,8 @@ - - + + diff --git a/src/Orchard.sln b/src/Orchard.sln index 099fec29e..d39af0ee4 100644 --- a/src/Orchard.sln +++ b/src/Orchard.sln @@ -278,6 +278,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Orchard.Resources", "Orchar EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Orchard.Azure.Tests", "Orchard.Azure.Tests\Orchard.Azure.Tests.csproj", "{1CC62F45-E6FF-43D5-84BF-509A1085D994}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Orchard.OpenId", "Orchard.Web\Modules\Orchard.OpenId\Orchard.OpenId.csproj", "{42E217C1-E163-4B6B-9E8F-42BEE21B6896}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution CodeCoverage|Any CPU = CodeCoverage|Any CPU @@ -1114,6 +1116,13 @@ Global {1CC62F45-E6FF-43D5-84BF-509A1085D994}.FxCop|Any CPU.Build.0 = Release|Any CPU {1CC62F45-E6FF-43D5-84BF-509A1085D994}.Release|Any CPU.ActiveCfg = Release|Any CPU {1CC62F45-E6FF-43D5-84BF-509A1085D994}.Release|Any CPU.Build.0 = Release|Any CPU + {42E217C1-E163-4B6B-9E8F-42BEE21B6896}.CodeCoverage|Any CPU.ActiveCfg = Release|Any CPU + {42E217C1-E163-4B6B-9E8F-42BEE21B6896}.Coverage|Any CPU.ActiveCfg = Release|Any CPU + {42E217C1-E163-4B6B-9E8F-42BEE21B6896}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {42E217C1-E163-4B6B-9E8F-42BEE21B6896}.Debug|Any CPU.Build.0 = Debug|Any CPU + {42E217C1-E163-4B6B-9E8F-42BEE21B6896}.FxCop|Any CPU.ActiveCfg = Release|Any CPU + {42E217C1-E163-4B6B-9E8F-42BEE21B6896}.Release|Any CPU.ActiveCfg = Release|Any CPU + {42E217C1-E163-4B6B-9E8F-42BEE21B6896}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -1202,6 +1211,7 @@ Global {98251EAE-A41B-47B2-AA91-E28B8482DA70} = {E9C9F120-07BA-4DFB-B9C3-3AFB9D44C9D5} {D4E8F7C8-2DB2-4C50-A422-DA1DF1E3CC73} = {E9C9F120-07BA-4DFB-B9C3-3AFB9D44C9D5} {1CC62F45-E6FF-43D5-84BF-509A1085D994} = {74E681ED-FECC-4034-B9BD-01B0BB1BDECA} + {42E217C1-E163-4B6B-9E8F-42BEE21B6896} = {E9C9F120-07BA-4DFB-B9C3-3AFB9D44C9D5} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution EnterpriseLibraryConfigurationToolBinariesPath = packages\TransientFaultHandling.Core.5.1.1209.1\lib\NET4 diff --git a/src/Orchard/Orchard.Framework.csproj b/src/Orchard/Orchard.Framework.csproj index 402f93b97..498842d98 100644 --- a/src/Orchard/Orchard.Framework.csproj +++ b/src/Orchard/Orchard.Framework.csproj @@ -84,8 +84,8 @@ True - - ..\packages\Microsoft.Owin.3.0.0\lib\net45\Microsoft.Owin.dll + + ..\packages\Microsoft.Owin.3.0.1\lib\net45\Microsoft.Owin.dll True diff --git a/src/Orchard/packages.config b/src/Orchard/packages.config index 2a043ca44..2bfae8874 100644 --- a/src/Orchard/packages.config +++ b/src/Orchard/packages.config @@ -12,7 +12,7 @@ - +