plugin-core/plugin/grails-app/commands/grails.plugin.springsecurity/S2QuickstartCommand.groovy (244 lines of code) (raw):

/* * Copyright 2023 Puneet Behl. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package grails.plugin.springsecurity import grails.build.logging.ConsoleLogger import grails.build.logging.GrailsConsole import grails.codegen.model.Model import grails.dev.commands.GrailsApplicationCommand import groovy.transform.CompileStatic /** * Creates domain classes and updates config settings for the Spring Security plugin. * Usage: <code>./gradlew runCommand "-Pargs=s2-quickstart [DOMAIN_CLASS_PACKAGE] [USER_CLASS_NAME] [ROLE_CLASS_NAME] [REQUEST_MAP_CLASS_NAME] --groupClassName=[GROUP_CLASS_NAME]"</code> or * <code>s2-quickstart --ui-only</code> * * For Example: * 1. <code>./gradlew runCommand "-Pargs=s2-quickstart com.yourapp User Role --groupClassName=RoleGroup"</code> * 2. <code>./gradlew runCommand "-Pargs=s2-quickstart com.yourapp Person Authority Requestmap"</code> * 3. <code>./gradlew runCommand "-Pargs=s2-quickstart --uiOnly"</code> * 4. <code>./gradlew runCommand "-Pargs=s2-quickstart com.yourapp User Role"</code> * * @author Puneet Behl * @since 6.0.0 */ @CompileStatic class S2QuickstartCommand implements GrailsApplicationCommand, CommandLineHelper, SkipBootstrap { public static final String GORM_VERSION_THRESHOLD = '6.0.10' private Map<String, String> templateAttributes private boolean uiOnly private boolean salt private String packageName private Model userModel private Model roleModel private Model requestmapModel private Model roleGroupModel String description = 'Creates domain classes and updates config settings for the Spring Security plugin.' private final static String USAGE_MESSAGE = ''' ./gradlew runCommand "-Pargs=s2-quickstart [DOMAIN-CLASS-PACKAGE] [USER-CLASS-NAME] [ROLE-CLASS-NAME] [REQUESTMAP-CLASS-NAME] --groupClassName=GROUP-CLASS-NAME" or ./gradlew runCommand "-Pargs=s2-quickstart --uiOnly" Example: ./gradlew runCommand "-Pargs=s2-quickstart com.yourapp User Role" Example: ./gradlew runCommand "-Pargs=s2-quickstart com.yourapp User Role --groupClassName=RoleGroup" Example: ./gradlew runCommand "-Pargs=s2-quickstart com.yourapp Person Authority Requestmap" Example: ./gradlew runCommand "-Pargs=s2-quickstart --uiOnly" ''' @Delegate ConsoleLogger consoleLogger = GrailsConsole.getInstance() @Override boolean handle() { if (uiOnly) { consoleLogger.addStatus('\nConfiguring Spring Security; not generating domain classes') } else { if (args.size() < 3) { error('Usage:' + USAGE_MESSAGE) return FAILURE } initialize() initializeTemplateAttributes() createDomains(userModel, roleModel, requestmapModel, roleGroupModel) } updateConfig(userModel?.simpleName, roleModel?.simpleName, requestmapModel?.simpleName, userModel?.packageName, roleGroupModel != null) logStatus() return SUCCESS } private void logStatus() { if (uiOnly) { consoleLogger.addStatus ''' ************************************************************ * Your grails-app/conf/application.groovy has been updated * * with security settings; please verify that the * * values are correct. * ************************************************************ ''' } else { consoleLogger.addStatus ''' ************************************************************ * Created security-related domain classes. Your * * grails-app/conf/application.groovy has been updated with * * the class names of the configured domain classes; * * please verify that the values are correct. * ************************************************************ ''' } } private void initializeTemplateAttributes() { templateAttributes = Collections.unmodifiableMap([ packageName : userModel.packageName, userClassName : userModel.simpleName, userClassProperty : userModel.modelName, roleClassName : roleModel.simpleName, roleClassProperty : roleModel.modelName, requestmapClassName: requestmapModel?.simpleName, groupClassName : roleGroupModel?.simpleName, groupClassProperty : roleGroupModel?.modelName]) } private void initialize() { uiOnly = isFlagPresent('uiOnly') salt = flagValue('salt') packageName = args[0] userModel = model(packageName + '.' + args[1]) if (userModel) { consoleLogger.addStatus('\nCreating User class ' + userModel.simpleName + ' in package ' + packageName) } roleModel = model(packageName + '.' + args[2]) if (roleModel) { consoleLogger.addStatus('\nCreating Role class ' + roleModel.simpleName + ' in package ' + packageName) } final String groupClassName = flagValue('groupClassName') roleGroupModel = groupClassName ? model(packageName + '.' + groupClassName) : null if (roleGroupModel) { consoleLogger.addStatus('\nCreating Role/Group classes ' + roleGroupModel.simpleName + ' in package ' + packageName) } } private Map<String, Integer> extractVersion(String versionString) { String[] arr = versionString.split('\\.') Map<String, Integer> v = new HashMap<>([mayor: 0, minor: 0, bug: 0]) try { if (arr.size() >= 1) { v.mayor = arr[0].toInteger() } if (arr.size() >= 2) { v.minor = arr[1].toInteger() } if (arr.size() >= 3) { v.bug = arr[2].toInteger() } } catch (Exception e) { v = [mayor: 0, minor: 0, bug: 0] } v } private boolean versionAfterOrEqualsToThreshold(String threshold, String value) { if (value == null) { return false } if (value.startsWith(threshold)) { return true } Map<String, Integer> va = extractVersion(value) Map<String, Integer> vb = extractVersion(threshold) List<Map<String, Integer>> l = [va, vb] l.sort { a, b -> def compare = a.mayor <=> b.mayor if (compare != 0) { return compare } compare = a.minor <=> b.minor if (compare != 0) { return compare } a.bug <=> b.bug } String sortedValue = l[0].collect { k, v -> v }.join('.') threshold.startsWith(sortedValue) } private void createDomains(Model userModel, Model roleModel, Model requestmapModel, Model groupModel) { final Properties props = new Properties() file("gradle.properties")?.withInputStream { props.load(it) } generateFile('PersonWithoutInjection', userModel.packagePath, userModel.simpleName) if (salt) { generateFile('PersonPasswordEncoderListenerWithSalt', userModel.packagePath, userModel.simpleName, "${userModel.simpleName}PasswordEncoderListener", 'src/main/groovy') } else { generateFile('PersonPasswordEncoderListener', userModel.packagePath, userModel.simpleName, "${userModel.simpleName}PasswordEncoderListener", 'src/main/groovy') } List<Map<String, String>> beans = [] beans.add([import : "import ${userModel.packageName}.${userModel.simpleName}PasswordEncoderListener".toString(), definition: "${userModel.propertyName}PasswordEncoderListener(${userModel.simpleName}PasswordEncoderListener)".toString()]) addBeans(beans, 'grails-app/conf/spring/resources.groovy') generateFile('Authority', roleModel.packagePath, roleModel.simpleName) generateFile('PersonAuthority', roleModel.packagePath, userModel.simpleName + roleModel.simpleName) if (requestmapModel) { generateFile('Requestmap', requestmapModel.packagePath, requestmapModel.simpleName) } if (groupModel) { generateFile('AuthorityGroup', groupModel.packagePath, groupModel.simpleName) generateFile('PersonAuthorityGroup', groupModel.packagePath, userModel.simpleName + groupModel.simpleName) generateFile('AuthorityGroupAuthority', groupModel.packagePath, groupModel.simpleName + roleModel.simpleName) } } private void updateConfig(String userClassName, String roleClassName, String requestmapClassName, String packageName, boolean useRoleGroups) { file('grails-app/conf/application.groovy').withWriterAppend { BufferedWriter writer -> writer.newLine() writer.newLine() writer.writeLine('// Added by the Spring Security Core plugin:') if (!uiOnly) { writer.writeLine("grails.plugin.springsecurity.userLookup.userDomainClassName = '${packageName}.$userClassName'") writer.writeLine("grails.plugin.springsecurity.userLookup.authorityJoinClassName = '${packageName}.$userClassName$roleClassName'") writer.writeLine("grails.plugin.springsecurity.authority.className = '${packageName}.$roleClassName'") } if (useRoleGroups) { writer.writeLine("grails.plugin.springsecurity.authority.groupAuthorityNameField = 'authorities'") writer.writeLine('grails.plugin.springsecurity.useRoleGroups = true') } if (requestmapClassName) { writer.writeLine("grails.plugin.springsecurity.requestMap.className = '${packageName}.$requestmapClassName'") writer.writeLine("grails.plugin.springsecurity.securityConfigType = 'Requestmap'") } writer.writeLine('grails.plugin.springsecurity.controllerAnnotations.staticRules = [') writer.writeLine("\t[pattern: '/', access: ['permitAll']],") writer.writeLine("\t[pattern: '/error', access: ['permitAll']],") writer.writeLine("\t[pattern: '/index', access: ['permitAll']],") writer.writeLine("\t[pattern: '/index.gsp', access: ['permitAll']],") writer.writeLine("\t[pattern: '/shutdown', access: ['permitAll']],") writer.writeLine("\t[pattern: '/assets/**', access: ['permitAll']],") writer.writeLine("\t[pattern: '/**/js/**', access: ['permitAll']],") writer.writeLine("\t[pattern: '/**/css/**', access: ['permitAll']],") writer.writeLine("\t[pattern: '/**/images/**', access: ['permitAll']],") writer.writeLine("\t[pattern: '/**/favicon.ico', access: ['permitAll']]") writer.writeLine(']') writer.newLine() writer.writeLine('grails.plugin.springsecurity.filterChain.chainMap = [') writer.writeLine("\t[pattern: '/assets/**', filters: 'none'],") writer.writeLine("\t[pattern: '/**/js/**', filters: 'none'],") writer.writeLine("\t[pattern: '/**/css/**', filters: 'none'],") writer.writeLine("\t[pattern: '/**/images/**', filters: 'none'],") writer.writeLine("\t[pattern: '/**/favicon.ico', filters: 'none'],") writer.writeLine("\t[pattern: '/**', filters: 'JOINED_FILTERS']") writer.writeLine(']') writer.newLine() } } private void generateFile(String templateName, String packagePath, String className, String fileName = null, String folder = 'grails-app/domain') { render template(templateName + '.groovy.template'), file("${folder}/$packagePath/${fileName ?: className}.groovy"), templateAttributes, false } private void addBeans(List<Map<String, String>> beans, String resourceConfigFilePath) { final File resourceConfig = new File(resourceConfigFilePath) List<String> lines = [] beans.forEach(bean -> lines.add(bean.import)) if (resourceConfig.exists()) { resourceConfig.eachLine { line, nb -> lines << line if (line.contains('beans = {')) { beans.each { Map bean -> lines << ' ' + bean.definition } } } } else { lines << 'beans = {' beans.each { Map bean -> lines << ' ' + bean.definition } lines << '}' } resourceConfig.withWriter('UTF-8') { writer -> lines.each { String line -> writer.write "${line}${System.lineSeparator()}" } } } }