Update: ok, I found that you can pass a UserDetails object, that is satisfactory. So I'm changing this to a documentation issue as no example was provided (that I saw) and I did not notice the additional method signature.
.with( SecurityMockMvcRequestPostProcessors.user( userDetails ) )
So my problem begins (here actually but this has nothing to do with this ticket).
@PostMapping
HttpStatus resetPasswordAuthenticated(
@Valid @NonNull UserDetails user,
@Valid @NonNull PasswordUpdateController.PasswordUpdateDto pass
) {
and a class cast exception User cannot be cast to MyUserDetails
@Override
@Transactional( readOnly = true )
public MyUserDetails getDexUserDetails() {
return isLoggedIn()
? ((MyUserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal()) : null;
}
```
now we can talk about how wonderful this ancient code is... but the problem is that this
```java
@BeforeEach
void setup( WebApplicationContext wac) throws Exception {
mbf.siteUserLogin().get();
username = mbf.siteUser().get().getUsername();
this.mockMvc = MockMvcBuilders.webAppContextSetup( wac )
.apply( SharedHttpSessionConfigurer.sharedHttpSession() )
.apply( SecurityMockMvcConfigurers.springSecurity() )
.alwaysDo( print() )
.build();
// logInAs( username );
}
@ParameterizedTest
@NullAndEmptySource
@ValueSource(strings = "abcd")
void testPasswordResetInvalidate( String password ) throws Exception {
PasswordUpdateDto dto = new PasswordUpdateDto( username, password );
RequestBuilder builder = MockMvcRequestBuilders.post( PASSWORD_RESET_PATH )
.with( user(username) )
.contentType( MediaType.APPLICATION_JSON )
.content( mapper.writer().writeValueAsString( dto ) );
mockMvc.perform( builder )
.andExpect( MockMvcResultMatchers.status().isNoContent() );
}
```
is causing a `User` class to be injected, and our UserDetails implementation, although it still can override User's implementation, most of the properties exposed are calculated, not just set, e.g.
```java
@Override
public boolean isAccountNonExpired() {
return getUserStatusType() != null && !getUserStatusType().equals( UserStatusType.DISABLED );
}
@Override
public boolean isAccountNonLocked() {
return !(getUserStatusType() == null || getUserStatusType().equals( UserStatusType.LOCKED )
|| !getActiveOrganization().organizationSpecification().canHaveUsers());
}
```
because we have a multi-tenancy, with organization and role switching. this also means authorities can't actually be immutable for us, at least not how it's currently implemented. Even though these methods aren't using the properties I still have ended up with noisy constructors, where most of the values passed don't actually matter.
```java
public static MyUserDetails detailsFor( @NonNull String username ) {
return detailsFor( username, null );
}
public static MyUserDetails detailsFor( @NonNull String username, @Nullable String password ) {
return new MyUserDetails( username, password, new ArrayList<>() );
}
public MyUserDetails( @NonNull String username, @Nullable String password,
@NonNull Collection<GrantedAuthority> authorities ) {
super(username, password, authorities);
this.AUTHORITIES = authorities;
}
public MyUserDetails(
@NonNull String username,
@Nullable String password,
boolean enabled,
boolean accountNonExpired,
boolean credentialsNonExpired,
boolean accountNonLocked,
@NonNull Collection<GrantedAuthority> authorities ) {
super( username, password, enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, authorities );
this.AUTHORITIES = authorities;
}
```
I'm not entirely certain how the `with( user(...) )` works, Seems like it stuffs a `User` into the `SecurityContextHolder.getContext()`. Obviously this did not play nice with our existing code, and I'm wondering if it would be passible maybe to do something like `user(...).usingClass(MyUserDetails.class)`. I'd personally be ok if it required `javac -parameters` to be enabled. This way I could have simply created this, instead of figuring out what the property differences were and whether they might be relevant to me.
```java
// or some interface that implements both...
class MyUserDetails implements UserDetails, CredentialsContainer {
public MyUserDetails(
@NonNull String username,
@Nullable String password
) { ... }
}
In fact on further reflection, even extending the User class will not cause our UserDetails to work. So I believe the only option I have is to stuff it into the context myself.
P.S. I notice that the spring security classes aren't decorated here's hoping that changes.
Comment From: xenoterracide
I've updated this ticket to be a documentation bug with the reference documentation.
Comment From: jzheaux
Thanks for all the detail above and for the suggestion to update the documentation, @xenoterracide.
I'm not entirely certain how the with( user(...) ) works,
You are correct that it makes so that getPrincipal() returns whatever is passed into that method. So, you could do:
MyUserDetails details = new MyUserDetails(...);
...
with( user(details) )
...
And I believe your getDexUserDetails would work.
Would you be able to contribute a PR with the updated documentation?
I'm not sure if I've missed anything else in your post - is there anything else needed to resolve the ticket?
Comment From: xenoterracide
And I believe your getDexUserDetails would work. It did not, not without providing a real instance of
DexUserDetails(actually hydrated from the database, though that probably has to do with some system specific logic)
I went to try to make an update, and couldn't find where the reference documentation lived. Now y'all will have to go into my open source queue of tickets, once I know where that is.
Comment From: jzheaux
It did not, not without providing a real instance of
DexUserDetails
This is what I meant by:
MyUserDetails details = new MyUserDetails(...);
...
with( user(details) )
...
where details is the real instance you referred to. Or did I misunderstand?
I went to try to make an update, and couldn't find where the reference documentation lived.
I think the Spring Security MockMvc docs would probably be the best place.
Comment From: xenoterracide
eh, it's very complicated, but yes, IIRC your solution worked, I just had to load the entity from the database first, I used MyUserDetails as the classname because I was trying to avoid mentioning Dex
Comment From: jzheaux
I'm closing this since documentation for this can be found at: https://docs.spring.io/spring-security/reference/5.6.0/servlet/test/mockmvc/authentication.html#test-mockmvc-securitycontextholder-rpp
Further improvements to the docs are welcome, of course.