mirror of
				https://github.com/spring-projects/spring-security.git
				synced 2025-10-30 22:28:46 +00:00 
			
		
		
		
	review phase1
This commit is contained in:
		
							parent
							
								
									678e0b19e0
								
							
						
					
					
						commit
						570eb01733
					
				| @ -1,5 +1,5 @@ | |||||||
| /* | /* | ||||||
|  * Copyright 2002-2013 the original author or authors. |  * Copyright 2002-2019 the original author or authors. | ||||||
|  * |  * | ||||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); |  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||||
|  * you may not use this file except in compliance with the License. |  * you may not use this file except in compliance with the License. | ||||||
| @ -40,6 +40,7 @@ import java.util.List; | |||||||
|  * {@link HandlerMethodArgumentResolver}. |  * {@link HandlerMethodArgumentResolver}. | ||||||
|  * |  * | ||||||
|  * @author Rob Winch |  * @author Rob Winch | ||||||
|  |  * @author Dan Zheng | ||||||
|  * @since 3.2 |  * @since 3.2 | ||||||
|  */ |  */ | ||||||
| class WebMvcSecurityConfiguration implements WebMvcConfigurer, ApplicationContextAware { | class WebMvcSecurityConfiguration implements WebMvcConfigurer, ApplicationContextAware { | ||||||
|  | |||||||
| @ -1,5 +1,5 @@ | |||||||
| /* | /* | ||||||
|  * Copyright 2002-2018 the original author or authors. |  * Copyright 2002-2019 the original author or authors. | ||||||
|  * |  * | ||||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); |  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||||
|  * you may not use this file except in compliance with the License. |  * you may not use this file except in compliance with the License. | ||||||
| @ -39,6 +39,7 @@ import org.springframework.web.reactive.result.method.annotation.ArgumentResolve | |||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * @author Rob Winch |  * @author Rob Winch | ||||||
|  |  * @author Dan Zheng | ||||||
|  * @since 5.0 |  * @since 5.0 | ||||||
|  */ |  */ | ||||||
| @Configuration | @Configuration | ||||||
|  | |||||||
| @ -1,5 +1,5 @@ | |||||||
| /* | /* | ||||||
|  * Copyright 2002-2013 the original author or authors. |  * Copyright 2002-2019 the original author or authors. | ||||||
|  * |  * | ||||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); |  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||||
|  * you may not use this file except in compliance with the License. |  * you may not use this file except in compliance with the License. | ||||||
| @ -26,7 +26,7 @@ import java.lang.annotation.Target; | |||||||
|  * argument. |  * argument. | ||||||
|  * |  * | ||||||
|  * @author Dan Zheng |  * @author Dan Zheng | ||||||
|  * @since 5.2.x |  * @since 5.2 | ||||||
|  * |  * | ||||||
|  * See: <a href= |  * See: <a href= | ||||||
|  * "{@docRoot}/org/springframework/security/web/bind/support/CurrentSecurityContextArgumentResolver.html" |  * "{@docRoot}/org/springframework/security/web/bind/support/CurrentSecurityContextArgumentResolver.html" | ||||||
| @ -43,29 +43,21 @@ public @interface CurrentSecurityContext { | |||||||
| 	 * @return | 	 * @return | ||||||
| 	 */ | 	 */ | ||||||
| 	boolean errorOnInvalidType() default false; | 	boolean errorOnInvalidType() default false; | ||||||
|  | 
 | ||||||
| 	/** | 	/** | ||||||
| 	 * If specified will use the provided SpEL expression to resolve the security context. This | 	 * If specified will use the provided SpEL expression to resolve the security context. This | ||||||
| 	 * is convenient if users need to transform the result. | 	 * is convenient if users need to transform the result. | ||||||
| 	 * | 	 * | ||||||
|  | 	 * <pre> | ||||||
|  | 	 * @CurrentSecurityContext(expression = "authentication") Authentication authentication | ||||||
|  | 	 * </pre> | ||||||
|  | 	 * | ||||||
| 	 * <p> | 	 * <p> | ||||||
| 	 * For example, perhaps the user wants to resolve a CustomUser object that is final | 	 *    if you want to retrieve more object from the authentcation, you can see the following the expression | ||||||
| 	 * and is leveraging a UserDetailsService. This can be handled by returning an object |  | ||||||
| 	 * that looks like: |  | ||||||
| 	 * </p> | 	 * </p> | ||||||
| 	 * | 	 * | ||||||
| 	 * <pre> | 	 * <pre> | ||||||
| 	 * public class CustomUserUserDetails extends User { | 	 * @CurrentSecurityContext(expression = "authentication.principal") Object principal | ||||||
| 	 *     // ... |  | ||||||
| 	 *     public CustomUser getCustomUser() { |  | ||||||
| 	 *         return customUser; |  | ||||||
| 	 *     } |  | ||||||
| 	 * } |  | ||||||
| 	 * </pre> |  | ||||||
| 	 * |  | ||||||
| 	 * Then the user can specify an annotation that looks like: |  | ||||||
| 	 * |  | ||||||
| 	 * <pre> |  | ||||||
| 	 * @CurrentSecurityContext(expression = "authentication") |  | ||||||
| 	 * </pre> | 	 * </pre> | ||||||
| 	 * | 	 * | ||||||
| 	 * @return the expression to use. | 	 * @return the expression to use. | ||||||
|  | |||||||
| @ -1,5 +1,5 @@ | |||||||
| /* | /* | ||||||
|  * Copyright 2002-2013 the original author or authors. |  * Copyright 2002-2019 the original author or authors. | ||||||
|  * |  * | ||||||
|  * Licensed under the Apache License, Version 2.0 (the "License"); |  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||||
|  * you may not use this file except in compliance with the License. |  * you may not use this file except in compliance with the License. | ||||||
| @ -15,6 +15,8 @@ | |||||||
|  */ |  */ | ||||||
| package org.springframework.security.web.bind.support; | package org.springframework.security.web.bind.support; | ||||||
| 
 | 
 | ||||||
|  | import java.lang.annotation.Annotation; | ||||||
|  | 
 | ||||||
| import org.springframework.core.MethodParameter; | import org.springframework.core.MethodParameter; | ||||||
| import org.springframework.core.annotation.AnnotationUtils; | import org.springframework.core.annotation.AnnotationUtils; | ||||||
| import org.springframework.expression.BeanResolver; | import org.springframework.expression.BeanResolver; | ||||||
| @ -32,8 +34,6 @@ import org.springframework.web.context.request.NativeWebRequest; | |||||||
| import org.springframework.web.method.support.HandlerMethodArgumentResolver; | import org.springframework.web.method.support.HandlerMethodArgumentResolver; | ||||||
| import org.springframework.web.method.support.ModelAndViewContainer; | import org.springframework.web.method.support.ModelAndViewContainer; | ||||||
| 
 | 
 | ||||||
| import java.lang.annotation.Annotation; |  | ||||||
| 
 |  | ||||||
| /** | /** | ||||||
|  * Allows resolving the {@link SecurityContext} using the |  * Allows resolving the {@link SecurityContext} using the | ||||||
|  * {@link CurrentSecurityContext} annotation. For example, the following |  * {@link CurrentSecurityContext} annotation. For example, the following | ||||||
| @ -69,7 +69,7 @@ import java.lang.annotation.Annotation; | |||||||
|  * </p> |  * </p> | ||||||
|  * |  * | ||||||
|  * @author Dan Zheng |  * @author Dan Zheng | ||||||
|  * @since 5.2.x |  * @since 5.2 | ||||||
|  */ |  */ | ||||||
| public final class CurrentSecurityContextArgumentResolver | public final class CurrentSecurityContextArgumentResolver | ||||||
| 		implements HandlerMethodArgumentResolver { | 		implements HandlerMethodArgumentResolver { | ||||||
|  | |||||||
| @ -40,7 +40,7 @@ import java.lang.annotation.Annotation; | |||||||
| /** | /** | ||||||
|  * Resolves the SecurityContext |  * Resolves the SecurityContext | ||||||
|  * @author Dan Zheng |  * @author Dan Zheng | ||||||
|  * @since 5.2.x |  * @since 5.2 | ||||||
|  */ |  */ | ||||||
| public class CurrentSecurityContextArgumentResolver extends HandlerMethodArgumentResolverSupport { | public class CurrentSecurityContextArgumentResolver extends HandlerMethodArgumentResolverSupport { | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -15,9 +15,12 @@ | |||||||
|  */ |  */ | ||||||
| package org.springframework.security.web.bind.support; | package org.springframework.security.web.bind.support; | ||||||
| 
 | 
 | ||||||
|  | import java.lang.reflect.Method; | ||||||
|  | 
 | ||||||
| import org.junit.After; | import org.junit.After; | ||||||
| import org.junit.Before; | import org.junit.Before; | ||||||
| import org.junit.Test; | import org.junit.Test; | ||||||
|  | 
 | ||||||
| import org.springframework.core.MethodParameter; | import org.springframework.core.MethodParameter; | ||||||
| import org.springframework.expression.spel.SpelEvaluationException; | import org.springframework.expression.spel.SpelEvaluationException; | ||||||
| import org.springframework.security.authentication.TestingAuthenticationToken; | import org.springframework.security.authentication.TestingAuthenticationToken; | ||||||
| @ -29,15 +32,12 @@ import org.springframework.security.core.context.SecurityContextHolder; | |||||||
| import org.springframework.security.core.userdetails.User; | import org.springframework.security.core.userdetails.User; | ||||||
| import org.springframework.util.ReflectionUtils; | import org.springframework.util.ReflectionUtils; | ||||||
| 
 | 
 | ||||||
| import java.lang.reflect.Method; |  | ||||||
| 
 |  | ||||||
| import static org.assertj.core.api.Assertions.assertThat; | import static org.assertj.core.api.Assertions.assertThat; | ||||||
| import static org.assertj.core.api.Assertions.assertThatExceptionOfType; | import static org.assertj.core.api.Assertions.assertThatExceptionOfType; | ||||||
| import static org.junit.Assert.fail; |  | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * @author Dan Zheng |  * @author Dan Zheng | ||||||
|  * @since 5.2.x |  * @since 5.2 | ||||||
|  * |  * | ||||||
|  */ |  */ | ||||||
| public class CurrentSecurityContextArgumentResolverTests { | public class CurrentSecurityContextArgumentResolverTests { | ||||||
| @ -64,6 +64,22 @@ public class CurrentSecurityContextArgumentResolverTests { | |||||||
| 		assertThat(resolver.supportsParameter(showSecurityContextAnnotation())).isTrue(); | 		assertThat(resolver.supportsParameter(showSecurityContextAnnotation())).isTrue(); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	@Test | ||||||
|  | 	public void resolveArgumentWithCustomSecurityContext() throws Exception { | ||||||
|  | 		String principal = "custom_security_context"; | ||||||
|  | 		setAuthenticationPrincipalWithCustomSecurityContext(principal); | ||||||
|  | 		CustomSecurityContext customSecurityContext = (CustomSecurityContext) resolver.resolveArgument(showAnnotationWithCustomSecurityContext(), null, null, null); | ||||||
|  | 		assertThat(customSecurityContext.getAuthentication().getPrincipal()).isEqualTo(principal); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	@Test | ||||||
|  | 	public void resolveArgumentWithCustomSecurityContextTypeMatch() throws Exception { | ||||||
|  | 		String principal = "custom_security_context_type_match"; | ||||||
|  | 		setAuthenticationPrincipalWithCustomSecurityContext(principal); | ||||||
|  | 		CustomSecurityContext customSecurityContext = (CustomSecurityContext) resolver.resolveArgument(showAnnotationWithCustomSecurityContext(), null, null, null); | ||||||
|  | 		assertThat(customSecurityContext.getAuthentication().getPrincipal()).isEqualTo(principal); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	@Test | 	@Test | ||||||
| 	public void resolveArgumentNullAuthentication() throws Exception { | 	public void resolveArgumentNullAuthentication() throws Exception { | ||||||
| 		SecurityContext context = SecurityContextHolder.getContext(); | 		SecurityContext context = SecurityContextHolder.getContext(); | ||||||
| @ -142,10 +158,8 @@ public class CurrentSecurityContextArgumentResolverTests { | |||||||
| 	public void resolveArgumentSecurityContextErrorOnInvalidTypeTrue() throws Exception { | 	public void resolveArgumentSecurityContextErrorOnInvalidTypeTrue() throws Exception { | ||||||
| 		String principal = "invalid_type_true"; | 		String principal = "invalid_type_true"; | ||||||
| 		setAuthenticationPrincipal(principal); | 		setAuthenticationPrincipal(principal); | ||||||
| 		try { | 		assertThatExceptionOfType(ClassCastException.class).isThrownBy(() -> resolver.resolveArgument(showSecurityContextErrorOnInvalidTypeTrue(), null, | ||||||
| 			resolver.resolveArgument(showSecurityContextErrorOnInvalidTypeTrue(), null, null, null); | 				null, null)); | ||||||
| 			fail("should not reach here"); |  | ||||||
| 		} catch(ClassCastException ex) {} |  | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	private MethodParameter showSecurityContextNoAnnotation() { | 	private MethodParameter showSecurityContextNoAnnotation() { | ||||||
| @ -156,6 +170,14 @@ public class CurrentSecurityContextArgumentResolverTests { | |||||||
| 		return getMethodParameter("showSecurityContextAnnotation", SecurityContext.class); | 		return getMethodParameter("showSecurityContextAnnotation", SecurityContext.class); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	private MethodParameter showAnnotationWithCustomSecurityContext() { | ||||||
|  | 		return getMethodParameter("showAnnotationWithCustomSecurityContext", CustomSecurityContext.class); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	private MethodParameter showAnnotationWithCustomSecurityContextTypeMatch() { | ||||||
|  | 		return getMethodParameter("showAnnotationWithCustomSecurityContextTypeMatch", SecurityContext.class); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	private MethodParameter showSecurityContextAuthenticationAnnotation() { | 	private MethodParameter showSecurityContextAuthenticationAnnotation() { | ||||||
| 		return getMethodParameter("showSecurityContextAuthenticationAnnotation", Authentication.class); | 		return getMethodParameter("showSecurityContextAuthenticationAnnotation", Authentication.class); | ||||||
| 	} | 	} | ||||||
| @ -197,6 +219,12 @@ public class CurrentSecurityContextArgumentResolverTests { | |||||||
| 		public void showSecurityContextAnnotation(@CurrentSecurityContext SecurityContext context) { | 		public void showSecurityContextAnnotation(@CurrentSecurityContext SecurityContext context) { | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
|  | 		public void showAnnotationWithCustomSecurityContext(@CurrentSecurityContext CustomSecurityContext context) { | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		public void showAnnotationWithCustomSecurityContextTypeMatch(@CurrentSecurityContext(errorOnInvalidType = true) SecurityContext context) { | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
| 		public void showSecurityContextAuthenticationAnnotation(@CurrentSecurityContext(expression = "authentication") Authentication authentication) { | 		public void showSecurityContextAuthenticationAnnotation(@CurrentSecurityContext(expression = "authentication") Authentication authentication) { | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| @ -229,6 +257,26 @@ public class CurrentSecurityContextArgumentResolverTests { | |||||||
| 								"ROLE_USER")); | 								"ROLE_USER")); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	private void setAuthenticationPrincipalWithCustomSecurityContext(Object principal) { | ||||||
|  | 		CustomSecurityContext csc = new CustomSecurityContext(); | ||||||
|  | 		csc.setAuthentication(new TestingAuthenticationToken(principal, "password", | ||||||
|  | 				"ROLE_USER")); | ||||||
|  | 		SecurityContextHolder.setContext(csc); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	static class CustomSecurityContext implements SecurityContext { | ||||||
|  | 		private Authentication authentication; | ||||||
|  | 		@Override | ||||||
|  | 		public Authentication getAuthentication() { | ||||||
|  | 			return authentication; | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		@Override | ||||||
|  | 		public void setAuthentication(Authentication authentication) { | ||||||
|  | 			this.authentication = authentication; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	private void setAuthenticationDetail(Object detail) { | 	private void setAuthenticationDetail(Object detail) { | ||||||
| 		TestingAuthenticationToken tat = new TestingAuthenticationToken("user", "password", | 		TestingAuthenticationToken tat = new TestingAuthenticationToken("user", "password", | ||||||
| 				"ROLE_USER"); | 				"ROLE_USER"); | ||||||
|  | |||||||
| @ -21,6 +21,9 @@ import org.junit.Test; | |||||||
| import org.junit.runner.RunWith; | import org.junit.runner.RunWith; | ||||||
| import org.mockito.Mock; | import org.mockito.Mock; | ||||||
| import org.mockito.junit.MockitoJUnitRunner; | import org.mockito.junit.MockitoJUnitRunner; | ||||||
|  | import reactor.core.publisher.Mono; | ||||||
|  | import reactor.util.context.Context; | ||||||
|  | 
 | ||||||
| import org.springframework.core.MethodParameter; | import org.springframework.core.MethodParameter; | ||||||
| import org.springframework.core.ReactiveAdapterRegistry; | import org.springframework.core.ReactiveAdapterRegistry; | ||||||
| import org.springframework.expression.BeanResolver; | import org.springframework.expression.BeanResolver; | ||||||
| @ -33,15 +36,14 @@ import org.springframework.security.core.context.SecurityContext; | |||||||
| import org.springframework.security.web.method.ResolvableMethod; | import org.springframework.security.web.method.ResolvableMethod; | ||||||
| import org.springframework.web.reactive.BindingContext; | import org.springframework.web.reactive.BindingContext; | ||||||
| import org.springframework.web.server.ServerWebExchange; | import org.springframework.web.server.ServerWebExchange; | ||||||
| import reactor.core.publisher.Mono; | 
 | ||||||
| import reactor.util.context.Context; |  | ||||||
| import static org.assertj.core.api.Assertions.assertThat; | import static org.assertj.core.api.Assertions.assertThat; | ||||||
| import static org.junit.Assert.fail; | import static org.junit.Assert.fail; | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * @author Dan Zheng |  * @author Dan Zheng | ||||||
|  * @since 5.2.x |  * @since 5.2 | ||||||
|  */ |  */ | ||||||
| @RunWith(MockitoJUnitRunner.class) | @RunWith(MockitoJUnitRunner.class) | ||||||
| public class CurrentSecurityContextArgumentResolverTests { | public class CurrentSecurityContextArgumentResolverTests { | ||||||
| @ -98,6 +100,17 @@ public class CurrentSecurityContextArgumentResolverTests { | |||||||
| 		ReactiveSecurityContextHolder.clearContext(); | 		ReactiveSecurityContextHolder.clearContext(); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	@Test | ||||||
|  | 	public void resolveArgumentWithCustomSecurityContext() throws Exception { | ||||||
|  | 		MethodParameter parameter = ResolvableMethod.on(getClass()).named("customSecurityContext").build().arg(Mono.class, SecurityContext.class); | ||||||
|  | 		Authentication auth = buildAuthenticationWithPrincipal("hello"); | ||||||
|  | 		Context context = ReactiveSecurityContextHolder.withSecurityContext(Mono.just(new CustomSecurityContext(auth))); | ||||||
|  | 		Mono<Object> argument = resolver.resolveArgument(parameter, bindingContext, exchange); | ||||||
|  | 		CustomSecurityContext securityContext = (CustomSecurityContext) argument.subscriberContext(context).cast(Mono.class).block().block(); | ||||||
|  | 		assertThat(securityContext.getAuthentication()).isSameAs(auth); | ||||||
|  | 		ReactiveSecurityContextHolder.clearContext(); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	@Test | 	@Test | ||||||
| 	public void resolveArgumentWithNullAuthentication1() throws Exception { | 	public void resolveArgumentWithNullAuthentication1() throws Exception { | ||||||
| 		MethodParameter parameter = ResolvableMethod.on(getClass()).named("securityContext").build().arg(Mono.class, SecurityContext.class); | 		MethodParameter parameter = ResolvableMethod.on(getClass()).named("securityContext").build().arg(Mono.class, SecurityContext.class); | ||||||
| @ -216,6 +229,8 @@ public class CurrentSecurityContextArgumentResolverTests { | |||||||
| 
 | 
 | ||||||
| 	void securityContext(@CurrentSecurityContext Mono<SecurityContext> monoSecurityContext) {} | 	void securityContext(@CurrentSecurityContext Mono<SecurityContext> monoSecurityContext) {} | ||||||
| 
 | 
 | ||||||
|  | 	void customSecurityContext(@CurrentSecurityContext Mono<SecurityContext> monoSecurityContext) {} | ||||||
|  | 
 | ||||||
| 	void securityContextWithAuthentication(@CurrentSecurityContext(expression = "authentication") Mono<Authentication> authentication) {} | 	void securityContextWithAuthentication(@CurrentSecurityContext(expression = "authentication") Mono<Authentication> authentication) {} | ||||||
| 
 | 
 | ||||||
| 	void securityContextWithDepthPropOptional(@CurrentSecurityContext(expression = "authentication?.principal") Mono<Object> principal) {} | 	void securityContextWithDepthPropOptional(@CurrentSecurityContext(expression = "authentication?.principal") Mono<Object> principal) {} | ||||||
| @ -230,7 +245,21 @@ public class CurrentSecurityContextArgumentResolverTests { | |||||||
| 
 | 
 | ||||||
| 	void errorOnInvalidTypeWhenExplicitTrue(@CurrentSecurityContext(errorOnInvalidType = true) Mono<String> implicit) {} | 	void errorOnInvalidTypeWhenExplicitTrue(@CurrentSecurityContext(errorOnInvalidType = true) Mono<String> implicit) {} | ||||||
| 
 | 
 | ||||||
|  | 	static class CustomSecurityContext implements SecurityContext { | ||||||
|  | 		private Authentication authentication; | ||||||
|  | 		public CustomSecurityContext(Authentication authentication) { | ||||||
|  | 			this.authentication = authentication; | ||||||
|  | 		} | ||||||
|  | 		@Override | ||||||
|  | 		public Authentication getAuthentication() { | ||||||
|  | 			return authentication; | ||||||
|  | 		} | ||||||
| 
 | 
 | ||||||
|  | 		@Override | ||||||
|  | 		public void setAuthentication(Authentication authentication) { | ||||||
|  | 			this.authentication = authentication; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
| 	private Authentication buildAuthenticationWithPrincipal(Object principal) { | 	private Authentication buildAuthenticationWithPrincipal(Object principal) { | ||||||
| 		return new TestingAuthenticationToken(principal, "password", | 		return new TestingAuthenticationToken(principal, "password", | ||||||
| 				"ROLE_USER"); | 				"ROLE_USER"); | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user