simplify configuration of external login providers
This commit is contained in:
		| @ -182,11 +182,29 @@ else | ||||
| 						</div>					 | ||||
| 					</Section> | ||||
| 					<Section Name="ExternalLogin" Heading="External Login Settings" ResourceKey="ExternalLoginSettings"> | ||||
| 						<div class="row mb-1 align-items-center"> | ||||
|                         <div class="row mb-1 align-items-center"> | ||||
|                             <Label Class="col-sm-3" For="provider" HelpText="Select the external login provider" ResourceKey="Provider">Provider:</Label> | ||||
|                             <div class="col-sm-9"> | ||||
|                                 <div class="input-group"> | ||||
|                                     <select id="provider" class="form-select" value="@_provider" @onchange="(e => ProviderChanged(e))"> | ||||
|                                         @foreach (var provider in Shared.ExternalLoginProviders.Providers) | ||||
|                                         { | ||||
|                                             <option value="@provider.Name">@Localizer[provider.Name]</option> | ||||
|                                         } | ||||
|                                     </select> | ||||
|                                     @if (!string.IsNullOrEmpty(_providerurl)) | ||||
|                                     { | ||||
|                                         <a href="@_providerurl" class="btn btn-secondary" target="_new">@Localizer["Info"]</a> | ||||
|                                     } | ||||
|                                 </div> | ||||
|  | ||||
|                             </div> | ||||
|                         </div> | ||||
|                         <div class="row mb-1 align-items-center"> | ||||
| 							<Label Class="col-sm-3" For="providertype" HelpText="Select the external login provider type" ResourceKey="ProviderType">Provider Type:</Label> | ||||
| 							<div class="col-sm-9"> | ||||
| 								<select id="providertype" class="form-select" value="@_providertype" @onchange="(e => ProviderTypeChanged(e))"> | ||||
| 									<option value="" selected>@Localizer["Not Specified"]</option> | ||||
|                                     <option value="" selected><@Localizer["Not Specified"]></option> | ||||
| 									<option value="@AuthenticationProviderTypes.OpenIDConnect">@Localizer["OpenID Connect"]</option> | ||||
| 									<option value="@AuthenticationProviderTypes.OAuth2">@Localizer["OAuth 2.0"]</option> | ||||
| 								</select> | ||||
| @ -452,6 +470,8 @@ else | ||||
|     private string _maximumfailures; | ||||
|     private string _lockoutduration; | ||||
|  | ||||
|     private string _provider; | ||||
|     private string _providerurl; | ||||
|     private string _providertype; | ||||
|     private string _providername; | ||||
|     private string _authority; | ||||
| @ -519,33 +539,7 @@ else | ||||
|             _maximumfailures = SettingService.GetSetting(settings, "IdentityOptions:Lockout:MaxFailedAccessAttempts", "5"); | ||||
|             _lockoutduration = TimeSpan.Parse(SettingService.GetSetting(settings, "IdentityOptions:Lockout:DefaultLockoutTimeSpan", "00:05:00")).TotalMinutes.ToString(); | ||||
|  | ||||
|             _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", ""); | ||||
|             _toggleclientsecret = SharedLocalizer["ShowPassword"]; | ||||
|             _authresponsetype = SettingService.GetSetting(settings, "ExternalLogin:AuthResponseType", "code"); | ||||
|             _scopes = SettingService.GetSetting(settings, "ExternalLogin:Scopes", ""); | ||||
|             _parameters = SettingService.GetSetting(settings, "ExternalLogin:Parameters", ""); | ||||
|             _pkce = SettingService.GetSetting(settings, "ExternalLogin:PKCE", "false"); | ||||
|             _redirecturl = PageState.Uri.Scheme + "://" + PageState.Alias.Name + "/signin-" + _providertype; | ||||
|             _reviewclaims = SettingService.GetSetting(settings, "ExternalLogin:ReviewClaims", "false"); | ||||
|             _externalloginurl = Utilities.TenantUrl(PageState.Alias, "/pages/external"); | ||||
|             _identifierclaimtype = SettingService.GetSetting(settings, "ExternalLogin:IdentifierClaimType", "sub"); | ||||
|             _nameclaimtype = SettingService.GetSetting(settings, "ExternalLogin:NameClaimType", "name"); | ||||
|             _emailclaimtype = SettingService.GetSetting(settings, "ExternalLogin:EmailClaimType", "email"); | ||||
|             _roleclaimtype = SettingService.GetSetting(settings, "ExternalLogin:RoleClaimType", ""); | ||||
|             _roleclaimmappings = SettingService.GetSetting(settings, "ExternalLogin:RoleClaimMappings", ""); | ||||
|             _synchronizeroles = SettingService.GetSetting(settings, "ExternalLogin:SynchronizeRoles", "false"); | ||||
|             _profileclaimtypes = SettingService.GetSetting(settings, "ExternalLogin:ProfileClaimTypes", ""); | ||||
|             _domainfilter = SettingService.GetSetting(settings, "ExternalLogin:DomainFilter", ""); | ||||
|             _createusers = SettingService.GetSetting(settings, "ExternalLogin:CreateUsers", "true"); | ||||
|             _verifyusers = SettingService.GetSetting(settings, "ExternalLogin:VerifyUsers", "true"); | ||||
|             LoadExternalLoginSettings(settings); | ||||
|  | ||||
|             _secret = SettingService.GetSetting(settings, "JwtOptions:Secret", ""); | ||||
|             _togglesecret = SharedLocalizer["ShowPassword"]; | ||||
| @ -555,6 +549,39 @@ else | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private void LoadExternalLoginSettings(Dictionary<string, string> settings) | ||||
|     { | ||||
|         _provider = SettingService.GetSetting(settings, "ExternalLogin:Provider", "Custom"); | ||||
|         _providerurl = SettingService.GetSetting(settings, "ExternalLogin:ProviderUrl", ""); | ||||
|         _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", ""); | ||||
|         _toggleclientsecret = SharedLocalizer["ShowPassword"]; | ||||
|         _authresponsetype = SettingService.GetSetting(settings, "ExternalLogin:AuthResponseType", "code"); | ||||
|         _scopes = SettingService.GetSetting(settings, "ExternalLogin:Scopes", ""); | ||||
|         _parameters = SettingService.GetSetting(settings, "ExternalLogin:Parameters", ""); | ||||
|         _pkce = SettingService.GetSetting(settings, "ExternalLogin:PKCE", "false"); | ||||
|         _redirecturl = PageState.Uri.Scheme + "://" + PageState.Alias.Name + "/signin-" + _providertype; | ||||
|         _reviewclaims = SettingService.GetSetting(settings, "ExternalLogin:ReviewClaims", "false"); | ||||
|         _externalloginurl = Utilities.TenantUrl(PageState.Alias, "/pages/external"); | ||||
|         _identifierclaimtype = SettingService.GetSetting(settings, "ExternalLogin:IdentifierClaimType", "sub"); | ||||
|         _nameclaimtype = SettingService.GetSetting(settings, "ExternalLogin:NameClaimType", "name"); | ||||
|         _emailclaimtype = SettingService.GetSetting(settings, "ExternalLogin:EmailClaimType", "email"); | ||||
|         _roleclaimtype = SettingService.GetSetting(settings, "ExternalLogin:RoleClaimType", ""); | ||||
|         _roleclaimmappings = SettingService.GetSetting(settings, "ExternalLogin:RoleClaimMappings", ""); | ||||
|         _synchronizeroles = SettingService.GetSetting(settings, "ExternalLogin:SynchronizeRoles", "false"); | ||||
|         _profileclaimtypes = SettingService.GetSetting(settings, "ExternalLogin:ProfileClaimTypes", ""); | ||||
|         _domainfilter = SettingService.GetSetting(settings, "ExternalLogin:DomainFilter", ""); | ||||
|         _createusers = SettingService.GetSetting(settings, "ExternalLogin:CreateUsers", "true"); | ||||
|         _verifyusers = SettingService.GetSetting(settings, "ExternalLogin:VerifyUsers", "true"); | ||||
|     } | ||||
|  | ||||
|     private async Task LoadUsersAsync(bool load) | ||||
|     { | ||||
|         if (load) | ||||
| @ -567,105 +594,117 @@ else | ||||
|                 users = users.OrderBy(u => u.User.DisplayName).ToList(); | ||||
|             }            | ||||
|         } | ||||
| 	} | ||||
|     } | ||||
|  | ||||
| 	private async Task DeleteUser(UserRole UserRole) | ||||
| 	{ | ||||
| 		try | ||||
| 		{ | ||||
| 			var user = await UserService.GetUserAsync(UserRole.UserId, PageState.Site.SiteId); | ||||
| 			if (user != null) | ||||
| 			{ | ||||
| 				await UserService.DeleteUserAsync(user.UserId, PageState.Site.SiteId); | ||||
| 				await logger.LogInformation("User Deleted {User}", UserRole.User); | ||||
| 				await LoadUsersAsync(true); | ||||
| 				StateHasChanged(); | ||||
| 			} | ||||
| 		} | ||||
| 		catch (Exception ex) | ||||
| 		{ | ||||
| 			await logger.LogError(ex, "Error Deleting User {User} {Error}", UserRole.User, ex.Message); | ||||
| 			AddModuleMessage(ex.Message, MessageType.Error); | ||||
| 		} | ||||
| 	} | ||||
|     private async Task DeleteUser(UserRole UserRole) | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             var user = await UserService.GetUserAsync(UserRole.UserId, PageState.Site.SiteId); | ||||
|             if (user != null) | ||||
|             { | ||||
|                 await UserService.DeleteUserAsync(user.UserId, PageState.Site.SiteId); | ||||
|                 await logger.LogInformation("User Deleted {User}", UserRole.User); | ||||
|                 await LoadUsersAsync(true); | ||||
|                 StateHasChanged(); | ||||
|             } | ||||
|         } | ||||
|         catch (Exception ex) | ||||
|         { | ||||
|             await logger.LogError(ex, "Error Deleting User {User} {Error}", UserRole.User, ex.Message); | ||||
|             AddModuleMessage(ex.Message, MessageType.Error); | ||||
|         } | ||||
|     } | ||||
|  | ||||
| 	private async Task SaveSiteSettings() | ||||
| 	{ | ||||
| 		try | ||||
| 		{ | ||||
| 			var site = PageState.Site; | ||||
| 			site.AllowRegistration = bool.Parse(_allowregistration); | ||||
| 			await SiteService.UpdateSiteAsync(site); | ||||
|     private async Task SaveSiteSettings() | ||||
|     { | ||||
|         try | ||||
|         { | ||||
|             var site = PageState.Site; | ||||
|             site.AllowRegistration = bool.Parse(_allowregistration); | ||||
|             await SiteService.UpdateSiteAsync(site); | ||||
|  | ||||
| 			var settings = await SettingService.GetSiteSettingsAsync(site.SiteId); | ||||
| 			settings = SettingService.SetSetting(settings, "LoginOptions:AllowSiteLogin", _allowsitelogin, false); | ||||
|             var settings = await SettingService.GetSiteSettingsAsync(site.SiteId); | ||||
|             settings = SettingService.SetSetting(settings, "LoginOptions:AllowSiteLogin", _allowsitelogin, false); | ||||
|  | ||||
| 			if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host)) | ||||
| 			{ | ||||
| 				settings = SettingService.SetSetting(settings, "LoginOptions:TwoFactor", _twofactor, false); | ||||
| 				settings = SettingService.SetSetting(settings, "LoginOptions:CookieName", _cookiename, true); | ||||
| 				settings = SettingService.SetSetting(settings, "LoginOptions:CookieExpiration", _cookieexpiration, true); | ||||
| 				settings = SettingService.SetSetting(settings, "LoginOptions:AlwaysRemember", _alwaysremember, false); | ||||
|             if (UserSecurity.IsAuthorized(PageState.User, RoleNames.Host)) | ||||
|             { | ||||
|                 settings = SettingService.SetSetting(settings, "LoginOptions:TwoFactor", _twofactor, false); | ||||
|                 settings = SettingService.SetSetting(settings, "LoginOptions:CookieName", _cookiename, true); | ||||
|                 settings = SettingService.SetSetting(settings, "LoginOptions:CookieExpiration", _cookieexpiration, true); | ||||
|                 settings = SettingService.SetSetting(settings, "LoginOptions:AlwaysRemember", _alwaysremember, false); | ||||
|  | ||||
| 				settings = SettingService.SetSetting(settings, "IdentityOptions:Password:RequiredLength", _minimumlength, true); | ||||
| 				settings = SettingService.SetSetting(settings, "IdentityOptions:Password:RequiredUniqueChars", _uniquecharacters, true); | ||||
| 				settings = SettingService.SetSetting(settings, "IdentityOptions:Password:RequireDigit", _requiredigit, true); | ||||
| 				settings = SettingService.SetSetting(settings, "IdentityOptions:Password:RequireUppercase", _requireupper, true); | ||||
| 				settings = SettingService.SetSetting(settings, "IdentityOptions:Password:RequireLowercase", _requirelower, true); | ||||
| 				settings = SettingService.SetSetting(settings, "IdentityOptions:Password:RequireNonAlphanumeric", _requirepunctuation, true); | ||||
|                 settings = SettingService.SetSetting(settings, "IdentityOptions:Password:RequiredLength", _minimumlength, true); | ||||
|                 settings = SettingService.SetSetting(settings, "IdentityOptions:Password:RequiredUniqueChars", _uniquecharacters, true); | ||||
|                 settings = SettingService.SetSetting(settings, "IdentityOptions:Password:RequireDigit", _requiredigit, true); | ||||
|                 settings = SettingService.SetSetting(settings, "IdentityOptions:Password:RequireUppercase", _requireupper, true); | ||||
|                 settings = SettingService.SetSetting(settings, "IdentityOptions:Password:RequireLowercase", _requirelower, true); | ||||
|                 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, "IdentityOptions:Lockout:MaxFailedAccessAttempts", _maximumfailures, true); | ||||
|                 settings = SettingService.SetSetting(settings, "IdentityOptions:Lockout:DefaultLockoutTimeSpan", TimeSpan.FromMinutes(Convert.ToInt64(_lockoutduration)).ToString(), true); | ||||
|  | ||||
| 				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:Provider", _provider, 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:AuthResponseType", _authresponsetype, true); | ||||
|                 settings = SettingService.SetSetting(settings, "ExternalLogin:Scopes", _scopes, true); | ||||
| 				settings = SettingService.SetSetting(settings, "ExternalLogin:Parameters", _parameters, true); | ||||
| 				settings = SettingService.SetSetting(settings, "ExternalLogin:PKCE", _pkce, true); | ||||
|                 settings = SettingService.SetSetting(settings, "ExternalLogin:Parameters", _parameters, true); | ||||
|                 settings = SettingService.SetSetting(settings, "ExternalLogin:PKCE", _pkce, true); | ||||
|                 settings = SettingService.SetSetting(settings, "ExternalLogin:ReviewClaims", _reviewclaims, true); | ||||
|                 settings = SettingService.SetSetting(settings, "ExternalLogin:IdentifierClaimType", _identifierclaimtype, true); | ||||
|                 settings = SettingService.SetSetting(settings, "ExternalLogin:NameClaimType", _nameclaimtype, true); | ||||
|                 settings = SettingService.SetSetting(settings, "ExternalLogin:EmailClaimType", _emailclaimtype, true); | ||||
| 				settings = SettingService.SetSetting(settings, "ExternalLogin:RoleClaimType", _roleclaimtype, true); | ||||
|                 settings = SettingService.SetSetting(settings, "ExternalLogin:RoleClaimType", _roleclaimtype, true); | ||||
|                 settings = SettingService.SetSetting(settings, "ExternalLogin:RoleClaimMappings", _roleclaimmappings, true); | ||||
|                 settings = SettingService.SetSetting(settings, "ExternalLogin:SynchronizeRoles", _synchronizeroles, true); | ||||
|                 settings = SettingService.SetSetting(settings, "ExternalLogin:ProfileClaimTypes", _profileclaimtypes, true); | ||||
| 				settings = SettingService.SetSetting(settings, "ExternalLogin:DomainFilter", _domainfilter, true); | ||||
| 				settings = SettingService.SetSetting(settings, "ExternalLogin:CreateUsers", _createusers, true); | ||||
|                 settings = SettingService.SetSetting(settings, "ExternalLogin:DomainFilter", _domainfilter, true); | ||||
|                 settings = SettingService.SetSetting(settings, "ExternalLogin:CreateUsers", _createusers, true); | ||||
|                 settings = SettingService.SetSetting(settings, "ExternalLogin:VerifyUsers", _verifyusers, true); | ||||
|  | ||||
| 				settings = SettingService.SetSetting(settings, "JwtOptions:Secret", _secret, true); | ||||
| 				settings = SettingService.SetSetting(settings, "JwtOptions:Issuer", _issuer, true); | ||||
| 				settings = SettingService.SetSetting(settings, "JwtOptions:Audience", _audience, true); | ||||
| 				settings = SettingService.SetSetting(settings, "JwtOptions:Lifetime", _lifetime, true); | ||||
| 			} | ||||
|                 settings = SettingService.SetSetting(settings, "JwtOptions:Secret", _secret, true); | ||||
|                 settings = SettingService.SetSetting(settings, "JwtOptions:Issuer", _issuer, true); | ||||
|                 settings = SettingService.SetSetting(settings, "JwtOptions:Audience", _audience, true); | ||||
|                 settings = SettingService.SetSetting(settings, "JwtOptions:Lifetime", _lifetime, true); | ||||
|             } | ||||
|  | ||||
| 			await SettingService.UpdateSiteSettingsAsync(settings, site.SiteId); | ||||
| 			await SettingService.ClearSiteSettingsCacheAsync(); | ||||
|             await SettingService.UpdateSiteSettingsAsync(settings, site.SiteId); | ||||
|             await SettingService.ClearSiteSettingsCacheAsync(); | ||||
|  | ||||
| 			if (!string.IsNullOrEmpty(_secret)) | ||||
| 			{ | ||||
| 				SiteState.AuthorizationToken = await UserService.GetTokenAsync(); | ||||
| 			} | ||||
|             if (!string.IsNullOrEmpty(_secret)) | ||||
|             { | ||||
|                 SiteState.AuthorizationToken = await UserService.GetTokenAsync(); | ||||
|             } | ||||
|  | ||||
| 			AddModuleMessage(Localizer["Success.SaveSiteSettings"], MessageType.Success);				 | ||||
| 		} | ||||
| 		catch (Exception ex) | ||||
| 		{ | ||||
| 			await logger.LogError(ex, "Error Saving Site Settings {Error}", ex.Message); | ||||
| 			AddModuleMessage(Localizer["Error.SaveSiteSettings"], MessageType.Error); | ||||
| 		} | ||||
|             AddModuleMessage(Localizer["Success.SaveSiteSettings"], MessageType.Success);				 | ||||
|         } | ||||
|         catch (Exception ex) | ||||
|         { | ||||
|             await logger.LogError(ex, "Error Saving Site Settings {Error}", ex.Message); | ||||
|             AddModuleMessage(Localizer["Error.SaveSiteSettings"], MessageType.Error); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private void ProviderChanged(ChangeEventArgs e) | ||||
|     { | ||||
|         _provider = (string)e.Value; | ||||
|         var provider = Shared.ExternalLoginProviders.Providers.FirstOrDefault(item => item.Name == _provider); | ||||
|         if (provider != null) | ||||
|         { | ||||
|             LoadExternalLoginSettings(provider.Settings); | ||||
|         } | ||||
|         StateHasChanged(); | ||||
| 	} | ||||
|  | ||||
| 	private void ProviderTypeChanged(ChangeEventArgs e) | ||||
|      | ||||
|     private void ProviderTypeChanged(ChangeEventArgs e) | ||||
| 	{ | ||||
| 		_providertype = (string)e.Value; | ||||
| 		if (string.IsNullOrEmpty(_providername)) | ||||
|  | ||||
		Reference in New Issue
	
	Block a user
	 sbwalker
					sbwalker