diff --git a/cli/cmd/generate.go b/cli/cmd/generate.go index 05a7b29..1f2f9f1 100644 --- a/cli/cmd/generate.go +++ b/cli/cmd/generate.go @@ -20,15 +20,17 @@ package cmd import ( "fmt" + "io/ioutil" "os" "path" + "text/template" kcimport "github.com/nmasse-itix/keycloak-realm-import" "github.com/spf13/cobra" ) var realmCount, clientCount, userCount int -var targetDir string +var targetDir, customTemplateFile string // generateCmd represents the generate command var generateCmd = &cobra.Command{ @@ -40,14 +42,32 @@ var generateCmd = &cobra.Command{ if err != nil { logger.Fatal(err) } + + var customTemplate *template.Template + if customTemplateFile != "" { + b, err := ioutil.ReadFile(customTemplateFile) + if err != nil { + logger.Fatal(err) + } + + content := string(b) + customTemplate, err = kcimport.GetRealmTemplate(content) + } realms := kcimport.GenerateRealms(realmCount, clientCount, userCount) + for _, realm := range realms { f, err := os.OpenFile(path.Join(targetDir, fmt.Sprintf("realm-%s.json", realm.ID)), os.O_TRUNC|os.O_CREATE|os.O_WRONLY, 0666) if err != nil { logger.Fatal(err) } defer f.Close() - err = kcimport.WriteRealmFile(realm, f) + + if customTemplate != nil { + err = kcimport.WriteRealmFileWithTemplate(realm, f, customTemplate) + } else { + err = kcimport.WriteRealmFile(realm, f) + } + if err != nil { logger.Fatal(err) } @@ -61,4 +81,5 @@ func init() { generateCmd.Flags().IntVar(&clientCount, "clients", 10, "number of clients to generate per realm") generateCmd.Flags().IntVar(&userCount, "users", 10, "number of users to generate per realm") generateCmd.Flags().StringVar(&targetDir, "target", ".", "target directory") + generateCmd.Flags().StringVar(&customTemplateFile, "template", "", "go template used to generate the realm") } diff --git a/examples/custom-with-ldap.template b/examples/custom-with-ldap.template new file mode 100644 index 0000000..a5d3a22 --- /dev/null +++ b/examples/custom-with-ldap.template @@ -0,0 +1,319 @@ +{ + "id": "realm_{{ .ID }}", + "realm": "realm_{{ .ID }}", + "displayName": "realm_{{ .ID }}", + "notBefore": 0, + "revokeRefreshToken": false, + "refreshTokenMaxReuse": 0, + "accessTokenLifespan": 300, + "accessTokenLifespanForImplicitFlow": 900, + "ssoSessionIdleTimeout": 1800, + "ssoSessionMaxLifespan": 36000, + "offlineSessionIdleTimeout": 2592000, + "accessCodeLifespan": 60, + "accessCodeLifespanUserAction": 300, + "accessCodeLifespanLogin": 1800, + "actionTokenGeneratedByAdminLifespan": 43200, + "actionTokenGeneratedByUserLifespan": 300, + "enabled": true, + "sslRequired": "external", + "registrationAllowed": false, + "registrationEmailAsUsername": false, + "rememberMe": false, + "verifyEmail": false, + "loginWithEmailAllowed": true, + "duplicateEmailsAllowed": false, + "resetPasswordAllowed": false, + "editUsernameAllowed": false, + "bruteForceProtected": false, + "permanentLockout": false, + "maxFailureWaitSeconds": 900, + "minimumQuickLoginWaitSeconds": 60, + "waitIncrementSeconds": 60, + "quickLoginCheckMilliSeconds": 1000, + "maxDeltaTimeSeconds": 43200, + "failureFactor": 30, + "users": [ +{{- range $count, $user := .Users }} +{{- if gt $count 0 }},{{ end }} + { + "username": "user_{{ $user.ID }}", + "firstName": "User", + "lastName": "{{ $user.ID }}", + "email": "user_{{ $user.ID }}@nowhere.test", + "emailVerified": true, + "enabled": true, + "credentials": [ + { + "type": "password", + "value": "user_{{ $user.ID }}" + } + ], + "requiredActions": [], + "realmRoles": [], + "applicationRoles": {} + } +{{- end }} + ], + "roles": { + "realm": [], + "client": {} + }, + "defaultRoles": [], + "requiredCredentials": [ "password" ], + "scopeMappings": [], + "clientScopeMappings": {}, + "clients": [ +{{- range $count, $client := .Clients }} +{{- if gt $count 0 }},{{ end }} + { + "clientId": "app_{{ $client.ID }}", + "name": "app_{{ $client.ID }}", + "enabled": true, + "publicClient": false, + "redirectUris": [ + "http://dummy/url" + ], + "fullScopeAllowed": false, + "standardFlowEnabled": true, + "directAccessGrantsEnabled": true, + "serviceAccountsEnabled": true, + "clientAuthenticatorType": "client-secret", + "secret": "{{ $client.Secret }}" + } +{{- end }} + ], + "components": { + "org.keycloak.storage.UserStorageProvider": [ + { + "name": "ldap", + "providerId": "ldap", + "subComponents": { + "org.keycloak.storage.ldap.mappers.LDAPStorageMapper": [ + { + "name": "modify date", + "providerId": "user-attribute-ldap-mapper", + "subComponents": {}, + "config": { + "ldap.attribute": [ + "modifyTimestamp" + ], + "is.mandatory.in.ldap": [ + "false" + ], + "always.read.value.from.ldap": [ + "true" + ], + "read.only": [ + "true" + ], + "user.model.attribute": [ + "modifyTimestamp" + ] + } + }, + { + "name": "username", + "providerId": "user-attribute-ldap-mapper", + "subComponents": {}, + "config": { + "ldap.attribute": [ + "uid" + ], + "is.mandatory.in.ldap": [ + "true" + ], + "read.only": [ + "true" + ], + "always.read.value.from.ldap": [ + "false" + ], + "user.model.attribute": [ + "username" + ] + } + }, + { + "name": "first name", + "providerId": "user-attribute-ldap-mapper", + "subComponents": {}, + "config": { + "ldap.attribute": [ + "cn" + ], + "is.mandatory.in.ldap": [ + "true" + ], + "read.only": [ + "true" + ], + "always.read.value.from.ldap": [ + "true" + ], + "user.model.attribute": [ + "firstName" + ] + } + }, + { + "name": "email", + "providerId": "user-attribute-ldap-mapper", + "subComponents": {}, + "config": { + "ldap.attribute": [ + "mail" + ], + "is.mandatory.in.ldap": [ + "false" + ], + "always.read.value.from.ldap": [ + "false" + ], + "read.only": [ + "true" + ], + "user.model.attribute": [ + "email" + ] + } + }, + { + "name": "last name", + "providerId": "user-attribute-ldap-mapper", + "subComponents": {}, + "config": { + "ldap.attribute": [ + "sn" + ], + "is.mandatory.in.ldap": [ + "true" + ], + "always.read.value.from.ldap": [ + "true" + ], + "read.only": [ + "true" + ], + "user.model.attribute": [ + "lastName" + ] + } + }, + { + "name": "creation date", + "providerId": "user-attribute-ldap-mapper", + "subComponents": {}, + "config": { + "ldap.attribute": [ + "createTimestamp" + ], + "is.mandatory.in.ldap": [ + "false" + ], + "always.read.value.from.ldap": [ + "true" + ], + "read.only": [ + "true" + ], + "user.model.attribute": [ + "createTimestamp" + ] + } + } + ] + }, + "config": { + "pagination": [ + "true" + ], + "fullSyncPeriod": [ + "-1" + ], + "usersDn": [ + "ou=users,dc=keycloak,dc=org" + ], + "connectionPooling": [ + "true" + ], + "cachePolicy": [ + "DEFAULT" + ], + "useKerberosForPasswordAuthentication": [ + "false" + ], + "importEnabled": [ + "true" + ], + "enabled": [ + "true" + ], + "bindCredential": [ + "keycloak" + ], + "bindDn": [ + "cn=admin,dc=keycloak,dc=org" + ], + "changedSyncPeriod": [ + "-1" + ], + "usernameLDAPAttribute": [ + "uid" + ], + "lastSync": [ + "1611161804" + ], + "vendor": [ + "other" + ], + "uuidLDAPAttribute": [ + "entryUUID" + ], + "connectionUrl": [ + "ldap://openldap.dns.podman:389/" + ], + "allowKerberosAuthentication": [ + "false" + ], + "syncRegistrations": [ + "false" + ], + "authType": [ + "simple" + ], + "debug": [ + "false" + ], + "searchScope": [ + "1" + ], + "useTruststoreSpi": [ + "ldapsOnly" + ], + "priority": [ + "0" + ], + "trustEmail": [ + "true" + ], + "userObjectClasses": [ + "inetOrgPerson, organizationalPerson" + ], + "rdnLDAPAttribute": [ + "uid" + ], + "editMode": [ + "READ_ONLY" + ], + "validatePasswordPolicy": [ + "false" + ], + "batchSizeForSync": [ + "1000" + ] + } + } + ] + } +} \ No newline at end of file diff --git a/generate.go b/generate.go index b12147d..8deab9f 100644 --- a/generate.go +++ b/generate.go @@ -57,7 +57,7 @@ func init() { fmt.Printf("init: %s\n", err) } - defaultTemplate, err = getTemplate(statikFS, "/realm.template", "realm") + defaultTemplate, err = getTemplate(statikFS, "/realm.template") if err != nil { fmt.Printf("init: %s\n", err) } @@ -100,31 +100,25 @@ func WriteRealmFile(realm GeneratedRealm, out io.Writer) error { return WriteRealmFileWithTemplate(realm, out, defaultTemplate) } -func getTemplate(statikFS http.FileSystem, filename string, name string) (*template.Template, error) { - tmpl := template.New(name) - content, err := slurpFile(statikFS, filename) - if err != nil { - return nil, err - } - +func GetRealmTemplate(content string) (*template.Template, error) { + tmpl := template.New("realm") customFunctions := template.FuncMap{ // TODO } - return tmpl.Funcs(customFunctions).Parse(content) } -func slurpFile(statikFS http.FileSystem, filename string) (string, error) { +func getTemplate(statikFS http.FileSystem, filename string) (*template.Template, error) { fd, err := statikFS.Open(filename) if err != nil { - return "", err + return nil, err } defer fd.Close() content, err := ioutil.ReadAll(fd) if err != nil { - return "", err + return nil, err } - return string(content), nil + return GetRealmTemplate(string(content)) }