Add OAuth2 support
This commit is contained in:
parent
ca17dd3ca3
commit
9d86d923aa
|
@ -21,7 +21,7 @@
|
|||
<div class="Oqtane-Modules-Admin-Login" @onkeypress="@(e => KeyPressed(e))">
|
||||
@if (_allowexternallogin)
|
||||
{
|
||||
<button type="button" class="btn btn-primary" @onclick="ExternalLogin">Use @PageState.Site.Settings["OpenIdConnectOptions:Provider"]</button>
|
||||
<button type="button" class="btn btn-primary" @onclick="ExternalLogin">Use @PageState.Site.Settings["ExternalLogin:ProviderName"]</button>
|
||||
<br /><br />
|
||||
}
|
||||
@if (_allowsitelogin)
|
||||
|
@ -95,12 +95,12 @@
|
|||
{
|
||||
_togglepassword = Localizer["ShowPassword"];
|
||||
|
||||
if (PageState.Site.Settings.ContainsKey("AllowSiteLogin") && !string.IsNullOrEmpty(PageState.Site.Settings["AllowSiteLogin"]))
|
||||
if (PageState.Site.Settings.ContainsKey("ExternalLogin:AllowSiteLogin") && !string.IsNullOrEmpty(PageState.Site.Settings["ExternalLogin:AllowSiteLogin"]))
|
||||
{
|
||||
_allowsitelogin = bool.Parse(PageState.Site.Settings["AllowSiteLogin"]);
|
||||
_allowsitelogin = bool.Parse(PageState.Site.Settings["ExternalLogin:AllowSiteLogin"]);
|
||||
}
|
||||
|
||||
if (PageState.Site.Settings.ContainsKey("OpenIdConnectOptions:Provider") && !string.IsNullOrEmpty(PageState.Site.Settings["OpenIdConnectOptions:Provider"]))
|
||||
if (PageState.Site.Settings.ContainsKey("ExternalLogin:ProviderType") && !string.IsNullOrEmpty(PageState.Site.Settings["ExternalLogin:ProviderType"]))
|
||||
{
|
||||
_allowexternallogin = true;
|
||||
}
|
||||
|
@ -269,7 +269,7 @@
|
|||
|
||||
private void ExternalLogin()
|
||||
{
|
||||
NavigationManager.NavigateTo(Utilities.TenantUrl(PageState.Alias, "/pages/oidc?returnurl=" + _returnUrl), true);
|
||||
NavigationManager.NavigateTo(Utilities.TenantUrl(PageState.Alias, "/pages/external?returnurl=" + _returnUrl), true);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -56,7 +56,7 @@ else
|
|||
<TabPanel Name="Settings" Heading="Settings" ResourceKey="Settings">
|
||||
<div class="container">
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="allowregistration" HelpText="Do You Want To Allow Visitors To Be Able To Register For A User Account On This Site?" ResourceKey="AllowRegistration">Allow User Registration? </Label>
|
||||
<Label Class="col-sm-3" For="allowregistration" HelpText="Do You Want To Allow Visitors To Be Able To Register For A User Account On This Site?" ResourceKey="AllowRegistration">Allow User Registration?</Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="allowregistration" class="form-select" @bind="@_allowregistration" required>
|
||||
<option value="True">@SharedLocalizer["Yes"]</option>
|
||||
|
@ -104,7 +104,7 @@ else
|
|||
<option value="false">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="requirepunctuation" HelpText="Indicate if Passwords Must Contain A Non-alphanumeric Character (ie. Punctuation)" ResourceKey="RequirePunctuation">Require Punctuation?</Label>
|
||||
<div class="col-sm-9">
|
||||
|
@ -131,62 +131,129 @@ else
|
|||
</Section>
|
||||
<Section Name="ExternalLogin" Heading="External Login Settings" ResourceKey="ExternalLoginSettings">
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="provider" HelpText="The OpenID Connect Provider Name. This Name Will Be Displayed On The Login Page" ResourceKey="Provider">Provider:</Label>
|
||||
<Label Class="col-sm-3" For="providertype" HelpText="Select the external login provider type" ResourceKey="ProviderType">Provider Type:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="provider" class="form-control" @bind="@_provider" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="authority" HelpText="The Authority Or Issuer URL Associated With The OpenID Connect Provider. " ResourceKey="Authority">Authority:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="authority" class="form-control" @bind="@_authority" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="clientid" HelpText="The OpenID Connect Client ID" ResourceKey="ClientID">Client ID:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="clientid" class="form-control" @bind="@_clientid" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="clientsecret" HelpText="The OpenID Connect Client Secret" ResourceKey="ClientSecret">Client Secret:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="clientsecret" class="form-control" @bind="@_clientsecret" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="redirecturl" HelpText="The Redirect Url (or Callback Url) Which May Need To Be Registered With The OpenID Connect Provider" ResourceKey="RedirectUrl">Redirect Url:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="redirecturl" class="form-control" @bind="@_redirecturl" readonly />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="emailclaimtype" HelpText="The Type Name For The Email Address Claim Provided By The OpenID Connect Provider" ResourceKey="EmailClaimType">Email Claim Type:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="emailclaimtype" class="form-control" @bind="@_emailclaimtype" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="metadata" HelpText="The Discovery Endpoint For Obtaining Metadata. Only Specify If The OpenID Connect Provider Does Not Use The Standard Approach (ie. /.well-known/openid-configuration)" ResourceKey="Metadata">Metadata Address:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="metadata" class="form-control" @bind="@_metadata" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="logouturl" HelpText="The Url For Logging Out The User From The OpenID Connect Provider. Only Specify If The OpenID Connect Provider Supports This Feature And You Do Not Want The User To Remain Signed In To The OpenID Connect Provider After Logging Out From The Site." ResourceKey="LogoutUrl">Logout Url:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="logouturl" class="form-control" @bind="@_logouturl" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="allowsitelogin" HelpText="Do You Want To Allow Users To Sign In Using A Username And Password That Is Managed Locally On This Site? Note That You Should Only Disable This Option If You Have Already Sucessfully Configured An External Login Provider, Or Else You May Lock Yourself Out Of This Site." ResourceKey="AllowSiteLogin">Allow Site Login? </Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="allowsitelogin" class="form-select" @bind="@_allowsitelogin" required>
|
||||
<option value="true">@SharedLocalizer["Yes"]</option>
|
||||
<option value="false">@SharedLocalizer["No"]</option>
|
||||
<select id="providertype" class="form-select" value="@_providertype" @onchange="(e => ProviderTypeChanged(e))">
|
||||
<option value="" selected>@Localizer["Not Specified"]</option>
|
||||
<option value="oidc">@Localizer["OpenID Connect"]</option>
|
||||
<option value="oauth2">@Localizer["OAuth 2.0"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@if (_providertype != "")
|
||||
{
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="providername" HelpText="The external login provider name which will be displayed on the login page" ResourceKey="ProviderName">Provider Name:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="providername" class="form-control" @bind="@_providername" />
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@if (_providertype == "oidc")
|
||||
{
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="authority" HelpText="The Authority Url or Issuer Url associated with the OpenID Connect provider" ResourceKey="Authority">Authority:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="authority" class="form-control" @bind="@_authority" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="metadataurl" HelpText="The discovery endpoint for obtaining metadata for this provider. Only specify if the OpenID Connect provider does not use the standard approach (ie. /.well-known/openid-configuration)" ResourceKey="MetadataUrl">Metadata Url:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="metadataurl" class="form-control" @bind="@_metadataurl" />
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@if (_providertype == "oauth2")
|
||||
{
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="authorizationurl" HelpText="The endpoint for obtaining an Authorization Code" ResourceKey="AuthorizationUrl">Authorization Url:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="authorizationurl" class="form-control" @bind="@_authorizationurl" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="tokenurl" HelpText="The endpoint for obtaining an Auth Token" ResourceKey="TokenUrl">Token Url:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="tokenurl" class="form-control" @bind="@_tokenurl" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="userinfourl" HelpText="The endpoint for obtaining user information. This should be an API or Page Url which contains the users email address." ResourceKey="UserInfoUrl">User Info Url:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="userinfourl" class="form-control" @bind="@_userinfourl" />
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@if (_providertype != "")
|
||||
{
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="clientid" HelpText="The Client ID from the provider" ResourceKey="ClientID">Client ID:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="clientid" class="form-control" @bind="@_clientid" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="clientsecret" HelpText="The Client Secret from the provider" ResourceKey="ClientSecret">Client Secret:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input type="password" id="clientsecret" class="form-control" @bind="@_clientsecret" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="scopes" HelpText="A list of Scopes to request from the provider (separated by commas). If none are specified, standard Scopes will be used by default." ResourceKey="Scopes">Scopes:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="scopes" class="form-control" @bind="@_scopes" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="pkce" HelpText="Indicate if the provider supports Proof Key for Code Exchange (PKCE)" ResourceKey="PKCE">Use PKCE?</Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="pkce" class="form-select" @bind="@_pkce" required>
|
||||
<option value="true">@SharedLocalizer["Yes"]</option>
|
||||
<option value="false">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="redirecturl" HelpText="The Redirect Url (or Callback Url) which usually needs to be registered with the provider" ResourceKey="RedirectUrl">Redirect Url:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="redirecturl" class="form-control" @bind="@_redirecturl" readonly />
|
||||
</div>
|
||||
</div>
|
||||
@if (_providertype == "oidc")
|
||||
{
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="emailclaimtype" HelpText="The type name for the email address claim provided by the provider" ResourceKey="EmailClaimType">Email Claim Type:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="emailclaimtype" class="form-control" @bind="@_emailclaimtype" />
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="domainfilter" HelpText="Provide any email domain filter criteria (separated by commas). Domains to exclude should be prefixed with an exclamation point (!). For example 'microsoft.com,!hotmail.com' would include microsoft.com email addresses but not hotmail.com email addresses." ResourceKey="DomainFilter">Domain Filter:</Label>
|
||||
<div class="col-sm-9">
|
||||
<input id="domainfilter" class="form-control" @bind="@_domainfilter" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="createusers" HelpText="Do you want new users to be created automatically? If you disable this option, users must already be registered on the site in order to sign in with their external login." ResourceKey="CreateUsers">Create New Users?</Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="createusers" class="form-select" @bind="@_createusers">
|
||||
<option value="true">@SharedLocalizer["Yes"]</option>
|
||||
<option value="false">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1 align-items-center">
|
||||
<Label Class="col-sm-3" For="allowsitelogin" HelpText="Do you want to allow users to sign in using a username and password that is managed locally on this site? Note that you should only disable this option if you have already sucessfully configured an external login provider, or else you may lock yourself out of the site." ResourceKey="AllowSiteLogin">Allow Site Login?</Label>
|
||||
<div class="col-sm-9">
|
||||
<select id="allowsitelogin" class="form-select" @bind="@_allowsitelogin">
|
||||
<option value="true">@SharedLocalizer["Yes"]</option>
|
||||
<option value="false">@SharedLocalizer["No"]</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</Section>
|
||||
</div>
|
||||
<br />
|
||||
|
@ -209,14 +276,22 @@ else
|
|||
private string _requirepunctuation;
|
||||
private string _maximumfailures;
|
||||
private string _lockoutduration;
|
||||
private string _provider;
|
||||
|
||||
private string _providertype;
|
||||
private string _providername;
|
||||
private string _authority;
|
||||
private string _metadataurl;
|
||||
private string _authorizationurl;
|
||||
private string _tokenurl;
|
||||
private string _userinfourl;
|
||||
private string _clientid;
|
||||
private string _clientsecret;
|
||||
private string _scopes;
|
||||
private string _pkce;
|
||||
private string _redirecturl;
|
||||
private string _emailclaimtype;
|
||||
private string _metadata;
|
||||
private string _logouturl;
|
||||
private string _domainfilter;
|
||||
private string _createusers;
|
||||
private string _allowsitelogin;
|
||||
|
||||
public override SecurityAccessLevel SecurityAccessLevel => SecurityAccessLevel.Admin;
|
||||
|
@ -229,6 +304,7 @@ else
|
|||
|
||||
_allowregistration = PageState.Site.AllowRegistration.ToString();
|
||||
var settings = await SettingService.GetSiteSettingsAsync(PageState.Site.SiteId);
|
||||
|
||||
_minimumlength = SettingService.GetSetting(settings, "IdentityOptions:Password:RequiredLength", "6");
|
||||
_uniquecharacters = SettingService.GetSetting(settings, "IdentityOptions:Password:RequiredUniqueChars", "1");
|
||||
_requiredigit = SettingService.GetSetting(settings, "IdentityOptions:Password:RequireDigit", "true");
|
||||
|
@ -237,15 +313,23 @@ else
|
|||
_requirepunctuation = SettingService.GetSetting(settings, "IdentityOptions:Password:RequireNonAlphanumeric", "true");
|
||||
_maximumfailures = SettingService.GetSetting(settings, "IdentityOptions:Lockout:MaxFailedAccessAttempts", "5");
|
||||
_lockoutduration = TimeSpan.Parse(SettingService.GetSetting(settings, "IdentityOptions:Lockout:DefaultLockoutTimeSpan", "00:05:00")).TotalMinutes.ToString();
|
||||
_provider = SettingService.GetSetting(settings, "OpenIdConnectOptions:Provider", "");
|
||||
_authority = SettingService.GetSetting(settings, "OpenIdConnectOptions:Authority", "");
|
||||
_clientid = SettingService.GetSetting(settings, "OpenIdConnectOptions:ClientId", "");
|
||||
_clientsecret = SettingService.GetSetting(settings, "OpenIdConnectOptions:ClientSecret", "");
|
||||
_redirecturl = PageState.Uri.Scheme + "://" + PageState.Alias.Name + "/signin-oidc";
|
||||
_emailclaimtype = SettingService.GetSetting(settings, "OpenIdConnectOptions:EmailClaimType", "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress");
|
||||
_metadata = SettingService.GetSetting(settings, "OpenIdConnectOptions:MetadataAddress", "");
|
||||
_logouturl = SettingService.GetSetting(settings, "OpenIdConnectOptions:LogoutUrl", "");
|
||||
_allowsitelogin = SettingService.GetSetting(settings, "AllowSiteLogin", "true");
|
||||
|
||||
_providertype = SettingService.GetSetting(settings, "ExternalLogin:ProviderType", "");
|
||||
_providername = SettingService.GetSetting(settings, "ExternalLogin:ProviderName", "");
|
||||
_authority = SettingService.GetSetting(settings, "ExternalLogin:Authority", "");
|
||||
_metadataurl = SettingService.GetSetting(settings, "ExternalLogin:MetadataUrl", "");
|
||||
_authorizationurl = SettingService.GetSetting(settings, "ExternalLogin:AuthorizationUrl", "");
|
||||
_tokenurl = SettingService.GetSetting(settings, "ExternalLogin:TokenUrl", "");
|
||||
_userinfourl = SettingService.GetSetting(settings, "ExternalLogin:UserInfoUrl", "");
|
||||
_clientid = SettingService.GetSetting(settings, "ExternalLogin:ClientId", "");
|
||||
_clientsecret = SettingService.GetSetting(settings, "ExternalLogin:ClientSecret", "");
|
||||
_scopes = SettingService.GetSetting(settings, "ExternalLogin:Scopes", "");
|
||||
_pkce = SettingService.GetSetting(settings, "ExternalLogin:PKCE", "false");
|
||||
_redirecturl = PageState.Uri.Scheme + "://" + PageState.Alias.Name + "/signin-" + _providertype;
|
||||
_emailclaimtype = SettingService.GetSetting(settings, "ExternalLogin:EmailClaimType", "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress");
|
||||
_domainfilter = SettingService.GetSetting(settings, "ExternalLogin:DomainFilter", "");
|
||||
_createusers = SettingService.GetSetting(settings, "ExternalLogin:CreateUsers", "true");
|
||||
_allowsitelogin = SettingService.GetSetting(settings, "ExternalLogin:AllowSiteLogin", "true");
|
||||
}
|
||||
|
||||
private List<UserRole> Search(string search)
|
||||
|
@ -324,14 +408,23 @@ else
|
|||
settings = SettingService.SetSetting(settings, "IdentityOptions:Password:RequireNonAlphanumeric", _requirepunctuation, true);
|
||||
settings = SettingService.SetSetting(settings, "IdentityOptions:Lockout:MaxFailedAccessAttempts", _maximumfailures, true);
|
||||
settings = SettingService.SetSetting(settings, "IdentityOptions:Lockout:DefaultLockoutTimeSpan", TimeSpan.FromMinutes(Convert.ToInt64(_lockoutduration)).ToString(), true);
|
||||
settings = SettingService.SetSetting(settings, "OpenIdConnectOptions:Provider", _provider, false);
|
||||
settings = SettingService.SetSetting(settings, "OpenIdConnectOptions:Authority", _authority, true);
|
||||
settings = SettingService.SetSetting(settings, "OpenIdConnectOptions:ClientId", _clientid, true);
|
||||
settings = SettingService.SetSetting(settings, "OpenIdConnectOptions:ClientSecret", _clientsecret, true);
|
||||
settings = SettingService.SetSetting(settings, "OpenIdConnectOptions:EmailClaimType", _emailclaimtype, true);
|
||||
settings = SettingService.SetSetting(settings, "OpenIdConnectOptions:MetadataAddress", _metadata, true);
|
||||
settings = SettingService.SetSetting(settings, "OpenIdConnectOptions:LogoutUrl", _logouturl, true);
|
||||
settings = SettingService.SetSetting(settings, "AllowSiteLogin", _allowsitelogin, false);
|
||||
|
||||
settings = SettingService.SetSetting(settings, "ExternalLogin:ProviderType", _providertype, false);
|
||||
settings = SettingService.SetSetting(settings, "ExternalLogin:ProviderName", _providername, false);
|
||||
settings = SettingService.SetSetting(settings, "ExternalLogin:Authority", _authority, true);
|
||||
settings = SettingService.SetSetting(settings, "ExternalLogin:MetadataUrl", _metadataurl, true);
|
||||
settings = SettingService.SetSetting(settings, "ExternalLogin:AuthorizationUrl", _authorizationurl, true);
|
||||
settings = SettingService.SetSetting(settings, "ExternalLogin:TokenUrl", _tokenurl, true);
|
||||
settings = SettingService.SetSetting(settings, "ExternalLogin:UserInfoUrl", _userinfourl, true);
|
||||
settings = SettingService.SetSetting(settings, "ExternalLogin:ClientId", _clientid, true);
|
||||
settings = SettingService.SetSetting(settings, "ExternalLogin:ClientSecret", _clientsecret, true);
|
||||
settings = SettingService.SetSetting(settings, "ExternalLogin:Scopes", _scopes, true);
|
||||
settings = SettingService.SetSetting(settings, "ExternalLogin:PKCE", _pkce, true);
|
||||
settings = SettingService.SetSetting(settings, "ExternalLogin:EmailClaimType", _emailclaimtype, true);
|
||||
settings = SettingService.SetSetting(settings, "ExternalLogin:DomainFilter", _domainfilter, true);
|
||||
settings = SettingService.SetSetting(settings, "ExternalLogin:CreateUsers", _createusers, true);
|
||||
settings = SettingService.SetSetting(settings, "ExternalLogin:AllowSiteLogin", _allowsitelogin, false);
|
||||
|
||||
await SettingService.UpdateSiteSettingsAsync(settings, site.SiteId);
|
||||
await SettingService.ClearSiteSettingsCacheAsync(site.SiteId);
|
||||
|
||||
|
@ -343,4 +436,19 @@ else
|
|||
AddModuleMessage(Localizer["Error.SaveSiteSettings"], MessageType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private void ProviderTypeChanged(ChangeEventArgs e)
|
||||
{
|
||||
_providertype = (string)e.Value;
|
||||
if (_providertype == "oidc")
|
||||
{
|
||||
_scopes = "openid,profile,email";
|
||||
}
|
||||
else
|
||||
{
|
||||
_scopes = "";
|
||||
}
|
||||
_redirecturl = PageState.Uri.Scheme + "://" + PageState.Alias.Name + "/signin-" + _providertype;
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -154,43 +154,43 @@
|
|||
<value>Roles</value>
|
||||
</data>
|
||||
<data name="LockoutDuration.HelpText" xml:space="preserve">
|
||||
<value>The Number Of Minutes A User Should Be Locked Out</value>
|
||||
<value>The number of minutes a user should be locked out</value>
|
||||
</data>
|
||||
<data name="LockoutDuration.Text" xml:space="preserve">
|
||||
<value>Lockout Duration:</value>
|
||||
</data>
|
||||
<data name="MaximumFailures.HelpText" xml:space="preserve">
|
||||
<value>The Maximum Number Of Sign In Attempts Before A User Is Locked Out</value>
|
||||
<value>The maximum number of sign in attempts before a user is locked out</value>
|
||||
</data>
|
||||
<data name="MaximumFailures.Text" xml:space="preserve">
|
||||
<value>Maximum Failures:</value>
|
||||
</data>
|
||||
<data name="RequireDigit.HelpText" xml:space="preserve">
|
||||
<value>Indicate If Passwords Must Contain A Digit</value>
|
||||
<value>Indicate if passwords must contain a digit</value>
|
||||
</data>
|
||||
<data name="RequireDigit.Text" xml:space="preserve">
|
||||
<value>Require Digit?</value>
|
||||
</data>
|
||||
<data name="RequiredLength.HelpText" xml:space="preserve">
|
||||
<value>The Minimum Length For A Password</value>
|
||||
<value>The minimum length for a password</value>
|
||||
</data>
|
||||
<data name="RequiredLength.Text" xml:space="preserve">
|
||||
<value>Minimum Length:</value>
|
||||
</data>
|
||||
<data name="RequireLower.HelpText" xml:space="preserve">
|
||||
<value>Indicate If Passwords Must Contain A Lower Case Character</value>
|
||||
<value>Indicate if passwords must contain a lower case character</value>
|
||||
</data>
|
||||
<data name="RequireLower.Text" xml:space="preserve">
|
||||
<value>Require Lowercase?</value>
|
||||
</data>
|
||||
<data name="RequirePunctuation.HelpText" xml:space="preserve">
|
||||
<value>Indicate if Passwords Must Contain A Non-alphanumeric Character (ie. Punctuation)</value>
|
||||
<value>Indicate if passwords must contain a non-alphanumeric character (ie. punctuation)</value>
|
||||
</data>
|
||||
<data name="RequirePunctuation.Text" xml:space="preserve">
|
||||
<value>Require Punctuation?</value>
|
||||
</data>
|
||||
<data name="RequireUpper.HelpText" xml:space="preserve">
|
||||
<value>Indicate If Passwords Must Contain An Upper Case Character</value>
|
||||
<value>Indicate if passwords must contain an upper case character</value>
|
||||
</data>
|
||||
<data name="RequireUpper.Text" xml:space="preserve">
|
||||
<value>Require Uppercase?</value>
|
||||
|
@ -199,9 +199,114 @@
|
|||
<value>Configuration Updated. Please Select Restart Application For These Changes To Be Activated.</value>
|
||||
</data>
|
||||
<data name="UniqueCharacters.HelpText" xml:space="preserve">
|
||||
<value>The Minimum Number Of Unique Characters Which A Password Must Contain</value>
|
||||
<value>The minimum number of unique characters which a password must contain</value>
|
||||
</data>
|
||||
<data name="UniqueCharacters.Text" xml:space="preserve">
|
||||
<value>Unique Characters:</value>
|
||||
</data>
|
||||
<data name="AllowSiteLogin.HelpText" xml:space="preserve">
|
||||
<value>Do you want to allow users to sign in using a username and password that is managed locally on this site? Note that you should only disable this option if you have already sucessfully configured an external login provider, or else you may lock yourself out of the site.</value>
|
||||
</data>
|
||||
<data name="AllowSiteLogin.Text" xml:space="preserve">
|
||||
<value>Allow Site Login?</value>
|
||||
</data>
|
||||
<data name="Authority.HelpText" xml:space="preserve">
|
||||
<value>The Authority Url or Issuer Url associated with the OpenID Connect provider</value>
|
||||
</data>
|
||||
<data name="Authority.Text" xml:space="preserve">
|
||||
<value>Authority:</value>
|
||||
</data>
|
||||
<data name="AuthorizationUrl.HelpText" xml:space="preserve">
|
||||
<value>The endpoint for obtaining an Authorization Code</value>
|
||||
</data>
|
||||
<data name="AuthorizationUrl.Text" xml:space="preserve">
|
||||
<value>Authorization Url:</value>
|
||||
</data>
|
||||
<data name="ClientID.HelpText" xml:space="preserve">
|
||||
<value>The Client ID from the provider</value>
|
||||
</data>
|
||||
<data name="ClientID.Text" xml:space="preserve">
|
||||
<value>Client ID:</value>
|
||||
</data>
|
||||
<data name="ClientSecret.HelpText" xml:space="preserve">
|
||||
<value>The Client Secret from the provider</value>
|
||||
</data>
|
||||
<data name="ClientSecret.Text" xml:space="preserve">
|
||||
<value>Client Secret:</value>
|
||||
</data>
|
||||
<data name="CreateUsers.HelpText" xml:space="preserve">
|
||||
<value>Do you want new users to be created automatically? If you disable this option, users must already be registered on the site in order to sign in with their external login.</value>
|
||||
</data>
|
||||
<data name="CreateUsers.Text" xml:space="preserve">
|
||||
<value>Create New Users?</value>
|
||||
</data>
|
||||
<data name="DomainFilter.HelpText" xml:space="preserve">
|
||||
<value>Provide any email domain filter criteria (separated by commas). Domains to exclude should be prefixed with an exclamation point (!). For example 'microsoft.com,!hotmail.com' would include microsoft.com email addresses but not hotmail.com email addresses.</value>
|
||||
</data>
|
||||
<data name="DomainFilter.Text" xml:space="preserve">
|
||||
<value>Domain Filter:</value>
|
||||
</data>
|
||||
<data name="EmailClaimType.HelpText" xml:space="preserve">
|
||||
<value>The type name for the email address claim provided by the provider</value>
|
||||
</data>
|
||||
<data name="EmailClaimType.Text" xml:space="preserve">
|
||||
<value>Email Claim Type:</value>
|
||||
</data>
|
||||
<data name="ExternalLoginSettings.Heading" xml:space="preserve">
|
||||
<value>External Login Settings</value>
|
||||
</data>
|
||||
<data name="LockoutSettings.Heading" xml:space="preserve">
|
||||
<value>Lockout Settings</value>
|
||||
</data>
|
||||
<data name="MetadataUrl.HelpText" xml:space="preserve">
|
||||
<value>The discovery endpoint for obtaining metadata for this provider. Only specify if the OpenID Connect provider does not use the standard approach (ie. /.well-known/openid-configuration)</value>
|
||||
</data>
|
||||
<data name="MetadataUrl.Text" xml:space="preserve">
|
||||
<value>Metadata Url:</value>
|
||||
</data>
|
||||
<data name="PasswordSettings.Heading" xml:space="preserve">
|
||||
<value>Password Settings</value>
|
||||
</data>
|
||||
<data name="PKCE.HelpText" xml:space="preserve">
|
||||
<value>Indicate if the provider supports Proof Key for Code Exchange (PKCE)</value>
|
||||
</data>
|
||||
<data name="PKCE.Text" xml:space="preserve">
|
||||
<value>Use PKCE?</value>
|
||||
</data>
|
||||
<data name="ProviderName.HelpText" xml:space="preserve">
|
||||
<value>The external login provider name which will be displayed on the login page</value>
|
||||
</data>
|
||||
<data name="ProviderName.Text" xml:space="preserve">
|
||||
<value>Provider Name:</value>
|
||||
</data>
|
||||
<data name="ProviderType.HelpText" xml:space="preserve">
|
||||
<value>Select the external login provider type</value>
|
||||
</data>
|
||||
<data name="ProviderType.Text" xml:space="preserve">
|
||||
<value>Provider Type:</value>
|
||||
</data>
|
||||
<data name="RedirectUrl.HelpText" xml:space="preserve">
|
||||
<value>The Redirect Url (or Callback Url) which usually needs to be registered with the provider</value>
|
||||
</data>
|
||||
<data name="RedirectUrl.Text" xml:space="preserve">
|
||||
<value>Redirect Url:</value>
|
||||
</data>
|
||||
<data name="Scopes.HelpText" xml:space="preserve">
|
||||
<value>A list of Scopes to request from the provider (separated by commas). If none are specified, standard Scopes will be used by default.</value>
|
||||
</data>
|
||||
<data name="Scopes.Text" xml:space="preserve">
|
||||
<value>Scopes:</value>
|
||||
</data>
|
||||
<data name="TokenUrl.HelpText" xml:space="preserve">
|
||||
<value>The endpoint for obtaining an Auth Token</value>
|
||||
</data>
|
||||
<data name="TokenUrl.Text" xml:space="preserve">
|
||||
<value>Token Url:</value>
|
||||
</data>
|
||||
<data name="UserInfoUrl.HelpText" xml:space="preserve">
|
||||
<value>The endpoint for obtaining user information. This should be an API or Page Url which contains the users email address.</value>
|
||||
</data>
|
||||
<data name="UserInfoUrl.Text" xml:space="preserve">
|
||||
<value>User Info Url:</value>
|
||||
</data>
|
||||
</root>
|
|
@ -15,6 +15,10 @@ using System.IO;
|
|||
using System.Collections.Generic;
|
||||
using Oqtane.Security;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Authentication.OAuth;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace Oqtane.Extensions
|
||||
{
|
||||
|
@ -36,152 +40,236 @@ namespace Oqtane.Extensions
|
|||
// site OpenIdConnect options
|
||||
builder.AddSiteOptions<OpenIdConnectOptions>((options, alias) =>
|
||||
{
|
||||
// default options
|
||||
options.SignInScheme = Constants.AuthenticationScheme; // identity cookie
|
||||
options.RequireHttpsMetadata = true;
|
||||
options.SaveTokens = true;
|
||||
options.GetClaimsFromUserInfoEndpoint = true;
|
||||
options.CallbackPath = string.IsNullOrEmpty(alias.Path) ? "/signin-oidc" : "/" + alias.Path + "/signin-oidc";
|
||||
options.ResponseType = OpenIdConnectResponseType.Code; // authorization code flow
|
||||
options.ResponseMode = OpenIdConnectResponseMode.FormPost; // recommended as most secure
|
||||
options.UsePkce = true;
|
||||
options.Scope.Add("openid"); // core claims
|
||||
options.Scope.Add("profile"); // name claims
|
||||
options.Scope.Add("email"); // email claim
|
||||
//options.Scope.Add("offline_access"); // refresh token
|
||||
if (alias.SiteSettings.GetValue("ExternalLogin:ProviderType", "") == "oidc")
|
||||
{
|
||||
// default options
|
||||
options.SignInScheme = Constants.AuthenticationScheme; // identity cookie
|
||||
options.RequireHttpsMetadata = true;
|
||||
options.SaveTokens = true;
|
||||
options.GetClaimsFromUserInfoEndpoint = true;
|
||||
options.CallbackPath = string.IsNullOrEmpty(alias.Path) ? "/signin-oidc" : "/" + alias.Path + "/signin-oidc";
|
||||
options.ResponseType = OpenIdConnectResponseType.Code; // authorization code flow
|
||||
options.ResponseMode = OpenIdConnectResponseMode.FormPost; // recommended as most secure
|
||||
|
||||
// cookie config is required to avoid Correlation Failed errors
|
||||
options.NonceCookie.SameSite = SameSiteMode.Unspecified;
|
||||
options.CorrelationCookie.SameSite = SameSiteMode.Unspecified;
|
||||
// cookie config is required to avoid Correlation Failed errors
|
||||
options.NonceCookie.SameSite = SameSiteMode.Unspecified;
|
||||
options.CorrelationCookie.SameSite = SameSiteMode.Unspecified;
|
||||
|
||||
// site options
|
||||
options.Authority = alias.SiteSettings.GetValue("OpenIdConnectOptions:Authority", options.Authority);
|
||||
options.ClientId = alias.SiteSettings.GetValue("OpenIdConnectOptions:ClientId", options.ClientId);
|
||||
options.ClientSecret = alias.SiteSettings.GetValue("OpenIdConnectOptions:ClientSecret", options.ClientSecret);
|
||||
options.MetadataAddress = alias.SiteSettings.GetValue("OpenIdConnectOptions:MetadataAddress", options.MetadataAddress);
|
||||
// site options
|
||||
options.Authority = alias.SiteSettings.GetValue("ExternalLogin:Authority", "");
|
||||
options.MetadataAddress = alias.SiteSettings.GetValue("ExternalLogin:MetadataUrl", "");
|
||||
options.ClientId = alias.SiteSettings.GetValue("ExternalLogin:ClientId", "");
|
||||
options.ClientSecret = alias.SiteSettings.GetValue("ExternalLogin:ClientSecret", "");
|
||||
options.UsePkce = bool.Parse(alias.SiteSettings.GetValue("ExternalLogin:PKCE", "false"));
|
||||
options.Scope.Clear();
|
||||
foreach (var scope in alias.SiteSettings.GetValue("ExternalLogin:Scopes", "openid,profile,email").Split(',', StringSplitOptions.RemoveEmptyEntries))
|
||||
{
|
||||
options.Scope.Add(scope);
|
||||
}
|
||||
|
||||
// openid connect events
|
||||
options.Events.OnTokenValidated = OnTokenValidated;
|
||||
options.Events.OnRedirectToIdentityProviderForSignOut = OnRedirectToIdentityProviderForSignOut;
|
||||
options.Events.OnAccessDenied = OnAccessDenied;
|
||||
options.Events.OnRemoteFailure = OnRemoteFailure;
|
||||
// openid connect events
|
||||
options.Events.OnTokenValidated = OnTokenValidated;
|
||||
options.Events.OnAccessDenied = OnAccessDenied;
|
||||
options.Events.OnRemoteFailure = OnRemoteFailure;
|
||||
}
|
||||
});
|
||||
|
||||
// site ChallengeScheme options
|
||||
builder.AddSiteOptions<AuthenticationOptions>((options, alias) =>
|
||||
// site OAuth2.0 options
|
||||
builder.AddSiteOptions<OAuthOptions>((options, alias) =>
|
||||
{
|
||||
if (alias.SiteSettings.GetValue("OpenIdConnectOptions:Authority", "") != "")
|
||||
if (alias.SiteSettings.GetValue("ExternalLogin:ProviderType", "") == "oauth2")
|
||||
{
|
||||
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
|
||||
// default options
|
||||
options.SignInScheme = Constants.AuthenticationScheme; // identity cookie
|
||||
options.CallbackPath = string.IsNullOrEmpty(alias.Path) ? "/signin-oauth2" : "/" + alias.Path + "/signin-oauth2";
|
||||
options.SaveTokens = true;
|
||||
|
||||
// site options
|
||||
options.AuthorizationEndpoint = alias.SiteSettings.GetValue("ExternalLogin:AuthorizationUrl", "");
|
||||
options.TokenEndpoint = alias.SiteSettings.GetValue("ExternalLogin:TokenUrl", "");
|
||||
options.UserInformationEndpoint = alias.SiteSettings.GetValue("ExternalLogin:UserInfoUrl", "");
|
||||
options.ClientId = alias.SiteSettings.GetValue("ExternalLogin:ClientId", "");
|
||||
options.ClientSecret = alias.SiteSettings.GetValue("ExternalLogin:ClientSecret", "");
|
||||
options.UsePkce = bool.Parse(alias.SiteSettings.GetValue("ExternalLogin:PKCE", "false"));
|
||||
options.Scope.Clear();
|
||||
foreach (var scope in alias.SiteSettings.GetValue("ExternalLogin:Scopes", "").Split(',', StringSplitOptions.RemoveEmptyEntries))
|
||||
{
|
||||
options.Scope.Add(scope);
|
||||
}
|
||||
|
||||
// cookie config is required to avoid Correlation Failed errors
|
||||
options.CorrelationCookie.SameSite = SameSiteMode.Unspecified;
|
||||
|
||||
// oauth2 events
|
||||
options.Events.OnCreatingTicket = OnCreatingTicket;
|
||||
options.Events.OnAccessDenied = OnAccessDenied;
|
||||
options.Events.OnRemoteFailure = OnRemoteFailure;
|
||||
}
|
||||
});
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
private static async Task OnCreatingTicket(OAuthCreatingTicketContext context)
|
||||
{
|
||||
// OAuth 2.0
|
||||
var email = "";
|
||||
if (context.Options.UserInformationEndpoint != "")
|
||||
{
|
||||
try
|
||||
{
|
||||
var request = new HttpRequestMessage(HttpMethod.Get, context.Options.UserInformationEndpoint);
|
||||
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
|
||||
request.Headers.UserAgent.Add(new ProductInfoHeaderValue(Constants.PackageId, Constants.Version));
|
||||
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", context.AccessToken);
|
||||
var response = await context.Backchannel.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, context.HttpContext.RequestAborted);
|
||||
response.EnsureSuccessStatusCode();
|
||||
var output = await response.Content.ReadAsStringAsync();
|
||||
|
||||
// get email address using Regex on the raw output (could be json or html)
|
||||
var regex = new Regex(@"\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*", RegexOptions.IgnoreCase);
|
||||
foreach (Match match in regex.Matches(output))
|
||||
{
|
||||
if (EmailValid(match.Value, context.HttpContext.GetAlias().SiteSettings.GetValue("ExternalLogin:DomainFilter", "")))
|
||||
{
|
||||
email = match.Value.ToLower();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var _logger = context.HttpContext.RequestServices.GetRequiredService<ILogManager>();
|
||||
_logger.Log(LogLevel.Error, "ExternalLogin", Enums.LogFunction.Security, "An Error Occurred Accessing The User Info Endpoint - {Error}", ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
// login user
|
||||
await LoginUser(email, context.HttpContext, context.Principal);
|
||||
}
|
||||
|
||||
private static async Task OnTokenValidated(TokenValidatedContext context)
|
||||
{
|
||||
var providerKey = context.Principal.FindFirstValue(ClaimTypes.NameIdentifier);
|
||||
var loginProvider = context.HttpContext.GetAlias().SiteSettings.GetValue("OpenIdConnectOptions:Authority", "");
|
||||
var emailClaimType = context.HttpContext.GetAlias().SiteSettings.GetValue("OpenIdConnectOptions:EmailClaimType", "");
|
||||
if (string.IsNullOrEmpty(emailClaimType))
|
||||
{
|
||||
emailClaimType = ClaimTypes.Email;
|
||||
}
|
||||
var alias = context.HttpContext.GetAlias();
|
||||
var _logger = context.HttpContext.RequestServices.GetRequiredService<ILogManager>();
|
||||
|
||||
// custom logic may be needed here to manipulate Principal sent by Provider - use interface similar to IClaimsTransformation
|
||||
|
||||
// OpenID Connect
|
||||
var emailClaimType = context.HttpContext.GetAlias().SiteSettings.GetValue("ExternalLogin:EmailClaimType", "");
|
||||
var email = context.Principal.FindFirstValue(emailClaimType);
|
||||
|
||||
// validate email claim
|
||||
if (email == null || !email.Contains("@") || !email.Contains("."))
|
||||
{
|
||||
var emailclaimtype = context.Principal.Claims.FirstOrDefault(item => item.Value.Contains("@") && item.Value.Contains("."));
|
||||
if (emailclaimtype != null)
|
||||
{
|
||||
email = emailclaimtype.Value;
|
||||
_logger.Log(LogLevel.Information, nameof(OqtaneSiteAuthenticationBuilderExtensions), Enums.LogFunction.Security, "Please Update The Email Claim Type For The OpenID Connect Provider To {EmailClaimType} In Site Settings", emailclaimtype.Type);
|
||||
}
|
||||
else
|
||||
{
|
||||
email = null;
|
||||
}
|
||||
}
|
||||
// login user
|
||||
await LoginUser(email, context.HttpContext, context.Principal);
|
||||
}
|
||||
|
||||
if (email != null)
|
||||
private static Task OnAccessDenied(AccessDeniedContext context)
|
||||
{
|
||||
var _logger = context.HttpContext.RequestServices.GetRequiredService<ILogManager>();
|
||||
_logger.Log(LogLevel.Information, "ExternalLogin", Enums.LogFunction.Security, "External Login Access Denied - User May Have Cancelled Their External Login Attempt");
|
||||
// redirect to login page
|
||||
var alias = context.HttpContext.GetAlias();
|
||||
context.Response.Redirect(alias.Path + "/login?returnurl=" + context.Properties.RedirectUri, true);
|
||||
context.HandleResponse();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private static Task OnRemoteFailure(RemoteFailureContext context)
|
||||
{
|
||||
var _logger = context.HttpContext.RequestServices.GetRequiredService<ILogManager>();
|
||||
_logger.Log(LogLevel.Error, "ExternalLogin", Enums.LogFunction.Security, "External Login Remote Failure - {Error}", context.Failure.Message);
|
||||
// redirect to login page
|
||||
var alias = context.HttpContext.GetAlias();
|
||||
context.Response.Redirect(alias.Path + "/login", true);
|
||||
context.HandleResponse();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private static async Task LoginUser(string email, HttpContext httpContext, ClaimsPrincipal claimsPrincipal)
|
||||
{
|
||||
var _logger = httpContext.RequestServices.GetRequiredService<ILogManager>();
|
||||
var alias = httpContext.GetAlias();
|
||||
|
||||
if (EmailValid(email, alias.SiteSettings.GetValue("ExternalLogin:DomainFilter", "")))
|
||||
{
|
||||
var _identityUserManager = context.HttpContext.RequestServices.GetRequiredService<UserManager<IdentityUser>>();
|
||||
var _users = context.HttpContext.RequestServices.GetRequiredService<IUserRepository>();
|
||||
var _userRoles = context.HttpContext.RequestServices.GetRequiredService<IUserRoleRepository>();
|
||||
var _identityUserManager = httpContext.RequestServices.GetRequiredService<UserManager<IdentityUser>>();
|
||||
var _users = httpContext.RequestServices.GetRequiredService<IUserRepository>();
|
||||
var _userRoles = httpContext.RequestServices.GetRequiredService<IUserRoleRepository>();
|
||||
var providerType = httpContext.GetAlias().SiteSettings.GetValue("ExternalLogin:ProviderType", "");
|
||||
var providerKey = claimsPrincipal.FindFirstValue(ClaimTypes.NameIdentifier);
|
||||
if (providerKey == null)
|
||||
{
|
||||
providerKey = email; // OAuth2 does not pass claims
|
||||
}
|
||||
User user = null;
|
||||
|
||||
var identityuser = await _identityUserManager.FindByEmailAsync(email);
|
||||
if (identityuser == null)
|
||||
{
|
||||
identityuser = new IdentityUser();
|
||||
identityuser.UserName = email;
|
||||
identityuser.Email = email;
|
||||
identityuser.EmailConfirmed = true;
|
||||
var result = await _identityUserManager.CreateAsync(identityuser, DateTime.UtcNow.ToString("yyyy-MMM-dd-HH-mm-ss"));
|
||||
if (result.Succeeded)
|
||||
if (bool.Parse(alias.SiteSettings.GetValue("ExternalLogin:CreateUsers", "true")))
|
||||
{
|
||||
// add user login
|
||||
await _identityUserManager.AddLoginAsync(identityuser, new UserLoginInfo(loginProvider, providerKey, email));
|
||||
|
||||
user = new User();
|
||||
user.SiteId = alias.SiteId;
|
||||
user.Username = email;
|
||||
user.DisplayName = email;
|
||||
user.Email = email;
|
||||
user.LastLoginOn = null;
|
||||
user.LastIPAddress = "";
|
||||
user = _users.AddUser(user);
|
||||
|
||||
// add folder for user
|
||||
var _folders = context.HttpContext.RequestServices.GetRequiredService<IFolderRepository>();
|
||||
Folder folder = _folders.GetFolder(user.SiteId, Utilities.PathCombine("Users", Path.DirectorySeparatorChar.ToString()));
|
||||
if (folder != null)
|
||||
identityuser = new IdentityUser();
|
||||
identityuser.UserName = email;
|
||||
identityuser.Email = email;
|
||||
identityuser.EmailConfirmed = true;
|
||||
var result = await _identityUserManager.CreateAsync(identityuser, DateTime.UtcNow.ToString("yyyy-MMM-dd-HH-mm-ss"));
|
||||
if (result.Succeeded)
|
||||
{
|
||||
_folders.AddFolder(new Folder
|
||||
// add user login
|
||||
await _identityUserManager.AddLoginAsync(identityuser, new UserLoginInfo(providerType, providerKey, ""));
|
||||
|
||||
user = new User();
|
||||
user.SiteId = alias.SiteId;
|
||||
user.Username = email;
|
||||
user.DisplayName = email;
|
||||
user.Email = email;
|
||||
user.LastLoginOn = null;
|
||||
user.LastIPAddress = "";
|
||||
user = _users.AddUser(user);
|
||||
|
||||
// add folder for user
|
||||
var _folders = httpContext.RequestServices.GetRequiredService<IFolderRepository>();
|
||||
Folder folder = _folders.GetFolder(user.SiteId, Utilities.PathCombine("Users", Path.DirectorySeparatorChar.ToString()));
|
||||
if (folder != null)
|
||||
{
|
||||
SiteId = folder.SiteId,
|
||||
ParentId = folder.FolderId,
|
||||
Name = "My Folder",
|
||||
Type = FolderTypes.Private,
|
||||
Path = Utilities.PathCombine(folder.Path, user.UserId.ToString(), Path.DirectorySeparatorChar.ToString()),
|
||||
Order = 1,
|
||||
ImageSizes = "",
|
||||
Capacity = Constants.UserFolderCapacity,
|
||||
IsSystem = true,
|
||||
Permissions = new List<Permission>
|
||||
_folders.AddFolder(new Folder
|
||||
{
|
||||
new Permission(PermissionNames.Browse, user.UserId, true),
|
||||
new Permission(PermissionNames.View, RoleNames.Everyone, true),
|
||||
new Permission(PermissionNames.Edit, user.UserId, true)
|
||||
}.EncodePermissions()
|
||||
});
|
||||
}
|
||||
SiteId = folder.SiteId,
|
||||
ParentId = folder.FolderId,
|
||||
Name = "My Folder",
|
||||
Type = FolderTypes.Private,
|
||||
Path = Utilities.PathCombine(folder.Path, user.UserId.ToString(), Path.DirectorySeparatorChar.ToString()),
|
||||
Order = 1,
|
||||
ImageSizes = "",
|
||||
Capacity = Constants.UserFolderCapacity,
|
||||
IsSystem = true,
|
||||
Permissions = new List<Permission>
|
||||
{
|
||||
new Permission(PermissionNames.Browse, user.UserId, true),
|
||||
new Permission(PermissionNames.View, RoleNames.Everyone, true),
|
||||
new Permission(PermissionNames.Edit, user.UserId, true)
|
||||
}.EncodePermissions()
|
||||
});
|
||||
}
|
||||
|
||||
// add auto assigned roles to user for site
|
||||
var _roles = context.HttpContext.RequestServices.GetRequiredService<IRoleRepository>();
|
||||
List<Role> roles = _roles.GetRoles(user.SiteId).Where(item => item.IsAutoAssigned).ToList();
|
||||
foreach (Role role in roles)
|
||||
{
|
||||
UserRole userrole = new UserRole();
|
||||
userrole.UserId = user.UserId;
|
||||
userrole.RoleId = role.RoleId;
|
||||
userrole.EffectiveDate = null;
|
||||
userrole.ExpiryDate = null;
|
||||
_userRoles.AddUserRole(userrole);
|
||||
// add auto assigned roles to user for site
|
||||
var _roles = httpContext.RequestServices.GetRequiredService<IRoleRepository>();
|
||||
List<Role> roles = _roles.GetRoles(user.SiteId).Where(item => item.IsAutoAssigned).ToList();
|
||||
foreach (Role role in roles)
|
||||
{
|
||||
UserRole userrole = new UserRole();
|
||||
userrole.UserId = user.UserId;
|
||||
userrole.RoleId = role.RoleId;
|
||||
userrole.EffectiveDate = null;
|
||||
userrole.ExpiryDate = null;
|
||||
_userRoles.AddUserRole(userrole);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Log(LogLevel.Error, "ExternalLogin", Enums.LogFunction.Security, "Creation Of New Users Is Disabled. User With Email Address {Email} Will First Need To Be Registered On The Site.", email);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var logins = await _identityUserManager.GetLoginsAsync(identityuser);
|
||||
var login = logins.FirstOrDefault(item => item.LoginProvider == loginProvider);
|
||||
var login = logins.FirstOrDefault(item => item.LoginProvider == providerType);
|
||||
if (login != null)
|
||||
{
|
||||
if (login.ProviderKey == providerKey)
|
||||
|
@ -191,13 +279,13 @@ namespace Oqtane.Extensions
|
|||
else
|
||||
{
|
||||
// provider keys do not match
|
||||
_logger.Log(LogLevel.Error, nameof(OqtaneSiteAuthenticationBuilderExtensions), Enums.LogFunction.Security, "OpenId Connect Provider Key Does Not Match For User {Email}. Login Denied.", email);
|
||||
_logger.Log(LogLevel.Error, "ExternalLogin", Enums.LogFunction.Security, "Provider Key Does Not Match For User {Username}. Login Denied.", identityuser.UserName);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// add user login
|
||||
await _identityUserManager.AddLoginAsync(identityuser, new UserLoginInfo(loginProvider, providerKey, identityuser.UserName));
|
||||
await _identityUserManager.AddLoginAsync(identityuser, new UserLoginInfo(providerType, providerKey, ""));
|
||||
user = _users.GetUser(identityuser.UserName);
|
||||
}
|
||||
}
|
||||
|
@ -207,75 +295,63 @@ namespace Oqtane.Extensions
|
|||
{
|
||||
// update user
|
||||
user.LastLoginOn = DateTime.UtcNow;
|
||||
user.LastIPAddress = context.HttpContext.Connection.RemoteIpAddress.ToString();
|
||||
user.LastIPAddress = httpContext.Connection.RemoteIpAddress.ToString();
|
||||
_users.UpdateUser(user);
|
||||
_logger.Log(LogLevel.Information, nameof(OqtaneSiteAuthenticationBuilderExtensions), Enums.LogFunction.Security, "User Login Successful {Username}", user.Username);
|
||||
|
||||
var principal = (ClaimsIdentity)context.Principal.Identity;
|
||||
|
||||
// remove the name claim if it exists in the principal
|
||||
var nameclaim = principal.Claims.FirstOrDefault(item => item.Type == ClaimTypes.Name);
|
||||
if (nameclaim != null)
|
||||
{
|
||||
principal.RemoveClaim(nameclaim);
|
||||
}
|
||||
_logger.Log(LogLevel.Information, "ExternalLogin", Enums.LogFunction.Security, "User Login Successful For {Username} Using Provider {Provider}", user.Username, providerType);
|
||||
|
||||
// add Oqtane claims
|
||||
var principal = (ClaimsIdentity)claimsPrincipal.Identity;
|
||||
UserSecurity.ResetClaimsIdentity(principal);
|
||||
List<UserRole> userroles = _userRoles.GetUserRoles(user.UserId, user.SiteId).ToList();
|
||||
var identity = UserSecurity.CreateClaimsIdentity(alias, user, userroles);
|
||||
principal.AddClaims(identity.Claims);
|
||||
|
||||
// add provider
|
||||
principal.AddClaim(new Claim("Provider", context.HttpContext.GetAlias().SiteSettings["OpenIdConnectOptions:Authority"]));
|
||||
}
|
||||
}
|
||||
else // no email claim
|
||||
{
|
||||
_logger.Log(LogLevel.Error, nameof(OqtaneSiteAuthenticationBuilderExtensions), Enums.LogFunction.Security, "OpenID Connect Provider Did Not Return An Email Claim To Uniquely Identify The User");
|
||||
}
|
||||
}
|
||||
|
||||
private static Task OnRedirectToIdentityProviderForSignOut(RedirectContext context)
|
||||
{
|
||||
var logoutUrl = context.HttpContext.GetAlias().SiteSettings.GetValue("OpenIdConnectOptions:LogoutUrl", "");
|
||||
if (logoutUrl != "")
|
||||
{
|
||||
var postLogoutUri = context.Properties.RedirectUri;
|
||||
if (!string.IsNullOrEmpty(postLogoutUri))
|
||||
else // user not logged in
|
||||
{
|
||||
if (postLogoutUri.StartsWith("/"))
|
||||
{
|
||||
var request = context.Request;
|
||||
postLogoutUri = request.Scheme + "://" + request.Host + request.PathBase + postLogoutUri;
|
||||
}
|
||||
logoutUrl += $"&returnTo={Uri.EscapeDataString(postLogoutUri)}";
|
||||
await httpContext.SignOutAsync();
|
||||
}
|
||||
context.Response.Redirect(logoutUrl);
|
||||
context.HandleResponse();
|
||||
}
|
||||
return Task.CompletedTask;
|
||||
else // email invalid
|
||||
{
|
||||
if (!string.IsNullOrEmpty(email))
|
||||
{
|
||||
_logger.Log(LogLevel.Error, "ExternalLogin", Enums.LogFunction.Security, "The Email Address {Email} Is Invalid Or Does Not Match The Domain Filter Criteria. Login Denied.", email);
|
||||
}
|
||||
else
|
||||
{
|
||||
var emailclaimtype = claimsPrincipal.Claims.FirstOrDefault(item => item.Value.Contains("@") && item.Value.Contains("."));
|
||||
if (emailclaimtype != null)
|
||||
{
|
||||
_logger.Log(LogLevel.Error, "ExternalLogin", Enums.LogFunction.Security, "Please Verify If \"{ClaimType}\" Is A Valid Email Claim Type For The Provider And Update Your External Login Settings Accordingly", emailclaimtype.Type);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.Log(LogLevel.Error, "ExternalLogin", Enums.LogFunction.Security, "Provider Did Not Return An Email To Uniquely Identify The User.");
|
||||
}
|
||||
}
|
||||
await httpContext.SignOutAsync();
|
||||
}
|
||||
}
|
||||
|
||||
private static Task OnAccessDenied(AccessDeniedContext context)
|
||||
private static bool EmailValid(string email, string domainfilter)
|
||||
{
|
||||
var _logger = context.HttpContext.RequestServices.GetRequiredService<ILogManager>();
|
||||
_logger.Log(LogLevel.Information, nameof(OqtaneSiteAuthenticationBuilderExtensions), Enums.LogFunction.Security, "OpenID Connect Access Denied - User May Have Cancelled Their External Login Attempt");
|
||||
// redirect to login page
|
||||
var alias = context.HttpContext.GetAlias();
|
||||
context.Response.Redirect(alias.Path + "/login?returnurl=" + context.Properties.RedirectUri);
|
||||
context.HandleResponse();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private static Task OnRemoteFailure(RemoteFailureContext context)
|
||||
{
|
||||
var _logger = context.HttpContext.RequestServices.GetRequiredService<ILogManager>();
|
||||
_logger.Log(LogLevel.Error, nameof(OqtaneSiteAuthenticationBuilderExtensions), Enums.LogFunction.Security, "OpenID Connect Remote Failure - {Error}", context.Failure.Message);
|
||||
// redirect to login page
|
||||
var alias = context.HttpContext.GetAlias();
|
||||
context.Response.Redirect(alias.Path + "/login?returnurl=" + context.Properties.RedirectUri);
|
||||
context.HandleResponse();
|
||||
return Task.CompletedTask;
|
||||
if (!string.IsNullOrEmpty(email) && email.Contains("@") && email.Contains("."))
|
||||
{
|
||||
var domains = domainfilter.ToLower().Split(',', StringSplitOptions.RemoveEmptyEntries);
|
||||
foreach (var domain in domains)
|
||||
{
|
||||
if (domain.StartsWith("!"))
|
||||
{
|
||||
if (email.ToLower().Contains(domain.Substring(1))) return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!email.ToLower().Contains(domain)) return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
3
Oqtane.Server/Pages/External.cshtml
Normal file
3
Oqtane.Server/Pages/External.cshtml
Normal file
|
@ -0,0 +1,3 @@
|
|||
@page "/pages/external"
|
||||
@namespace Oqtane.Pages
|
||||
@model Oqtane.Pages.ExternalModel
|
29
Oqtane.Server/Pages/External.cshtml.cs
Normal file
29
Oqtane.Server/Pages/External.cshtml.cs
Normal file
|
@ -0,0 +1,29 @@
|
|||
using System.Net;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
using Oqtane.Extensions;
|
||||
|
||||
namespace Oqtane.Pages
|
||||
{
|
||||
public class ExternalModel : PageModel
|
||||
{
|
||||
public IActionResult OnGetAsync(string returnurl)
|
||||
{
|
||||
returnurl = (returnurl == null) ? "/" : returnurl;
|
||||
returnurl = (!returnurl.StartsWith("/")) ? "/" + returnurl : returnurl;
|
||||
|
||||
var providertype = HttpContext.GetAlias().SiteSettings.GetValue("ExternalLogin:ProviderType", "");
|
||||
if (providertype != "")
|
||||
{
|
||||
return new ChallengeResult(providertype, new AuthenticationProperties { RedirectUri = returnurl });
|
||||
}
|
||||
else
|
||||
{
|
||||
HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden;
|
||||
return new EmptyResult();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -20,18 +20,7 @@ namespace Oqtane.Pages
|
|||
returnurl = (returnurl == null) ? "/" : returnurl;
|
||||
returnurl = (!returnurl.StartsWith("/")) ? "/" + returnurl : returnurl;
|
||||
|
||||
var provider = HttpContext.User.Claims.FirstOrDefault(item => item.Type == "Provider");
|
||||
var authority = HttpContext.GetAlias().SiteSettings.GetValue("OpenIdConnectOptions:Authority", "");
|
||||
var logoutUrl = HttpContext.GetAlias().SiteSettings.GetValue("OpenIdConnectOptions:LogoutUrl", "");
|
||||
if (provider != null && provider.Value == authority && logoutUrl != "")
|
||||
{
|
||||
return new SignOutResult(OpenIdConnectDefaults.AuthenticationScheme,
|
||||
new AuthenticationProperties { RedirectUri = returnurl });
|
||||
}
|
||||
else
|
||||
{
|
||||
return LocalRedirect(Url.Content("~" + returnurl));
|
||||
}
|
||||
return LocalRedirect(Url.Content("~" + returnurl));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
@page "/pages/oidc"
|
||||
@namespace Oqtane.Pages
|
||||
@model Oqtane.Pages.OIDCModel
|
|
@ -1,19 +0,0 @@
|
|||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.RazorPages;
|
||||
|
||||
namespace Oqtane.Pages
|
||||
{
|
||||
public class OIDCModel : PageModel
|
||||
{
|
||||
public IActionResult OnGetAsync(string returnurl)
|
||||
{
|
||||
returnurl = (returnurl == null) ? "/" : returnurl;
|
||||
returnurl = (!returnurl.StartsWith("/")) ? "/" + returnurl : returnurl;
|
||||
|
||||
return new ChallengeResult(OpenIdConnectDefaults.AuthenticationScheme,
|
||||
new AuthenticationProperties { RedirectUri = returnurl });
|
||||
}
|
||||
}
|
||||
}
|
|
@ -115,7 +115,8 @@ namespace Oqtane
|
|||
options.DefaultChallengeScheme = Constants.AuthenticationScheme;
|
||||
})
|
||||
.AddCookie(Constants.AuthenticationScheme)
|
||||
.AddOpenIdConnect();
|
||||
.AddOpenIdConnect("oidc", options => { })
|
||||
.AddOAuth("oauth2", options => { });
|
||||
|
||||
services.ConfigureOqtaneCookieOptions();
|
||||
|
||||
|
|
|
@ -45,7 +45,7 @@ namespace Oqtane.Security
|
|||
{
|
||||
if (user == null)
|
||||
{
|
||||
authorized = IsAuthorized(-1, "", permissions); // user is not authenticated but may have access to resource
|
||||
authorized = IsAuthorized(-1, "", permissions); // user is not authenticated but may have access to resource
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -152,5 +152,24 @@ namespace Oqtane.Security
|
|||
}
|
||||
return identity;
|
||||
}
|
||||
|
||||
public static void ResetClaimsIdentity(ClaimsIdentity identity)
|
||||
{
|
||||
var claim = identity.Claims.FirstOrDefault(item => item.Type == ClaimTypes.Name);
|
||||
if (claim != null)
|
||||
{
|
||||
identity.RemoveClaim(claim);
|
||||
}
|
||||
claim = identity.Claims.FirstOrDefault(item => item.Type == ClaimTypes.PrimarySid);
|
||||
if (claim != null)
|
||||
{
|
||||
identity.RemoveClaim(claim);
|
||||
}
|
||||
claim = identity.Claims.FirstOrDefault(item => item.Type == ClaimTypes.GroupSid);
|
||||
if (claim != null)
|
||||
{
|
||||
identity.RemoveClaim(claim);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user