Merge "Freeze inspec version for salt-formulas tests 	- in inspec >= 3.0.0 there are changes in schema 	  which require to have tests/integration dir 	  to contain tests, otherwise fails 	- this is temporary workaround until we decide 	  proper fix"
diff --git a/README.rst b/README.rst
index e969e1f..ec12c3d 100644
--- a/README.rst
+++ b/README.rst
@@ -17,6 +17,103 @@
       server:
         enabled: true
 
+Keycloak service with realm using LDAP identity provider
+
+.. code-block:: yaml
+
+    keycloak:
+      server:
+        realm:
+          ldap-realm:
+            enabled: true
+            id: ldap-realm
+            client:
+              ldap-app:
+                enabled: true
+                base_url: /ldap-portal
+                redirect_uris:
+                - /ldap-portal/*
+                admin_url: /ldap-portal
+                secret: password
+                protocol_mapper:
+                  oidc-usermodel-property-mapper:
+                    username:
+                      name: username
+                      user_attribute: username
+                      claim_name: preferred_username
+                    given_name:
+                      name: given name
+                      user_attribute: firstName
+                      claim_name: given_name
+                    family_name:
+                      name: family name
+                      user_attribute: lastName
+                      claim_name: family_name
+                    email:
+                      name: email
+                      user_attribute: email
+                      claim_name: email
+                  oidc-full-name-mapper:
+                    full_name:
+                      name: full_name
+            federation_provider:
+              ldap:
+                display_name: ldap-server
+                users_dn: ou=people,dc=keycloak,dc=org
+                user_object_classes: inetOrgPerson, organizationalPerson
+                username_ldap_attribute: uid
+                bind_dn: cn=admin,dc=keycloak,dc=org
+                bind_credential: password
+                rdn_ldap_attribute: uid
+                edit_mode: READ_ONLY
+                uuid_ldap_attribute: entryUUID
+                connection_url: ldap://localhost:10389
+                sync_registrations: false
+            federation_mapper:
+              user-attribute-ldap-mapper:
+                username:
+                  name: username
+                  provider_display_name: ldap-server
+                  ldap_attribute: uid
+                  model_attribute: username
+                  mandatory: true
+                  read_only: false
+                  always_read: false
+                first_name:
+                  name: first name
+                  provider_display_name: ldap-server
+                  ldap_attribute: cn
+                  model_attribute: firstName
+                  mandatory: true
+                  read_only: false
+                  always_read: false
+                last_name:
+                  name: last name
+                  provider_display_name: ldap-server
+                  ldap_attribute: sn
+                  model_attribute: lastName
+                  mandatory: true
+                  read_only: false
+                  always_read: false
+                email:
+                  name: email
+                  provider_display_name: ldap-server
+                  ldap_attribute: mail
+                  model_attribute: email
+                  mandatory: false
+                  read_only: false
+                  always_read: false
+              role-ldap-mapper:
+                realm_roles:
+                  name: realm roles
+                  provider_display_name: ldap-server
+                  roles_dn: ou=groups,dc=cicd,dc=local
+                  membership_ldap_attribute: member
+                  role_name_ldap_attribute: cn
+                  role_object_classes: groupOfNames
+                  mode: LDAP_ONLY
+                  realm_roles_mapping: true
+
 
 References
 ==========
diff --git a/keycloak/files/server/realms.json b/keycloak/files/server/realms.json
new file mode 100644
index 0000000..0e8f450
--- /dev/null
+++ b/keycloak/files/server/realms.json
@@ -0,0 +1,120 @@
+{%- for realm_name, realm in realms.iteritems() %}
+
+{
+  "id": "{{ realm.get('id', realm_name) }}",
+  "realm": "{{ realm_name }}",
+  "enabled": {{ realm.get('enabled', true)|json }},
+  "sslRequired": "external",
+  "requiredCredentials": [ "password" ],
+  "clients": [
+    {%- for client_name, client in realm.get('client', {}).iteritems() %}
+    {
+      "clientId": "{{ client_name }}",
+      "enabled": {{ client.get("enabled", True)|json }},
+      "baseUrl": "{{ client.base_url }}",
+      "redirectUris": {{ client.redirect_uris|json }},
+      "adminUrl": "{{ client.admin_url }}",
+      "directAccessGrantsEnabled": {{ client.get('direct_access_grants_enabled', False)|json }},
+      "secret": "{{ client.secret }}",
+      "fullScopeAllowed": true,
+      "protocolMappers": [
+        {%- for type, mapper in client.get('protocol_mapper', {}).iteritems() %}
+        {%- set outer_loop = loop %}
+          {%- for _, params in mapper.iteritems() %}
+            {%- if type == "oidc-usermodel-property-mapper" %}
+        {
+          "protocolMapper" : "oidc-usermodel-property-mapper",
+          "protocol" : "openid-connect",
+          "name" : "{{ params.name }}",
+          "config" : {
+            "Claim JSON Type" : "String",
+            "user.attribute" : "{{ params.user_attribute }}",
+            "claim.name" : "{{ params.claim_name }}",
+            "id.token.claim" : "true",
+            "access.token.claim" : "true"
+          }
+        }{%- if not (loop.last and outer_loop.last) %},{%- endif %}
+            {%- elif type == "oidc-full-name-mapper" %}
+        {
+          "protocolMapper" : "oidc-full-name-mapper",
+          "protocol" : "openid-connect",
+          "name" : "{{ params.name }}",
+          "config" : {
+            "id.token.claim" : "true",
+            "access.token.claim" : "true"
+          }
+        }{%- if not (loop.last and outer_loop.last) %},{%- endif %}
+            {%- endif %}
+          {%- endfor %}
+        {%- endfor %}
+      ]
+    }{%- if not loop.last %},{%- endif %}
+  {%- endfor %}
+  ],
+  "userFederationProviders": [
+    {%- for provider_name, provider in realm.get("federation_provider", {}).iteritems() %}
+    {
+      "displayName": "{{ provider.display_name }}",
+      "providerName": "{{ provider_name }}",
+      "priority": 1,
+      "fullSyncPeriod": -1,
+      "changedSyncPeriod": -1,
+      "config": {
+        "pagination" : "true",
+        "debug" : "false",
+        "searchScope" : "1",
+        "connectionPooling" : "true",
+        "usersDn" : "{{ provider.users_dn }}",
+        "userObjectClasses" : "{{ provider.get('user_object_classes', 'inetOrgPerson') }}",
+        "usernameLDAPAttribute" : "{{ provider.get('username_ldap_attribute', 'uid') }}",
+        "bindDn" : "{{ provider.bind_dn }}",
+        "bindCredential" : "{{ provider.bind_credential }}",
+        "rdnLDAPAttribute" : "{{ provider.get('rdn_ldap_attribute', 'uid') }}",
+        "vendor" : "other",
+        "editMode" : "{{ provider.get('edit_mode', 'READ_ONLY') }}",
+        "uuidLDAPAttribute" : "{{ provider.get('uuid_ldap_attribute', 'entryUUID') }}",
+        "connectionUrl" : "{{ provider.get('connection_url', 'ldap://localhost:389') }}",
+        "syncRegistrations" : "{{ provider.get('sync_registrations', False)|json }}",
+        "authType" : "simple"
+      }
+    }{%- if not loop.last %},{%- endif %}
+    {%- endfor %}
+  ],
+  "userFederationMappers" : [
+    {%- for type, mapper in realm.get('federation_mapper', {}).iteritems() %}
+    {%- set outer_loop = loop %}
+      {%- for _, params in mapper.iteritems() %}
+        {%- if type == "user-attribute-ldap-mapper" %}
+    {
+      "name" : "{{ params.name }}",
+      "federationMapperType" : "user-attribute-ldap-mapper",
+      "federationProviderDisplayName" : "{{ params.provider_display_name }}",
+      "config" : {
+        "ldap.attribute" : "{{ params.ldap_attribute }}",
+        "user.model.attribute" : "{{ params.model_attribute }}",
+        "is.mandatory.in.ldap" : "{{ params.get('mandatory', True)|json }}",
+        "read.only" : "{{ params.get('read_only', False)|json }}",
+        "always.read.value.from.ldap" : "{{ params.get('always_read', False)|json }}"
+      }
+    }{%- if not (loop.last and outer_loop.last) %},{%- endif %}
+        {%- elif type == "role-ldap-mapper" %}
+    {
+      "name" : "{{ params.name }}",
+      "federationMapperType" : "role-ldap-mapper",
+      "federationProviderDisplayName" : "{{ params.provider_display_name }}",
+      "config" : {
+        "roles.dn" : "{{ params.roles_dn }}",
+        "membership.ldap.attribute" : "{{ params.get('membership_ldap_attribute', 'memberUid') }}",
+        "role.name.ldap.attribute" : "{{ params.get('role_name_ldap_attribute', 'cn') }}",
+        "role.object.classes" : "{{ params.get('role_object_classes', 'posixGroup') }}",
+        "mode" : "{{ params.get('mode', 'LDAP_ONLY') }}",
+        "use.realm.roles.mapping" : "{{ params.get('realm_roles_mapping', True)|json }}"
+      }
+    }{%- if not (loop.last and outer_loop.last) %},{%- endif %}
+        {%- endif %}
+      {%- endfor %}
+    {%- endfor %}
+  ]
+}{%- if not loop.last %},{%- endif %}
+
+{%- endfor %}
diff --git a/keycloak/map.jinja b/keycloak/map.jinja
index b0f53de..d699df6 100644
--- a/keycloak/map.jinja
+++ b/keycloak/map.jinja
@@ -1,6 +1,11 @@
-{%- load_yaml as base %}
-dir: /srv/volumes/keycloak
-{%- endload %}
+{% set proxy = salt['grains.filter_by']({
+    'default': {
+        'dir': '/srv/volumes/keycloak'
+    }
+}, merge=salt['pillar.get']('keycloak:proxy')) %}
 
-{%- set server = salt['pillar.get']('keycloak:server') %}
-{%- set proxy = salt['pillar.get']('keycloak:proxy') %}
+{% set server = salt['grains.filter_by']({
+    'default': {
+        'dir': '/srv/volumes/keycloak'
+    }
+}, merge=salt['pillar.get']('keycloak:server')) %}
diff --git a/keycloak/proxy/init.sls b/keycloak/proxy/init.sls
index 6ca45f4..a50b953 100644
--- a/keycloak/proxy/init.sls
+++ b/keycloak/proxy/init.sls
@@ -1,10 +1,9 @@
 {%- from "keycloak/map.jinja" import proxy with context %}
-{%- from "keycloak/map.jinja" import base with context %}
 
 {%- if proxy.enabled|default(False) %}
 keycloak_proxy_dir:
   file.directory:
-    - name: {{ base.dir }}/proxy
+    - name: {{ proxy.dir }}/proxy
     - user: root
     - group: root
     - mode: 0755
@@ -12,7 +11,7 @@
 
 keycloak_proxy_conf:
   file.managed:
-    - name: {{ base.dir }}/proxy/proxy.json
+    - name: {{ proxy.dir }}/proxy/proxy.json
     - source: salt://keycloak/files/proxy/proxy.json
     - template: jinja
     - user: root
diff --git a/keycloak/server/init.sls b/keycloak/server/init.sls
index e69de29..0b45945 100644
--- a/keycloak/server/init.sls
+++ b/keycloak/server/init.sls
@@ -0,0 +1,15 @@
+{%- from "keycloak/map.jinja" import server with context %}
+include:
+- keycloak.server.realm
+
+{%- if server.get("enabled", False) %}
+
+keycloak_server_dir:
+  file.directory:
+    - name: {{ server.dir }}/server
+    - user: root
+    - group: root
+    - mode: 0755
+    - makedirs: true
+
+{%- endif %}
diff --git a/keycloak/server/realm.sls b/keycloak/server/realm.sls
new file mode 100644
index 0000000..1b13ce9
--- /dev/null
+++ b/keycloak/server/realm.sls
@@ -0,0 +1,19 @@
+{%- from "keycloak/map.jinja" import server with context %}
+
+{%- if server.get("enabled", False) %}
+{%- if server.realm is defined %}
+
+keycloak_realms:
+  file.managed:
+    - name: {{ server.dir }}/server/realms.json
+    - source: salt://keycloak/files/server/realms.json
+    - template: jinja
+    - user: root
+    - mode: 0644
+    - defaults:
+        realms: {{ server.realm }}
+    - require:
+      - file: keycloak_server_dir
+
+{%- endif %}
+{%- endif %}