Repository: jhipster/generator-jhipster
Pull request #9580 (2019-04-18 21:41:49)

Fix skipUserManagement option for monoliths
-   Please make sure the below checklist is followed for Pull Requests.

-   [x] [Travis tests](https://travis-ci.org/jhipster/generator-jhipster/pull_requests) are green
-   [x] Tests are added where necessary
-   [x] Documentation is added/updated where necessary
-   [x] Coding Rules & Commit Guidelines as per our [CONTRIBUTING.md document](https://github.com/jhipster/generator-jhipster/blob/master/CONTRIBUTING.md) are followed

<!--
Please also reference the issue number in a commit message to [automatically close the related Github issue](https://help.github.com/articles/closing-issues-via-commit-messages/)

Note: It is also possible to add `[skip ci]` to your commit message to skip Travis tests
-->

Currently when building a monolith with skipUserManagement, the user can't login because the `/account` endpoint doesn't exist and the in-memory user autoconfigured by Spring Boot is not properly setup.

Notes:
* parts of the front-end should be adapted or removed when skipUserManagement is set (register screen, ...). This will be done in a later PR.
* after this PR login can be done without a DB. Next step will be to implement the "no database" option for monoliths.

cbornet: I'll also add a test for the new AccountResource.
pascalgrimaud: @cbornet : same here, I'd like to merge your PR, can you fix the conflict plz ?

Patch:
diff --git a/generators/server/files.js b/generators/server/files.js
index c32828869f..446417ca32 100644
--- a/generators/server/files.js
+++ b/generators/server/files.js
@@ -379,13 +379,7 @@ const serverFiles = {
             ]
         },
         {
-            condition: generator =>
-                !generator.reactive &&
-                (generator.applicationType === 'microservice' ||
-                    (generator.applicationType !== 'uaa' &&
-                        ((shouldSkipUserManagement(generator) && generator.authenticationType === 'jwt') ||
-                            !shouldSkipUserManagement(generator) ||
-                            generator.authenticationType === 'uaa'))),
+            condition: generator => !generator.reactive && generator.applicationType !== 'uaa',
             path: SERVER_MAIN_SRC_DIR,
             templates: [
                 {
@@ -395,13 +389,7 @@ const serverFiles = {
             ]
         },
         {
-            condition: generator =>
-                generator.reactive &&
-                (generator.applicationType === 'microservice' ||
-                    (generator.applicationType !== 'uaa' &&
-                        ((shouldSkipUserManagement(generator) && generator.authenticationType === 'jwt') ||
-                            !shouldSkipUserManagement(generator) ||
-                            generator.authenticationType === 'uaa'))),
+            condition: generator => generator.reactive && generator.applicationType !== 'uaa',
             path: SERVER_MAIN_SRC_DIR,
             templates: [
                 {
@@ -490,7 +478,7 @@ const serverFiles = {
             ]
         },
         {
-            condition: generator => !shouldSkipUserManagement(generator) && generator.authenticationType === 'jwt',
+            condition: generator => generator.applicationType !== 'microservice' && generator.authenticationType === 'jwt',
             path: SERVER_MAIN_SRC_DIR,
             templates: [
                 {
@@ -1400,7 +1388,7 @@ const serverFiles = {
         {
             condition: generator =>
                 generator.skipUserManagement &&
-                generator.authenticationType === 'oauth2' &&
+                generator.authenticationType !== 'uaa' &&
                 ['monolith', 'gateway'].includes(generator.applicationType),
             path: SERVER_MAIN_SRC_DIR,
             templates: [
@@ -1431,7 +1419,7 @@ const serverFiles = {
         {
             condition: generator =>
                 generator.skipUserManagement &&
-                generator.authenticationType === 'oauth2' &&
+                generator.authenticationType !== 'uaa' &&
                 ['monolith', 'gateway'].includes(generator.applicationType),
             path: SERVER_TEST_SRC_DIR,
             templates: [
@@ -1628,7 +1616,7 @@ const serverFiles = {
             ]
         },
         {
-            condition: generator => !generator.skipUserManagement && generator.authenticationType === 'jwt',
+            condition: generator => generator.applicationType !== 'microservice' && generator.authenticationType === 'jwt',
             path: SERVER_TEST_SRC_DIR,
             templates: [
                 {
diff --git a/generators/server/templates/src/main/java/package/config/SecurityConfiguration.java.ejs b/generators/server/templates/src/main/java/package/config/SecurityConfiguration.java.ejs
index f4e6e9e14f..cf05a886ec 100644
--- a/generators/server/templates/src/main/java/package/config/SecurityConfiguration.java.ejs
+++ b/generators/server/templates/src/main/java/package/config/SecurityConfiguration.java.ejs
@@ -222,10 +222,12 @@ public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
         .and()
         <%_ } _%>
             .authorizeRequests()
+            <%_ if (authenticationType !== 'oauth2') { _%>
+            .antMatchers("/api/authenticate").permitAll()
+            <%_ } _%>
             <%_ if (authenticationType !== 'oauth2' && !skipUserManagement) { _%>
             .antMatchers("/api/register").permitAll()
             .antMatchers("/api/activate").permitAll()
-            .antMatchers("/api/authenticate").permitAll()
             .antMatchers("/api/account/reset-password/init").permitAll()
             .antMatchers("/api/account/reset-password/finish").permitAll()
             <%_ } _%>
diff --git a/generators/server/templates/src/main/java/package/web/rest/AccountResource.java.ejs b/generators/server/templates/src/main/java/package/web/rest/AccountResource.java.ejs
index f421ce523a..48b39895c7 100644
--- a/generators/server/templates/src/main/java/package/web/rest/AccountResource.java.ejs
+++ b/generators/server/templates/src/main/java/package/web/rest/AccountResource.java.ejs
@@ -89,7 +89,66 @@ public class AccountResource {
         }
     }
 }
-<%_ } else { _%>
+<%_ } else if (skipUserManagement) { // not oauth, skipUserManagement _%>
+import <%=packageName%>.security.SecurityUtils;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.Set;
+import java.util.stream.Collectors;
+
+@RestController
+@RequestMapping("/api")
+public class AccountResource {
+
+    private static class AccountResourceException extends RuntimeException {
+    }
+
+    /**
+     * {@code GET  /account} : get the current user.
+     *
+     * @return the current user.
+     * @throws AccountResourceException {@code 500 (Internal Server Error)} if the user couldn't be returned.
+     */
+    @GetMapping("/account")
+    public UserVM getAccount() {
+        String login = SecurityUtils.getCurrentUserLogin()
+            .orElseThrow(AccountResourceException::new);
+        Set<String> authorities = SecurityContextHolder.getContext().getAuthentication().getAuthorities().stream()
+            .map(GrantedAuthority::getAuthority)
+            .collect(Collectors.toSet());
+        return new UserVM(login, authorities);
+    }
+
+    private static class UserVM {
+        private String login;
+        private Set<String> authorities;
+
+        @JsonCreator
+        UserVM(String login, Set<String> authorities) {
+            this.login = login;
+            this.authorities = authorities;
+        }
+
+        public boolean isActivated() {
+            return true;
+        }
+
+        public Set<String> getAuthorities() {
+            return authorities;
+        }
+
+        public String getLogin() {
+            return login;
+        }
+    }
+}
+<%_ } else {  // not oauth, not skipUserManagement _%>
 
 <%_ if (authenticationType === 'session' && !reactive) { _%>
 import <%=packageName%>.domain.PersistentToken;
diff --git a/generators/server/templates/src/main/java/package/web/rest/vm/LoginVM.java.ejs b/generators/server/templates/src/main/java/package/web/rest/vm/LoginVM.java.ejs
index ac3f6da8eb..3879f0ff7c 100644
--- a/generators/server/templates/src/main/java/package/web/rest/vm/LoginVM.java.ejs
+++ b/generators/server/templates/src/main/java/package/web/rest/vm/LoginVM.java.ejs
@@ -31,7 +31,7 @@ public class LoginVM {
     private String username;
 
     @NotNull
-    @Size(min = ManagedUserVM.PASSWORD_MIN_LENGTH, max = ManagedUserVM.PASSWORD_MAX_LENGTH)
+    @Size(min = 4, max = 100)
     private String password;
 
     private Boolean rememberMe;
diff --git a/generators/server/templates/src/main/resources/config/application.yml.ejs b/generators/server/templates/src/main/resources/config/application.yml.ejs
index abd4f69455..4d7de8be3a 100644
--- a/generators/server/templates/src/main/resources/config/application.yml.ejs
+++ b/generators/server/templates/src/main/resources/config/application.yml.ejs
@@ -199,6 +199,15 @@ spring:
     mvc:
         favicon:
             enabled: false
+    <%_ if (skipUserManagement && ['monolith', 'gateway'].includes(applicationType) && !['oauth2','uaa'].includes(authenticationType)) { _%>
+    security:
+        user:
+            name: admin
+            password: admin
+            roles:
+                - ADMIN
+                - USER
+    <%_ } _%>
     task:
         execution:
             thread-name-prefix: <%= dasherizedBaseName %>-task-


Test patch:
diff --git a/generators/server/templates/src/test/java/package/web/rest/AccountResourceIT.java.ejs b/generators/server/templates/src/test/java/package/web/rest/AccountResourceIT.java.ejs
index be05c2008a..7e862367e3 100644
--- a/generators/server/templates/src/test/java/package/web/rest/AccountResourceIT.java.ejs
+++ b/generators/server/templates/src/test/java/package/web/rest/AccountResourceIT.java.ejs
@@ -16,20 +16,20 @@
  See the License for the specific language governing permissions and
  limitations under the License.
 -%>
-package <%=packageName%>.web.rest;
+package <%= packageName %>.web.rest;
 <%_ if (authenticationType === 'oauth2') { _%>
 
 <%_ if (databaseType === 'cassandra') { _%>
-import <%=packageName%>.AbstractCassandraTest;
+import <%= packageName %>.AbstractCassandraTest;
 <%_ } _%>
-import <%=packageName%>.<%= mainClass %>;
-import <%=packageName%>.config.TestSecurityConfiguration;
-import <%=packageName%>.domain.Authority;
-import <%=packageName%>.domain.<%= asEntity('User') %>;
-import <%=packageName%>.repository.UserRepository;
-import <%=packageName%>.security.AuthoritiesConstants;
-import <%=packageName%>.service.UserService;
-import <%=packageName%>.web.rest.errors.ExceptionTranslator;
+import <%= packageName %>.<%= mainClass %>;
+import <%= packageName %>.config.TestSecurityConfiguration;
+import <%= packageName %>.domain.Authority;
+import <%= packageName %>.domain.<%= asEntity('User') %>;
+import <%= packageName %>.repository.UserRepository;
+import <%= packageName %>.security.AuthoritiesConstants;
+import <%= packageName %>.service.UserService;
+import <%= packageName %>.web.rest.errors.ExceptionTranslator;
 
 import org.apache.commons.lang3.RandomStringUtils;
 import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user;
@@ -156,35 +156,95 @@ public class AccountResourceIT <% if (databaseType === 'cassandra') { %>extends
             .andExpect(status().isInternalServerError());
     }
 }
-<%_ } else { _%>
+<%_ } else if (skipUserManagement) { // not oauth, skipUserManagement _%>
+
+<%_ if (databaseType === 'cassandra') { _%>
+import <%= packageName %>.AbstractCassandraTest;
+<%_ } _%>
+import <%= packageName %>.<%= mainClass %>;
+import <%= packageName %>.security.AuthoritiesConstants;
+import <%= packageName %>.web.rest.errors.ExceptionTranslator;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.http.MediaType;
+import org.springframework.security.test.context.support.WithMockUser;
+import org.springframework.test.context.junit4.SpringRunner;
+import org.springframework.test.web.servlet.MockMvc;
+import org.springframework.test.web.servlet.setup.MockMvcBuilders;
+
+import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
+
+/**
+ * Integration tests for the {@link AccountResource} REST controller.
+ */
+@RunWith(SpringRunner.class)
+@SpringBootTest(classes = <%= mainClass %>.class)
+public class AccountResourceIT <% if (databaseType === 'cassandra') { %>extends AbstractCassandraTest <% } %>{
+
+    @Autowired
+    private ExceptionTranslator exceptionTranslator;
+
+    private MockMvc mockMvc;
+
+    @Before
+    public void setup() {
+        AccountResource accountUserMockResource = new AccountResource();
+        this.mockMvc = MockMvcBuilders.standaloneSetup(accountUserMockResource)
+                .setControllerAdvice(exceptionTranslator)
+                .build();
+    }
+
+    @Test
+    @WithMockUser(username = "test", roles = "ADMIN")
+    public void testGetExistingAccount() throws Exception {
+        mockMvc.perform(get("/api/account")
+                .accept(MediaType.APPLICATION_JSON))
+                .andExpect(status().isOk())
+                .andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8_VALUE))
+                .andExpect(jsonPath("$.login").value("test"))
+                .andExpect(jsonPath("$.authorities").value(AuthoritiesConstants.ADMIN));
+    }
+
+    @Test
+    public void testGetUnknownAccount() throws Exception {
+        mockMvc.perform(get("/api/account")
+                .accept(MediaType.APPLICATION_PROBLEM_JSON))
+                .andExpect(status().isInternalServerError());
+    }
+}
+<%_ } else {  // not oauth, not skipUserManagement _%>
 
 <%_ if (databaseType === 'cassandra') { _%>
-import <%=packageName%>.AbstractCassandraTest;
+import <%= packageName %>.AbstractCassandraTest;
 <%_ } _%>
-import <%=packageName%>.<%= mainClass %>;
-import <%=packageName%>.config.Constants;
+import <%= packageName %>.<%= mainClass %>;
+import <%= packageName %>.config.Constants;
 <%_ if (databaseType === 'sql' || databaseType === 'mongodb') { _%>
-import <%=packageName%>.domain.Authority;
+import <%= packageName %>.domain.Authority;
 <%_ } _%>
 <%_ if (authenticationType === 'session' && !reactive) { _%>
-import <%=packageName%>.domain.PersistentToken;
+import <%= packageName %>.domain.PersistentToken;
 <%_ } _%>
-import <%=packageName%>.domain.<%= asEntity('User') %>;
+import <%= packageName %>.domain.<%= asEntity('User') %>;
 <%_ if (databaseType === 'sql' || databaseType === 'mongodb' || databaseType === 'couchbase') { _%>
-import <%=packageName%>.repository<% if (reactive) { %>.reactive<% } %>.AuthorityRepository;
+import <%= packageName %>.repository<% if (reactive) { %>.reactive<% } %>.AuthorityRepository;
 <%_ } _%>
 <%_ if (authenticationType === 'session' && !reactive) { _%>
-import <%=packageName%>.repository.PersistentTokenRepository;
+import <%= packageName %>.repository.PersistentTokenRepository;
 <%_ } _%>
-import <%=packageName%>.repository<% if (reactive) { %>.reactive<% } %>.UserRepository;
-import <%=packageName%>.security.AuthoritiesConstants;
-import <%=packageName%>.service.MailService;
-import <%=packageName%>.service.UserService;
-import <%=packageName%>.service.dto.PasswordChangeDTO;
-import <%=packageName%>.service.dto.<%= asDto('User') %>;
-import <%=packageName%>.web.rest.errors.ExceptionTranslator;
-import <%=packageName%>.web.rest.vm.KeyAndPasswordVM;
-import <%=packageName%>.web.rest.vm.ManagedUserVM;
+import <%= packageName %>.repository<% if (reactive) { %>.reactive<% } %>.UserRepository;
+import <%= packageName %>.security.AuthoritiesConstants;
+import <%= packageName %>.service.MailService;
+import <%= packageName %>.service.UserService;
+import <%= packageName %>.service.dto.PasswordChangeDTO;
+import <%= packageName %>.service.dto.<%= asDto('User') %>;
+import <%= packageName %>.web.rest.errors.ExceptionTranslator;
+import <%= packageName %>.web.rest.vm.KeyAndPasswordVM;
+import <%= packageName %>.web.rest.vm.ManagedUserVM;
 import org.apache.commons.lang3.RandomStringUtils;
 
 import org.junit.jupiter.api.BeforeEach;
diff --git a/generators/server/templates/src/test/java/package/web/rest/UserJWTControllerIT.java.ejs b/generators/server/templates/src/test/java/package/web/rest/UserJWTControllerIT.java.ejs
index 3044b0a286..c496d71788 100644
--- a/generators/server/templates/src/test/java/package/web/rest/UserJWTControllerIT.java.ejs
+++ b/generators/server/templates/src/test/java/package/web/rest/UserJWTControllerIT.java.ejs
@@ -22,8 +22,10 @@ package <%=packageName%>.web.rest;
 import <%=packageName%>.AbstractCassandraTest;
 <%_ } _%>
 import <%=packageName%>.<%= mainClass %>;
+<%_ if (!skipUserManagement) { _%>
 import <%=packageName%>.domain.<%= asEntity('User') %>;
 import <%=packageName%>.repository<% if (reactive) { %>.reactive<% } %>.UserRepository;
+<%_ } _%>
 import <%=packageName%>.security.jwt.TokenProvider;
 import <%=packageName%>.web.rest.errors.ExceptionTranslator;
 import <%=packageName%>.web.rest.vm.LoginVM;
@@ -37,7 +39,9 @@ import org.springframework.security.authentication.ReactiveAuthenticationManager
 <%_ if (!reactive) { _%>
 import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
 <%_ } _%>
+<%_ if (!skipUserManagement) { _%>
 import org.springframework.security.crypto.password.PasswordEncoder;
+<%_ } _%>
 <%_ if (reactive) { _%>
 import org.springframework.test.web.reactive.server.WebTestClient;
 <%_ } _%>
@@ -75,12 +79,14 @@ public class UserJWTControllerIT <% if (databaseType === 'cassandra') { %>extend
     @Autowired
     private <% if (reactive) { %>Reactive<% } %>AuthenticationManager<% if (!reactive) { %>Builder<% } %> authenticationManager;
 
+<%_ if (!skipUserManagement) { _%>
     @Autowired
     private UserRepository userRepository;
 
     @Autowired
     private PasswordEncoder passwordEncoder;
 
+<%_ }  _%>
     @Autowired
     private ExceptionTranslator exceptionTranslator;
 
@@ -104,6 +110,7 @@ public class UserJWTControllerIT <% if (databaseType === 'cassandra') { %>extend
     }
 
     @Test
+<%_ if (!skipUserManagement) { _%>
     <%_ if (databaseType === 'sql') { _%>
     @Transactional
     <%_ } _%>
@@ -125,6 +132,11 @@ public class UserJWTControllerIT <% if (databaseType === 'cassandra') { %>extend
 
         LoginVM login = new LoginVM();
         login.setUsername("user-jwt-controller");
+<%_ } else { _%>
+    public void testAuthorize() throws Exception {
+        LoginVM login = new LoginVM();
+        login.setUsername("test");
+<%_ } _%>
         login.setPassword("test");
         <%_ if (!reactive) { _%>
         mockMvc.perform(post("/api/authenticate")
@@ -148,6 +160,7 @@ public class UserJWTControllerIT <% if (databaseType === 'cassandra') { %>extend
     }
 
     @Test
+<%_ if (!skipUserManagement) { _%>
     <%_ if (databaseType === 'sql') { _%>
     @Transactional
     <%_ } _%>
@@ -169,6 +182,11 @@ public class UserJWTControllerIT <% if (databaseType === 'cassandra') { %>extend
 
         LoginVM login = new LoginVM();
         login.setUsername("user-jwt-controller-remember-me");
+<%_ } else { _%>
+    public void testAuthorizeWithRememberMe() throws Exception {
+        LoginVM login = new LoginVM();
+        login.setUsername("test");
+<%_ } _%>
         login.setPassword("test");
         login.setRememberMe(true);
         <%_ if (!reactive) { _%>
@@ -193,9 +211,6 @@ public class UserJWTControllerIT <% if (databaseType === 'cassandra') { %>extend
     }
 
     @Test
-    <%_ if (databaseType === 'sql') { _%>
-    @Transactional
-    <%_ } _%>
     public void testAuthorizeFails() throws Exception {
         LoginVM login = new LoginVM();
         login.setUsername("wrong-user");
diff --git a/generators/server/templates/src/test/resources/config/application.yml.ejs b/generators/server/templates/src/test/resources/config/application.yml.ejs
index f86fa759df..97e6a8f28a 100644
--- a/generators/server/templates/src/test/resources/config/application.yml.ejs
+++ b/generators/server/templates/src/test/resources/config/application.yml.ejs
@@ -124,6 +124,12 @@ spring:
     mvc:
         favicon:
             enabled: false
+    <%_ if (skipUserManagement && ['monolith', 'gateway'].includes(applicationType) && authenticationType === 'jwt') { _%>
+    security:
+        user:
+            name: test
+            password: test
+    <%_ } _%>
     task:
         execution:
             thread-name-prefix: <%= dasherizedBaseName %>-task-
diff --git a/test/utils/expected-files.js b/test/utils/expected-files.js
index 739b0bf750..c8cda35259 100644
--- a/test/utils/expected-files.js
+++ b/test/utils/expected-files.js
@@ -193,7 +193,9 @@ const expectedFiles = {
         `${SERVER_MAIN_SRC_DIR}com/mycompany/myapp/web/rest/errors/FieldErrorVM.java`,
         `${SERVER_MAIN_SRC_DIR}com/mycompany/myapp/web/rest/vm/package-info.java`,
         `${SERVER_MAIN_SRC_DIR}com/mycompany/myapp/web/rest/package-info.java`,
+        `${SERVER_MAIN_SRC_DIR}com/mycompany/myapp/web/rest/AccountResource.java`,
         `${SERVER_TEST_SRC_DIR}com/mycompany/myapp/security/SecurityUtilsUnitTest.java`,
+        `${SERVER_TEST_SRC_DIR}com/mycompany/myapp/web/rest/AccountResourceIT.java`,
         `${SERVER_TEST_SRC_DIR}com/mycompany/myapp/web/rest/TestUtil.java`,
         `${SERVER_TEST_SRC_DIR}com/mycompany/myapp/web/rest/errors/ExceptionTranslatorIT.java`,
         `${SERVER_TEST_SRC_DIR}com/mycompany/myapp/web/rest/errors/ExceptionTranslatorTestController.java`,
@@ -228,12 +230,10 @@ const expectedFiles = {
         `${SERVER_MAIN_SRC_DIR}com/mycompany/myapp/web/rest/errors/EmailNotFoundException.java`,
         `${SERVER_MAIN_SRC_DIR}com/mycompany/myapp/web/rest/errors/InvalidPasswordException.java`,
         `${SERVER_MAIN_SRC_DIR}com/mycompany/myapp/web/rest/errors/LoginAlreadyUsedException.java`,
-        `${SERVER_MAIN_SRC_DIR}com/mycompany/myapp/web/rest/AccountResource.java`,
         `${SERVER_MAIN_SRC_DIR}com/mycompany/myapp/web/rest/AuditResource.java`,
         `${SERVER_MAIN_SRC_DIR}com/mycompany/myapp/web/rest/UserResource.java`,
         `${SERVER_MAIN_SRC_DIR}com/mycompany/myapp/web/rest/vm/KeyAndPasswordVM.java`,
         `${SERVER_MAIN_SRC_DIR}com/mycompany/myapp/web/rest/vm/ManagedUserVM.java`,
-        `${SERVER_TEST_SRC_DIR}com/mycompany/myapp/web/rest/AccountResourceIT.java`,
         `${SERVER_TEST_SRC_DIR}com/mycompany/myapp/web/rest/AuditResourceIT.java`,
         `${SERVER_TEST_SRC_DIR}com/mycompany/myapp/web/rest/UserResourceIT.java`,
         `${SERVER_TEST_SRC_DIR}com/mycompany/myapp/service/UserServiceIT.java`,