All Downloads are FREE. Search and download functionalities are using the official Maven repository.

grails.plugin.springsecurity.S2QuickstartCommand.groovy Maven / Gradle / Ivy

Go to download

The Spring Security plugin simplifies the integration of Spring Security into Grails® framework applications.

The newest version!
/*
 * 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
 *
 *      http://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: ./gradlew runCommand "-Pargs=s2-quickstart [DOMAIN_CLASS_PACKAGE] [USER_CLASS_NAME] [ROLE_CLASS_NAME] [REQUEST_MAP_CLASS_NAME] --groupClassName=[GROUP_CLASS_NAME]" or
 * s2-quickstart --ui-only
 *
 * For Example:
 * 1. ./gradlew runCommand "-Pargs=s2-quickstart com.yourapp User Role --groupClassName=RoleGroup"
 * 2. ./gradlew runCommand "-Pargs=s2-quickstart com.yourapp Person Authority Requestmap"
 * 3. ./gradlew runCommand "-Pargs=s2-quickstart --uiOnly"
 * 4. ./gradlew runCommand "-Pargs=s2-quickstart com.yourapp User Role"
 *
 * @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 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 extractVersion(String versionString) {
        String[] arr = versionString.split('\\.')
        Map 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 va = extractVersion(value)
        Map vb = extractVersion(threshold)
        List> 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> 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> beans, String resourceConfigFilePath) {
        final File resourceConfig = new File(resourceConfigFilePath)
        List 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()}"
            }
        }
    }

}





© 2015 - 2025 Weber Informatics LLC | Privacy Policy