keycloak-aplcache

Details

diff --git a/broker/kerberos/pom.xml b/broker/kerberos/pom.xml
new file mode 100644
index 0000000..0c69371
--- /dev/null
+++ b/broker/kerberos/pom.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+    <parent>
+        <artifactId>keycloak-broker-parent</artifactId>
+        <groupId>org.keycloak</groupId>
+        <version>1.2.0.Beta1-SNAPSHOT</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>keycloak-broker-kerberos</artifactId>
+    <name>Keycloak Broker Kerberos</name>
+    <description/>
+    <packaging>jar</packaging>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-broker-core</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.jboss.logging</groupId>
+            <artifactId>jboss-logging</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>net.iharder</groupId>
+            <artifactId>base64</artifactId>
+            <scope>provided</scope>
+        </dependency>
+    </dependencies>
+</project>
diff --git a/broker/kerberos/src/main/java/org/keycloak/broker/kerberos/impl/KerberosServerSubjectAuthenticator.java b/broker/kerberos/src/main/java/org/keycloak/broker/kerberos/impl/KerberosServerSubjectAuthenticator.java
new file mode 100644
index 0000000..3384a43
--- /dev/null
+++ b/broker/kerberos/src/main/java/org/keycloak/broker/kerberos/impl/KerberosServerSubjectAuthenticator.java
@@ -0,0 +1,66 @@
+package org.keycloak.broker.kerberos.impl;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.security.auth.Subject;
+import javax.security.auth.login.AppConfigurationEntry;
+import javax.security.auth.login.Configuration;
+import javax.security.auth.login.LoginContext;
+import javax.security.auth.login.LoginException;
+
+import org.jboss.logging.Logger;
+import org.keycloak.broker.kerberos.KerberosIdentityProviderConfig;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class KerberosServerSubjectAuthenticator {
+
+    private static final Logger logger = Logger.getLogger(KerberosServerSubjectAuthenticator.class);
+
+    private final KerberosIdentityProviderConfig config;
+    private LoginContext loginContext;
+
+    public KerberosServerSubjectAuthenticator(KerberosIdentityProviderConfig config) {
+        this.config = config;
+    }
+
+    public Subject authenticateServerSubject() throws LoginException {
+        Configuration config = createJaasConfiguration();
+        loginContext = new LoginContext("does-not-matter", null, null, config);
+        loginContext.login();
+        return loginContext.getSubject();
+    }
+
+    public void logoutServerSubject() {
+        if (loginContext != null) {
+            try {
+                loginContext.logout();
+            } catch (LoginException le) {
+                logger.error("Failed to logout kerberos server subject: " + config.getServerPrincipal(), le);
+            }
+        }
+    }
+
+    protected Configuration createJaasConfiguration() {
+        return new Configuration() {
+
+            @Override
+            public AppConfigurationEntry[] getAppConfigurationEntry(String name) {
+                Map<String, Object> options = new HashMap<String, Object>();
+                options.put("storeKey", "true");
+                options.put("doNotPrompt", "true");
+                options.put("isInitiator", "false");
+                options.put("useKeyTab", "true");
+
+                options.put("keyTab", config.getKeyTab());
+                options.put("principal", config.getServerPrincipal());
+                options.put("debug", String.valueOf(config.getDebug()));
+                AppConfigurationEntry kerberosLMConfiguration = new AppConfigurationEntry("com.sun.security.auth.module.Krb5LoginModule", AppConfigurationEntry.LoginModuleControlFlag.REQUIRED, options);
+                return new AppConfigurationEntry[] { kerberosLMConfiguration };
+            }
+        };
+    }
+
+}
diff --git a/broker/kerberos/src/main/java/org/keycloak/broker/kerberos/impl/SPNEGOAuthenticator.java b/broker/kerberos/src/main/java/org/keycloak/broker/kerberos/impl/SPNEGOAuthenticator.java
new file mode 100644
index 0000000..cbac036
--- /dev/null
+++ b/broker/kerberos/src/main/java/org/keycloak/broker/kerberos/impl/SPNEGOAuthenticator.java
@@ -0,0 +1,138 @@
+package org.keycloak.broker.kerberos.impl;
+
+import java.io.IOException;
+import java.security.PrivilegedExceptionAction;
+
+import javax.security.auth.Subject;
+
+import net.iharder.Base64;
+import org.ietf.jgss.GSSContext;
+import org.ietf.jgss.GSSCredential;
+import org.ietf.jgss.GSSException;
+import org.ietf.jgss.GSSManager;
+import org.ietf.jgss.Oid;
+import org.jboss.logging.Logger;
+import org.keycloak.broker.kerberos.KerberosConstants;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class SPNEGOAuthenticator {
+
+    private static final Logger logger = Logger.getLogger(SPNEGOAuthenticator.class);
+
+    private static final GSSManager GSS_MANAGER = GSSManager.getInstance();
+
+    private final KerberosServerSubjectAuthenticator kerberosSubjectAuthenticator;
+    private final String spnegoToken;
+
+    private boolean authenticated = false;
+    private String principal = null;
+    private GSSCredential delegationCredential;
+    private String responseToken = null;
+
+    public SPNEGOAuthenticator(KerberosServerSubjectAuthenticator kerberosSubjectAuthenticator, String spnegoToken) {
+        this.kerberosSubjectAuthenticator = kerberosSubjectAuthenticator;
+        this.spnegoToken = spnegoToken;
+    }
+
+    public void authenticate() {
+        // TODO: debug
+        logger.info("SPNEGO Login with token: " + spnegoToken);
+
+        try {
+            Subject serverSubject = kerberosSubjectAuthenticator.authenticateServerSubject();
+            authenticated = Subject.doAs(serverSubject, new AcceptSecContext());
+        } catch (Exception e) {
+            logger.warn("SPNEGO login failed: " + e.getMessage());
+
+            // TODO: debug and check if it is shown in the log
+            if (logger.isInfoEnabled()) {
+                logger.info("SPNEGO login failed: " + e.getMessage(), e);
+            }
+        } finally {
+            kerberosSubjectAuthenticator.logoutServerSubject();
+        }
+    }
+
+    public boolean isAuthenticated() {
+        return authenticated;
+    }
+
+    public String getPrincipal() {
+        return principal;
+    }
+
+    public String getResponseToken() {
+        return responseToken;
+    }
+
+    public GSSCredential getDelegationCredential() {
+        return delegationCredential;
+    }
+
+    private class AcceptSecContext implements PrivilegedExceptionAction<Boolean> {
+
+        @Override
+        public Boolean run() throws Exception {
+            GSSContext gssContext = null;
+            try {
+                // TODO: debug
+                logger.info("Going to establish security context");
+                gssContext = establishContext();
+                logAuthDetails(gssContext);
+
+                // What should be done with delegation credential? Figure out if there are use-cases for storing it as claims in FederatedIdentity
+                if (gssContext.getCredDelegState()) {
+                    delegationCredential = gssContext.getDelegCred();
+                }
+
+                if (gssContext.isEstablished()) {
+                    principal = gssContext.getSrcName().toString();
+                    return true;
+                } else {
+                    return false;
+                }
+            } finally {
+                if (gssContext != null) {
+                    gssContext.dispose();
+                }
+            }
+        }
+
+    }
+
+    protected GSSContext establishContext() throws GSSException, IOException {
+        Oid spnegoOid = new Oid(KerberosConstants.SPNEGO_OID);
+        GSSCredential credential = GSS_MANAGER.createCredential(null,
+                GSSCredential.DEFAULT_LIFETIME,
+                spnegoOid,
+                GSSCredential.ACCEPT_ONLY);
+        GSSContext gssContext = GSS_MANAGER.createContext(credential);
+
+        byte[] inputToken = Base64.decode(spnegoToken);
+        byte[] respToken = gssContext.acceptSecContext(inputToken, 0, inputToken.length);
+        responseToken = Base64.encodeBytes(respToken);
+
+        return gssContext;
+    }
+
+    protected void logAuthDetails(GSSContext gssContext) throws GSSException {
+
+        // TODO: debug
+        if (logger.isInfoEnabled()) {
+            String message = new StringBuilder("SPNEGO Security context accepted with token: " + responseToken)
+                    .append(", established: " + gssContext.isEstablished())
+                    .append(", credDelegState: " + gssContext.getCredDelegState())
+                    .append(", mutualAuthState: " + gssContext.getMutualAuthState())
+                    .append(", lifetime: " + gssContext.getLifetime())
+                    .append(", confState: " + gssContext.getConfState())
+                    .append(", integState: " + gssContext.getIntegState())
+                    .append(", srcName: " + gssContext.getSrcName())
+                    .append(", targName: " + gssContext.getTargName())
+                    .toString();
+            logger.info(message);
+        }
+    }
+
+}
diff --git a/broker/kerberos/src/main/java/org/keycloak/broker/kerberos/KerberosConstants.java b/broker/kerberos/src/main/java/org/keycloak/broker/kerberos/KerberosConstants.java
new file mode 100644
index 0000000..f84cb90
--- /dev/null
+++ b/broker/kerberos/src/main/java/org/keycloak/broker/kerberos/KerberosConstants.java
@@ -0,0 +1,25 @@
+package org.keycloak.broker.kerberos;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class KerberosConstants {
+
+    /**
+     *  Value of HTTP Headers "WWW-Authenticate" or "Authorization" used for SPNEGO/Kerberos
+     **/
+    public static final String NEGOTIATE = "Negotiate";
+
+
+    /**
+     * Helper parameter for relay state
+     */
+    public static final String RELAY_STATE_PARAM = "RelayState";
+
+
+    /**
+     * OID of SPNEGO mechanism. See http://www.oid-info.com/get/1.3.6.1.5.5.2
+     */
+    public static final String SPNEGO_OID = "1.3.6.1.5.5.2";
+
+}
diff --git a/broker/kerberos/src/main/java/org/keycloak/broker/kerberos/KerberosIdentityProvider.java b/broker/kerberos/src/main/java/org/keycloak/broker/kerberos/KerberosIdentityProvider.java
new file mode 100644
index 0000000..e2ae6be
--- /dev/null
+++ b/broker/kerberos/src/main/java/org/keycloak/broker/kerberos/KerberosIdentityProvider.java
@@ -0,0 +1,127 @@
+package org.keycloak.broker.kerberos;
+
+import java.net.URI;
+
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriBuilder;
+import javax.ws.rs.core.UriInfo;
+
+import org.jboss.logging.Logger;
+import org.keycloak.broker.kerberos.impl.KerberosServerSubjectAuthenticator;
+import org.keycloak.broker.kerberos.impl.SPNEGOAuthenticator;
+import org.keycloak.broker.provider.AbstractIdentityProvider;
+import org.keycloak.broker.provider.AuthenticationRequest;
+import org.keycloak.broker.provider.AuthenticationResponse;
+import org.keycloak.broker.provider.FederatedIdentity;
+import org.keycloak.models.FederatedIdentityModel;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class KerberosIdentityProvider extends AbstractIdentityProvider<KerberosIdentityProviderConfig> {
+
+    private static final Logger logger = Logger.getLogger(KerberosIdentityProvider.class);
+
+    public KerberosIdentityProvider(KerberosIdentityProviderConfig config) {
+        super(config);
+    }
+
+
+    @Override
+    public AuthenticationResponse handleRequest(AuthenticationRequest request) {
+        // TODO: trace
+        logger.info("handleRequest");
+
+        // Just redirect to handleResponse for now
+        URI redirectUri = UriBuilder.fromUri(request.getRedirectUri()).queryParam(KerberosConstants.RELAY_STATE_PARAM, request.getState()).build();
+        Response response = Response.status(302)
+                .location(redirectUri)
+                .build();
+
+        return AuthenticationResponse.fromResponse(response);
+    }
+
+
+    @Override
+    public String getRelayState(AuthenticationRequest request) {
+        UriInfo uriInfo = request.getUriInfo();
+        return uriInfo.getQueryParameters().getFirst(KerberosConstants.RELAY_STATE_PARAM);
+    }
+
+
+    @Override
+    public AuthenticationResponse handleResponse(AuthenticationRequest request) {
+        String authHeader = request.getHttpRequest().getHttpHeaders().getRequestHeaders().getFirst(HttpHeaders.AUTHORIZATION);
+
+        // Case when we don't yet have any Negotiate header
+        if (authHeader == null) {
+            return sendNegotiateResponse(null);
+        }
+
+        String[] tokens = authHeader.split(" ");
+        if (tokens.length != 2) {
+            logger.warn("Invalid length of tokens: " + tokens.length);
+            return sendNegotiateResponse(null);
+        } else if (!KerberosConstants.NEGOTIATE.equalsIgnoreCase(tokens[0])) {
+            logger.warn("Unknown scheme " + tokens[0]);
+            return sendNegotiateResponse(null);
+        } else {
+            String spnegoToken = tokens[1];
+            SPNEGOAuthenticator spnegoAuthenticator = createSPNEGOAuthenticator(spnegoToken);
+            spnegoAuthenticator.authenticate();
+
+            if (spnegoAuthenticator.isAuthenticated()) {
+                FederatedIdentity federatedIdentity = getFederatedIdentity(spnegoAuthenticator);
+                return AuthenticationResponse.end(federatedIdentity);
+            }  else {
+                return sendNegotiateResponse(spnegoAuthenticator.getResponseToken());
+            }
+        }
+    }
+
+    protected SPNEGOAuthenticator createSPNEGOAuthenticator(String spnegoToken) {
+        KerberosServerSubjectAuthenticator kerberosAuth = createKerberosSubjectAuthenticator();
+        return new SPNEGOAuthenticator(kerberosAuth, spnegoToken);
+    }
+
+    protected KerberosServerSubjectAuthenticator createKerberosSubjectAuthenticator() {
+        return new KerberosServerSubjectAuthenticator(getConfig());
+    }
+
+
+    /**
+     * Send response with header "WWW-Authenticate: Negotiate {negotiateToken}"
+     *
+     * @param negotiateToken token to be send back in response or null if just "WWW-Authenticate: Negotiate" should be sent
+     * @return AuthenticationResponse
+     */
+    protected AuthenticationResponse sendNegotiateResponse(String negotiateToken) {
+        String negotiateHeader = negotiateToken == null ? KerberosConstants.NEGOTIATE : KerberosConstants.NEGOTIATE + " " + negotiateToken;
+
+        Response response = Response.status(Response.Status.UNAUTHORIZED)
+                .header(HttpHeaders.WWW_AUTHENTICATE, negotiateHeader)
+                .build();
+        return AuthenticationResponse.fromResponse(response);
+    }
+
+
+    protected FederatedIdentity getFederatedIdentity(SPNEGOAuthenticator spnegoAuthenticator) {
+        String kerberosUsername = spnegoAuthenticator.getPrincipal();
+        FederatedIdentity user = new FederatedIdentity(kerberosUsername);
+        user.setUsername(kerberosUsername);
+
+        // Just guessing email, but likely can't do anything better...
+        String[] tokens = kerberosUsername.split("@");
+        String email = tokens[0] + "@" + tokens[1].toLowerCase();
+        user.setEmail(email);
+        return user;
+    }
+
+
+    @Override
+    public Response retrieveToken(FederatedIdentityModel identity) {
+        logger.warn("retrieveToken unsupported for Kerberos right now");
+        return null;
+    }
+}
diff --git a/broker/kerberos/src/main/java/org/keycloak/broker/kerberos/KerberosIdentityProviderConfig.java b/broker/kerberos/src/main/java/org/keycloak/broker/kerberos/KerberosIdentityProviderConfig.java
new file mode 100644
index 0000000..50dd85a
--- /dev/null
+++ b/broker/kerberos/src/main/java/org/keycloak/broker/kerberos/KerberosIdentityProviderConfig.java
@@ -0,0 +1,26 @@
+package org.keycloak.broker.kerberos;
+
+import org.keycloak.models.IdentityProviderModel;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class KerberosIdentityProviderConfig extends IdentityProviderModel {
+
+    public KerberosIdentityProviderConfig(IdentityProviderModel identityProviderModel) {
+        super(identityProviderModel);
+    }
+
+    public String getServerPrincipal() {
+        return getConfig().get("serverPrincipal");
+    }
+
+    public String getKeyTab() {
+        return getConfig().get("keyTab");
+    }
+
+    public boolean getDebug() {
+        return Boolean.valueOf(getConfig().get("debug"));
+    }
+
+}
diff --git a/broker/kerberos/src/main/java/org/keycloak/broker/kerberos/KerberosIdentityProviderFactory.java b/broker/kerberos/src/main/java/org/keycloak/broker/kerberos/KerberosIdentityProviderFactory.java
new file mode 100644
index 0000000..e99f7d5
--- /dev/null
+++ b/broker/kerberos/src/main/java/org/keycloak/broker/kerberos/KerberosIdentityProviderFactory.java
@@ -0,0 +1,28 @@
+package org.keycloak.broker.kerberos;
+
+import org.keycloak.broker.kerberos.KerberosIdentityProvider;
+import org.keycloak.broker.provider.AbstractIdentityProviderFactory;
+import org.keycloak.models.IdentityProviderModel;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class KerberosIdentityProviderFactory extends AbstractIdentityProviderFactory<KerberosIdentityProvider> {
+
+    public static final String PROVIDER_ID = "kerberos";
+
+    @Override
+    public String getId() {
+        return PROVIDER_ID;
+    }
+
+    @Override
+    public String getName() {
+        return "Kerberos";
+    }
+
+    @Override
+    public KerberosIdentityProvider create(IdentityProviderModel model) {
+        return new KerberosIdentityProvider(new KerberosIdentityProviderConfig(model));
+    }
+}
diff --git a/broker/kerberos/src/main/resources/META-INF/services/org.keycloak.broker.provider.IdentityProviderFactory b/broker/kerberos/src/main/resources/META-INF/services/org.keycloak.broker.provider.IdentityProviderFactory
new file mode 100644
index 0000000..4c72cb8
--- /dev/null
+++ b/broker/kerberos/src/main/resources/META-INF/services/org.keycloak.broker.provider.IdentityProviderFactory
@@ -0,0 +1 @@
+org.keycloak.broker.kerberos.KerberosIdentityProviderFactory
\ No newline at end of file

broker/pom.xml 1(+1 -0)

diff --git a/broker/pom.xml b/broker/pom.xml
index 7121b2b..e2ae6ff 100755
--- a/broker/pom.xml
+++ b/broker/pom.xml
@@ -18,6 +18,7 @@
         <module>core</module>
         <module>oidc</module>
         <module>saml</module>
+        <module>kerberos</module>
     </modules>
 
 </project>
diff --git a/dependencies/server-all/pom.xml b/dependencies/server-all/pom.xml
index 5d5576b..c2546e8 100755
--- a/dependencies/server-all/pom.xml
+++ b/dependencies/server-all/pom.xml
@@ -95,6 +95,11 @@
         </dependency>
         <dependency>
             <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-broker-kerberos</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
             <artifactId>keycloak-social-github</artifactId>
             <version>${project.version}</version>
         </dependency>
diff --git a/forms/common-themes/src/main/resources/theme/admin/base/resources/js/controllers/realm.js b/forms/common-themes/src/main/resources/theme/admin/base/resources/js/controllers/realm.js
index 98a6e6b..4971ee1 100755
--- a/forms/common-themes/src/main/resources/theme/admin/base/resources/js/controllers/realm.js
+++ b/forms/common-themes/src/main/resources/theme/admin/base/resources/js/controllers/realm.js
@@ -785,6 +785,14 @@ module.controller('RealmIdentityProviderCtrl', function($scope, $filter, $upload
             $scope.identityProvider.config.postBindingResponse = true;
         }
     }
+
+    $scope.initKerberosProvider = function() {
+        if (instance && instance.id) {
+            $scope.identityProvider.config.debug = $scope.getBoolean($scope.identityProvider.config.debug);
+        } else {
+            $scope.identityProvider.config.debug = false;
+        }
+    }
 });
 
 module.controller('RealmTokenDetailCtrl', function($scope, Realm, realm, $http, $location, $route, Dialog, Notifications, TimeUnit) {
diff --git a/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/realm-identity-provider-kerberos.html b/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/realm-identity-provider-kerberos.html
new file mode 100644
index 0000000..489628a
--- /dev/null
+++ b/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/realm-identity-provider-kerberos.html
@@ -0,0 +1,71 @@
+<div class="bs-sidebar col-sm-3 " data-ng-include data-src="'partials/realm-menu.html'"></div>
+<div id="content-area" class="col-sm-9" role="main" data-ng-init="initKerberosProvider()">
+    <data-kc-navigation data-kc-current="social" data-kc-realm="realm.realm" data-kc-social="realm.social"></data-kc-navigation>
+    <h2></h2>
+    <div id="content">
+        <ol class="breadcrumb">
+            <li><a href="#/realms/{{realm.realm}}/identity-provider-settings">Identity Providers</a></li>
+            <li class="active">{{identityProvider.name}} Provider Settings</li>
+        </ol>
+        <h2 class="pull-left">{{identityProvider.name}} Provider Settings</h2>
+        <p class="subtitle"><span class="required">*</span> Required fields</p>
+        <form class="form-horizontal" name="realmForm" novalidate>
+            <fieldset>
+                <div class="form-group clearfix">
+                    <label class="col-sm-2 control-label" for="identifier">Alias <span class="required">*</span></label>
+                    <div class="col-sm-4">
+                        <input class="form-control" id="identifier" type="text" ng-model="identityProvider.id" required>
+                    </div>
+                    <span tooltip-placement="right" tooltip="The alias unique identifies an identity provider and it is also used to build the redirect uri." class="fa fa-info-circle"></span>
+                </div>
+                <div class="form-group clearfix">
+                    <label class="col-sm-2 control-label" for="name">Name <span class="required">*</span></label>
+                    <div class="col-sm-4">
+                        <input class="form-control" id="name" type="text" ng-model="identityProvider.name" required>
+                    </div>
+                    <span tooltip-placement="right" tooltip="The friendly name for this identity provider." class="fa fa-info-circle"></span>
+                </div>
+                <div class="form-group clearfix">
+                    <label class="col-sm-2 control-label" for="serverPrincipal">Server principal <span class="required">*</span></label>
+                    <div class="col-sm-4">
+                        <input class="form-control" id="serverPrincipal" type="text" ng-model="identityProvider.config.serverPrincipal" required>
+                    </div>
+                    <span tooltip-placement="right" tooltip="Full name of server principal for HTTP service including server and domain name. For example HTTP/host.foo.org@FOO.ORG" class="fa fa-info-circle"></span>
+                </div>
+                <div class="form-group clearfix">
+                    <label class="col-sm-2 control-label" for="keyTab">KeyTab <span class="required">*</span></label>
+                    <div class="col-sm-4">
+                        <input class="form-control" id="keyTab" type="text" ng-model="identityProvider.config.keyTab" required>
+                    </div>
+                    <span tooltip-placement="right" tooltip="Location of Kerberos KeyTab file containing the credentials of server principal. For example /etc/krb5.keytab" class="fa fa-info-circle"></span>
+                </div>
+                <div class="form-group">
+                    <label class="col-sm-2 control-label" for="debug">Debug </label>
+                    <div class="col-sm-4">
+                        <input ng-model="identityProvider.config.debug" id="debug" onoffswitch />
+                    </div>
+                    <span tooltip-placement="right" tooltip="Enable/disable debug logging to standard output for Krb5LoginModule." class="fa fa-info-circle"></span>
+                </div>
+                <div class="form-group">
+                    <label class="col-sm-2 control-label" for="enabled">Enabled</label>
+                    <div class="col-sm-4">
+                        <input ng-model="identityProvider.enabled" id="enabled" onoffswitch />
+                    </div>
+                    <span tooltip-placement="right" tooltip="Enable/disable this identity provider." class="fa fa-info-circle"></span>
+                </div>
+                <div class="form-group">
+                    <label class="col-sm-2 control-label" for="updateProfileFirstLogin">Update Profile on First Login</label>
+                    <div class="col-sm-4">
+                        <input ng-model="identityProvider.updateProfileFirstLogin" name="identityProvider.updateProfileFirstLogin" id="updateProfileFirstLogin" onoffswitch />
+                    </div>
+                    <span tooltip-placement="right" tooltip="Indicates if user must update his profile right after the first login." class="fa fa-info-circle"></span>
+                </div>
+            </fieldset>
+
+            <div class="pull-right form-actions">
+                <button kc-save>Save</button>
+                <button kc-delete data-ng-click="remove()" data-ng-show="!newIdentityProvider">Delete</button>
+            </div>
+        </form>
+    </div>
+</div>