diff --git a/plugin/grails-app/commands/grails.plugin.springsecurity/CommandLineHelper.groovy b/plugin/grails-app/commands/grails.plugin.springsecurity/CommandLineHelper.groovy
new file mode 100644
index 000000000..0133412d9
--- /dev/null
+++ b/plugin/grails-app/commands/grails.plugin.springsecurity/CommandLineHelper.groovy
@@ -0,0 +1,34 @@
+package grails.plugin.springsecurity
+
+import grails.dev.commands.ExecutionContext
+import io.micronaut.core.naming.NameUtils
+import org.grails.build.parsing.CommandLine
+
+trait CommandLineHelper {
+
+ static final boolean SUCCESS = true
+ static final boolean FAILURE = false
+
+ abstract ExecutionContext getExecutionContext()
+
+ boolean isFlagPresent(String name) {
+ final CommandLine commandLine = executionContext.commandLine
+ if (commandLine.hasOption(name)) {
+ return commandLine.optionValue(name) ? true : false
+ } else {
+ def value = commandLine?.undeclaredOptions?.get(name)
+ return value ? true : false
+ }
+ }
+
+ String flagValue(String name) {
+ final CommandLine commandLine = executionContext.commandLine
+ if (commandLine.hasOption(name)) {
+ return commandLine.optionValue(name)
+ } else {
+ def value = commandLine?.undeclaredOptions?.get(name)
+ return value
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/plugin/grails-app/commands/grails.plugin.springsecurity/S2CreatePersistentTokenCommand.groovy b/plugin/grails-app/commands/grails.plugin.springsecurity/S2CreatePersistentTokenCommand.groovy
new file mode 100644
index 000000000..fc60ebe4e
--- /dev/null
+++ b/plugin/grails-app/commands/grails.plugin.springsecurity/S2CreatePersistentTokenCommand.groovy
@@ -0,0 +1,69 @@
+/*
+ * 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 a persistent token domain class for the Spring Security Core plugin.
+ * Usage: ./gradlew runCommand "-Pargs=s2-create-persistent-token [DOMAIN CLASS NAME]"
+*
+ * For example: ./gradlew runCommand "-Pargs=s2-create-persistent-token com.yourapp.PersistentLogin"
+ *
+ * @author Puneet Behl
+ * @since 6.0.0
+ */
+@CompileStatic
+class S2CreatePersistentTokenCommand implements GrailsApplicationCommand, CommandLineHelper, SkipBootstrap {
+
+ @Delegate
+ ConsoleLogger consoleLogger = GrailsConsole.getInstance()
+
+ private final static USAGE_MESSAGE = '''
+./gradlew runCommand "-Pargs=s2-create-persistent-token [DOMAIN CLASS NAME]"
+
+For example: ./gradlew runCommand "-Pargs=s2-create-persistent-token com.yourapp.PersistentLogin"
+'''
+
+ @Override
+ boolean handle() {
+
+ if (args.size() == 0) {
+ consoleLogger.error("Usage: " + USAGE_MESSAGE)
+ return FAILURE
+ }
+
+ final String domainClass = args[0]
+ final Model domainModel = model(domainClass)
+ consoleLogger.addStatus ("\nCreating persistent token class $domainClass")
+ render(template: template("PersistentLogin.groovy.template"),
+ destination: file("grails-app/domain/$domainModel.packagePath/${domainModel.simpleName}.groovy"),
+ model: domainModel,
+ overrite: false
+ )
+ file('grails-app/conf/application.groovy').withWriterAppend { BufferedWriter writer ->
+ writer.newLine()
+ writer.writeLine 'grails.plugin.springsecurity.rememberMe.persistent = true'
+ writer.writeLine "grails.plugin.springsecurity.rememberMe.persistentToken.domainClassName = '$domainClass'"
+ writer.newLine()
+ }
+ return SUCCESS
+ }
+}
diff --git a/plugin/grails-app/commands/grails.plugin.springsecurity/S2CreateRoleHierarchyEntryCommand.groovy b/plugin/grails-app/commands/grails.plugin.springsecurity/S2CreateRoleHierarchyEntryCommand.groovy
new file mode 100644
index 000000000..88f0a9c5e
--- /dev/null
+++ b/plugin/grails-app/commands/grails.plugin.springsecurity/S2CreateRoleHierarchyEntryCommand.groovy
@@ -0,0 +1,68 @@
+/*
+ * 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 a domain class for a persistent role hierarchy for the Spring Security Core plugin
+ * Usage: ./gradlew runCommand "-Pargs=s2-create-role-hierarchy-entry [DOMAIN CLASS NAME]"
+ * For example: ./gradlew runCommand "-Pargs=s2-create-role-hierarchy-entry com.yourapp.RoleHierarchyEntry"
+ *
+ * @author Puneet Behl
+ * @since 6.0.0
+ */
+@CompileStatic
+class S2CreateRoleHierarchyEntryCommand implements GrailsApplicationCommand, CommandLineHelper, SkipBootstrap {
+
+ private static final String USAGE_MESSAGE = '''
+./gradlew runCommand "-Pargs=s2-create-role-hierarchy-entry [DOMAIN CLASS NAME]"
+
+ For example: ./gradlew runCommand "-Pargs=s2-create-role-hierarchy-entry com.yourapp.RoleHierarchyEntry"
+'''
+
+ @Delegate
+ ConsoleLogger consoleLogger = GrailsConsole.getInstance()
+
+ @Override
+ boolean handle() {
+
+ if (args.size() == 0) {
+ consoleLogger.error("Usage: " + USAGE_MESSAGE)
+ return FAILURE
+ }
+
+ final String domainClass = args[0]
+ final Model domainModel = model(domainClass)
+
+ consoleLogger.addStatus("\nCreating role hierarchy entry class $domainClass")
+ render(template: template('RoleHierarchyEntry.groovy.template'),
+ destination: file("grails-app/domain/$domainModel.packagePath/${domainModel.simpleName}.groovy"),
+ model: domainModel, overwrite: false)
+
+ file('grails-app/conf/application.groovy').withWriterAppend { BufferedWriter writer ->
+ writer.newLine()
+ writer.writeLine "grails.plugin.springsecurity.roleHierarchyEntryClassName = '$domainClass'"
+ writer.newLine()
+ }
+
+ return SUCCESS
+ }
+}
diff --git a/plugin/grails-app/commands/grails.plugin.springsecurity/S2QuickstartCommand.groovy b/plugin/grails-app/commands/grails.plugin.springsecurity/S2QuickstartCommand.groovy
new file mode 100644
index 000000000..ed331615c
--- /dev/null
+++ b/plugin/grails-app/commands/grails.plugin.springsecurity/S2QuickstartCommand.groovy
@@ -0,0 +1,316 @@
+/*
+ * 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