Repository: karaf
Updated Branches:
  refs/heads/karaf-2.3.x 743e8cde8 -> 54e7493cd


[KARAF-3166] Add JAAS SyncopeLoginModule


Project: http://git-wip-us.apache.org/repos/asf/karaf/repo
Commit: http://git-wip-us.apache.org/repos/asf/karaf/commit/855d1983
Tree: http://git-wip-us.apache.org/repos/asf/karaf/tree/855d1983
Diff: http://git-wip-us.apache.org/repos/asf/karaf/diff/855d1983

Branch: refs/heads/karaf-2.3.x
Commit: 855d1983624e0a6f8e8d1dbd16e9377295e93195
Parents: 743e8cd
Author: Jean-Baptiste Onofré <[email protected]>
Authored: Mon Aug 18 11:38:18 2014 +0200
Committer: Jean-Baptiste Onofré <[email protected]>
Committed: Mon Aug 18 11:40:47 2014 +0200

----------------------------------------------------------------------
 jaas/modules/pom.xml                            |  14 +-
 .../modules/syncope/SyncopeBackingEngine.java   | 175 +++++++++++++++++++
 .../syncope/SyncopeBackingEngineFactory.java    |  51 ++++++
 .../modules/syncope/SyncopeLoginModule.java     | 154 ++++++++++++++++
 .../modules/syncope/SyncopeLoginModuleTest.java | 105 +++++++++++
 .../developers-guide/security-framework.conf    |  49 ++++++
 6 files changed, 547 insertions(+), 1 deletion(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/karaf/blob/855d1983/jaas/modules/pom.xml
----------------------------------------------------------------------
diff --git a/jaas/modules/pom.xml b/jaas/modules/pom.xml
index 3ebf7f7..c645957 100644
--- a/jaas/modules/pom.xml
+++ b/jaas/modules/pom.xml
@@ -53,6 +53,13 @@
         </dependency>
 
         <dependency>
+            <groupId>org.apache.httpcomponents</groupId>
+            <artifactId>httpclient</artifactId>
+            <version>4.3.5</version>
+            <scope>provided</scope>
+        </dependency>
+
+        <dependency>
             <groupId>org.apache.felix</groupId>
             <artifactId>org.apache.felix.fileinstall</artifactId>
         </dependency>
@@ -86,6 +93,7 @@
             <artifactId>org.apache.aries.blueprint.core</artifactId>
             <scope>provided</scope>
         </dependency>
+
         <dependency>
             <groupId>org.apache.directory.server</groupId>
             <artifactId>apacheds-core-integ</artifactId>
@@ -126,10 +134,14 @@
                             javax.net,
                             org.apache.karaf.jaas.config,
                             org.apache.aries.blueprint.ext,
+                            !net.sf.ehcache*,
+                            !net.spy.memcached*,
                             *
                         </Import-Package>
                         <Private-Package>
-                            org.apache.felix.utils*
+                            org.apache.felix.utils*,
+                            org.apache.http*,
+                            org.apache.commons.codec*
                         </Private-Package>
                     </instructions>
                 </configuration>

http://git-wip-us.apache.org/repos/asf/karaf/blob/855d1983/jaas/modules/src/main/java/org/apache/karaf/jaas/modules/syncope/SyncopeBackingEngine.java
----------------------------------------------------------------------
diff --git 
a/jaas/modules/src/main/java/org/apache/karaf/jaas/modules/syncope/SyncopeBackingEngine.java
 
b/jaas/modules/src/main/java/org/apache/karaf/jaas/modules/syncope/SyncopeBackingEngine.java
new file mode 100644
index 0000000..0579914
--- /dev/null
+++ 
b/jaas/modules/src/main/java/org/apache/karaf/jaas/modules/syncope/SyncopeBackingEngine.java
@@ -0,0 +1,175 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.karaf.jaas.modules.syncope;
+
+import org.apache.http.HttpResponse;
+import org.apache.http.auth.AuthScope;
+import org.apache.http.auth.Credentials;
+import org.apache.http.auth.UsernamePasswordCredentials;
+import org.apache.http.client.methods.HttpDelete;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.client.DefaultHttpClient;
+import org.apache.http.util.EntityUtils;
+import org.apache.karaf.jaas.boot.principal.GroupPrincipal;
+import org.apache.karaf.jaas.boot.principal.RolePrincipal;
+import org.apache.karaf.jaas.boot.principal.UserPrincipal;
+import org.apache.karaf.jaas.modules.BackingEngine;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.security.Principal;
+import java.util.ArrayList;
+import java.util.List;
+
+public class SyncopeBackingEngine implements BackingEngine {
+
+    private final Logger logger = 
LoggerFactory.getLogger(SyncopeBackingEngine.class);
+
+    private String address;
+
+    private DefaultHttpClient client;
+
+    public SyncopeBackingEngine(String address, String adminUser, String 
adminPassword) {
+        this.address = address;
+
+        client = new DefaultHttpClient();
+        Credentials creds = new UsernamePasswordCredentials(adminUser, 
adminPassword);
+        client.getCredentialsProvider().setCredentials(AuthScope.ANY, creds);
+    }
+
+    public void addUser(String username, String password) {
+        if (username.startsWith(GROUP_PREFIX)) {
+            throw new IllegalArgumentException("Group prefix " + GROUP_PREFIX 
+ " not permitted with Syncope backend");
+        }
+        HttpPost request = new HttpPost(address + "/users");
+        String userTO = "<?xml version=\"1.0\" encoding=\"UTF-8\" 
standalone=\"yes\"?>" +
+                "<user>" +
+                "<attributes>" +
+                
"<attribute><readonly>false</readonly><schema>fullname</schema><value>" + 
username + "</value></attribute>" +
+                
"<attribute><readonly>false</readonly><schema>surname</schema><value>" + 
username + "</value></attribute>" +
+                
"<attribute><readonly>false</readonly><schema>userId</schema><value>" + 
username + "@karaf.apache.org</value></attribute>" +
+                "</attributes>" +
+                "<password>" + password + "</password>" +
+                "<username>" + username + "</username>" +
+                "</user>";
+        try {
+            StringEntity entity = new StringEntity(userTO);
+            request.setEntity(entity);
+            HttpResponse response = client.execute(request);
+        } catch (Exception e) {
+            logger.error("Can't add user {}", username, e);
+            throw new RuntimeException("Can't add user " + username, e);
+        }
+    }
+
+    public void deleteUser(String username) {
+        if (username.startsWith(GROUP_PREFIX)) {
+            throw new IllegalArgumentException("Group prefix " + GROUP_PREFIX 
+ " not permitted with Syncope backend");
+        }
+        HttpDelete request = new HttpDelete(address + "/users/" + username);
+        try {
+            client.execute(request);
+        } catch (Exception e) {
+            logger.error("Can't delete user {}", username, e);
+            throw new RuntimeException("Can't delete user " + username, e);
+        }
+    }
+
+    public List<UserPrincipal> listUsers() {
+        List<UserPrincipal> users = new ArrayList<UserPrincipal>();
+        HttpGet request = new HttpGet(address + "/users");
+        try {
+            HttpResponse response = client.execute(request);
+            String responseTO = EntityUtils.toString(response.getEntity());
+            if (responseTO != null && !responseTO.isEmpty()) {
+                // extracting the user
+                int index = responseTO.indexOf("<username>");
+                while (index != -1) {
+                    responseTO = responseTO.substring(index + 
"<username>".length());
+                    int end = responseTO.indexOf("</username>");
+                    if (end == -1) {
+                        index = -1;
+                    }
+                    String username = responseTO.substring(0, end);
+                    users.add(new UserPrincipal(username));
+                    responseTO = responseTO.substring(end + 
"</username>".length());
+                    index = responseTO.indexOf("<username>");
+                }
+            }
+        } catch (Exception e) {
+            throw new RuntimeException("Error listing users", e);
+        }
+        return users;
+    }
+
+    public List<RolePrincipal> listRoles(Principal principal) {
+        List<RolePrincipal> roles = new ArrayList<RolePrincipal>();
+        HttpGet request = new HttpGet(address + "/users?username=" + 
principal.getName());
+        try {
+            HttpResponse response  = client.execute(request);
+            String responseTO = EntityUtils.toString(response.getEntity());
+            if (responseTO != null && !responseTO.isEmpty()) {
+                int index = responseTO.indexOf("<roleName>");
+                while (index != 1) {
+                    responseTO = responseTO.substring(index + 
"<roleName>".length());
+                    int end = responseTO.indexOf("</roleName>");
+                    if (end == -1) {
+                        index = -1;
+                        break;
+                    }
+                    String role = responseTO.substring(0, end);
+                    roles.add(new RolePrincipal(role));
+                    responseTO = responseTO.substring(end + 
"</roleName>".length());
+                    index = responseTO.indexOf("<roleName>");
+                }
+            }
+        } catch (Exception e) {
+            throw new RuntimeException("Error listing roles", e);
+        }
+        return roles;
+    }
+
+    public void addRole(String username, String role) {
+        throw new RuntimeException("Roles management should be done on the 
Syncope side");
+    }
+
+    public void deleteRole(String username, String role) {
+        throw new RuntimeException("Roles management should be done on the 
Syncope side");
+    }
+
+    public List<GroupPrincipal> listGroups(UserPrincipal principal) {
+        return new ArrayList<GroupPrincipal>();
+    }
+
+    public void addGroup(String username, String group) {
+        throw new RuntimeException("Group management is not supported by 
Syncope backend");
+    }
+
+    public void deleteGroup(String username, String group) {
+        throw new RuntimeException("Group management is not supported by 
Syncope backend");
+    }
+
+    public void addGroupRole(String group, String role) {
+        throw new RuntimeException("Group management is not supported by 
Syncope backend");
+    }
+
+    public void deleteGroupRole(String group, String role) {
+        throw new RuntimeException("Group management is not supported by 
Syncope backend");
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/855d1983/jaas/modules/src/main/java/org/apache/karaf/jaas/modules/syncope/SyncopeBackingEngineFactory.java
----------------------------------------------------------------------
diff --git 
a/jaas/modules/src/main/java/org/apache/karaf/jaas/modules/syncope/SyncopeBackingEngineFactory.java
 
b/jaas/modules/src/main/java/org/apache/karaf/jaas/modules/syncope/SyncopeBackingEngineFactory.java
new file mode 100644
index 0000000..6a72677
--- /dev/null
+++ 
b/jaas/modules/src/main/java/org/apache/karaf/jaas/modules/syncope/SyncopeBackingEngineFactory.java
@@ -0,0 +1,51 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.karaf.jaas.modules.syncope;
+
+import org.apache.karaf.jaas.modules.BackingEngine;
+import org.apache.karaf.jaas.modules.BackingEngineFactory;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Map;
+
+public class SyncopeBackingEngineFactory implements BackingEngineFactory {
+
+    private static final Logger LOGGER = 
LoggerFactory.getLogger(SyncopeBackingEngineFactory.class);
+
+    public BackingEngine build(Map options) {
+        SyncopeBackingEngine instance = null;
+        String address = (String) options.get(SyncopeLoginModule.ADDRESS);
+        String adminUser = (String) options.get(SyncopeLoginModule.ADMIN_USER);
+        String adminPassword = (String) 
options.get(SyncopeLoginModule.ADMIN_PASSWORD);
+
+        try {
+            instance = new SyncopeBackingEngine(address, adminUser, 
adminPassword);
+        } catch (Exception e) {
+            LOGGER.error("Error creating the Syncope backing engine", e);
+        }
+
+        return instance;
+    }
+
+    /**
+     * Returns the login module class, that this factory can build.
+     */
+    public String getModuleClass() {
+        return SyncopeLoginModule.class.getName();
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/855d1983/jaas/modules/src/main/java/org/apache/karaf/jaas/modules/syncope/SyncopeLoginModule.java
----------------------------------------------------------------------
diff --git 
a/jaas/modules/src/main/java/org/apache/karaf/jaas/modules/syncope/SyncopeLoginModule.java
 
b/jaas/modules/src/main/java/org/apache/karaf/jaas/modules/syncope/SyncopeLoginModule.java
new file mode 100644
index 0000000..47cbd0f
--- /dev/null
+++ 
b/jaas/modules/src/main/java/org/apache/karaf/jaas/modules/syncope/SyncopeLoginModule.java
@@ -0,0 +1,154 @@
+/*
+ *  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.
+ *  under the License.
+ */
+package org.apache.karaf.jaas.modules.syncope;
+
+import org.apache.http.HttpStatus;
+import org.apache.http.auth.AuthScope;
+import org.apache.http.auth.Credentials;
+import org.apache.http.auth.UsernamePasswordCredentials;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.impl.client.DefaultHttpClient;
+import org.apache.http.util.EntityUtils;
+import org.apache.karaf.jaas.boot.principal.RolePrincipal;
+import org.apache.karaf.jaas.boot.principal.UserPrincipal;
+import org.apache.karaf.jaas.modules.AbstractKarafLoginModule;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.security.auth.Subject;
+import javax.security.auth.callback.*;
+import javax.security.auth.login.LoginException;
+import java.io.IOException;
+import java.security.Principal;
+import java.util.*;
+
+/**
+ * Karaf login module which uses Apache Syncope backend.
+ */
+public class SyncopeLoginModule extends AbstractKarafLoginModule {
+
+    private final static Logger LOGGER = 
LoggerFactory.getLogger(SyncopeLoginModule.class);
+
+    public final static String ADDRESS = "address";
+    public final static String ADMIN_USER = "admin.user"; // for the backing 
engine
+    public final static String ADMIN_PASSWORD = "admin.password"; // for the 
backing engine
+
+    private String address;
+
+    public void initialize(Subject subject, CallbackHandler callbackHandler, 
Map<String, ?> sharedState, Map<String, ?> options) {
+        super.initialize(subject, callbackHandler, options);
+        address = (String) options.get(ADDRESS);
+    }
+
+    public boolean login() throws LoginException {
+        Callback[] callbacks = new Callback[2];
+        callbacks[0] = new NameCallback("Username: ");
+        callbacks[1] = new PasswordCallback("Password: ", false);
+
+        try {
+            callbackHandler.handle(callbacks);
+        } catch (IOException ioException) {
+            throw new LoginException(ioException.getMessage());
+        } catch (UnsupportedCallbackException unsupportedCallbackException) {
+            throw new LoginException(unsupportedCallbackException.getMessage() 
+ " not available to obtain information from user.");
+        }
+
+        user = ((NameCallback) callbacks[0]).getName();
+
+        char[] tmpPassword = ((PasswordCallback) callbacks[1]).getPassword();
+        if (tmpPassword == null) {
+            tmpPassword = new char[0];
+        }
+        String password = new String(tmpPassword);
+        principals = new HashSet<Principal>();
+
+        // authenticate the user on Syncope
+        LOGGER.debug("Authenticate user {} on Syncope located {}", user, 
address);
+        DefaultHttpClient client = new DefaultHttpClient();
+        Credentials creds = new UsernamePasswordCredentials(user, password);
+        client.getCredentialsProvider().setCredentials(AuthScope.ANY, creds);
+        HttpGet get = new HttpGet(address + "/users/self");
+        List<String> roles = new ArrayList<String>();
+        try {
+            CloseableHttpResponse response = client.execute(get);
+            LOGGER.debug("Syncope HTTP response status code: {}", 
response.getStatusLine().getStatusCode());
+            if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
+                LOGGER.warn("User {} not authenticated", user);
+                return false;
+            }
+            LOGGER.debug("User {} authenticated", user);
+            LOGGER.debug("Populating principals with user");
+            principals.add(new UserPrincipal(user));
+            LOGGER.debug("Retrieving user {} roles", user);
+            roles = 
extractingRoles(EntityUtils.toString(response.getEntity()));
+        } catch (Exception e) {
+            LOGGER.error("User {} authentication failed", user, e);
+            throw new LoginException("User " + user + " authentication failed: 
" + e.getMessage());
+        }
+
+        LOGGER.debug("Populating principals with roles");
+        for (String role : roles) {
+            principals.add(new RolePrincipal(role));
+        }
+
+        return true;
+    }
+
+    /**
+     * Extract the user roles from the Syncope entity response.
+     *
+     * @param response the HTTP response from Syncope.
+     * @return the list of user roles.
+     * @throws Exception in case of extraction failure.
+     */
+    protected List<String> extractingRoles(String response) throws Exception {
+        List<String> roles = new ArrayList<String>();
+        if (response != null && !response.isEmpty()) {
+            // extract the <memberships> element
+            int index = response.indexOf("<memberships>");
+            response = response.substring(index + "<memberships>".length());
+            index = response.indexOf("</memberships>");
+            response = response.substring(0, index);
+
+            // looking for the roleName elements
+            index = response.indexOf("<roleName>");
+            while (index != -1) {
+                response = response.substring(index + "<roleName>".length());
+                int end = response.indexOf("</roleName>");
+                if (end == -1) {
+                    index = -1;
+                }
+                String role = response.substring(0, end);
+                roles.add(role);
+                response = response.substring(end + "</roleName>".length());
+                index = response.indexOf("<roleName>");
+            }
+
+        }
+        return roles;
+    }
+
+    public boolean abort() {
+        return true;
+    }
+
+    public boolean logout() throws LoginException {
+        subject.getPrincipals().removeAll(principals);
+        principals.clear();
+        return true;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/855d1983/jaas/modules/src/test/java/org/apache/karaf/jaas/modules/syncope/SyncopeLoginModuleTest.java
----------------------------------------------------------------------
diff --git 
a/jaas/modules/src/test/java/org/apache/karaf/jaas/modules/syncope/SyncopeLoginModuleTest.java
 
b/jaas/modules/src/test/java/org/apache/karaf/jaas/modules/syncope/SyncopeLoginModuleTest.java
new file mode 100644
index 0000000..eca0818
--- /dev/null
+++ 
b/jaas/modules/src/test/java/org/apache/karaf/jaas/modules/syncope/SyncopeLoginModuleTest.java
@@ -0,0 +1,105 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.karaf.jaas.modules.syncope;
+
+import org.junit.Test;
+import org.junit.Assert;
+
+import java.util.List;
+
+public class SyncopeLoginModuleTest {
+
+    @Test
+    public void testRolesExtraction() throws Exception {
+        String syncopeResponse = "<?xml version=\"1.0\" encoding=\"UTF-8\" 
standalone=\"yes\"?>\n" +
+                "<user>\n" +
+                "    <attributes>\n" +
+                "        <attribute>\n" +
+                "            <readonly>false</readonly>\n" +
+                "            <schema>cool</schema>\n" +
+                "            <value>false</value>\n" +
+                "        </attribute>\n" +
+                "        <attribute>\n" +
+                "            <readonly>false</readonly>\n" +
+                "            <schema>email</schema>\n" +
+                "            <value>[email protected]</value>\n" +
+                "        </attribute>\n" +
+                "        <attribute>\n" +
+                "            <readonly>false</readonly>\n" +
+                "            <schema>fullname</schema>\n" +
+                "            <value>karaf</value>\n" +
+                "        </attribute>\n" +
+                "        <attribute>\n" +
+                "            <readonly>false</readonly>\n" +
+                "            <schema>gender</schema>\n" +
+                "            <value>M</value>\n" +
+                "        </attribute>\n" +
+                "        <attribute>\n" +
+                "            <readonly>false</readonly>\n" +
+                "            <schema>surname</schema>\n" +
+                "            <value>karaf</value>\n" +
+                "        </attribute>\n" +
+                "        <attribute>\n" +
+                "            <readonly>false</readonly>\n" +
+                "            <schema>userId</schema>\n" +
+                "            <value>[email protected]</value>\n" +
+                "        </attribute>\n" +
+                "    </attributes>\n" +
+                "    <derivedAttributes/>\n" +
+                "    <id>100</id>\n" +
+                "    <propagationStatuses/>\n" +
+                "    <resources/>\n" +
+                "    <virtualAttributes/>\n" +
+                "    
<creationDate>2014-08-12T18:37:09.202+02:00</creationDate>\n" +
+                "    <failedLogins>0</failedLogins>\n" +
+                "    
<lastLoginDate>2014-08-13T09:38:02.204+02:00</lastLoginDate>\n" +
+                "    <memberships>\n" +
+                "        <membership>\n" +
+                "            <attributes/>\n" +
+                "            <derivedAttributes/>\n" +
+                "            <id>100</id>\n" +
+                "            <propagationStatuses/>\n" +
+                "            <resources/>\n" +
+                "            <virtualAttributes/>\n" +
+                "            <resources/>\n" +
+                "            <roleId>100</roleId>\n" +
+                "            <roleName>admin</roleName>\n" +
+                "        </membership>\n" +
+                "        <membership>\n" +
+                "            <attributes/>\n" +
+                "            <derivedAttributes/>\n" +
+                "            <id>101</id>\n" +
+                "            <propagationStatuses/>\n" +
+                "            <resources/>\n" +
+                "            <virtualAttributes/>\n" +
+                "            <resources/>\n" +
+                "            <roleId>101</roleId>\n" +
+                "            <roleName>another</roleName>\n" +
+                "        </membership>\n" +
+                "    </memberships>\n" +
+                "    
<password>36460D3A3C1E27C0DB2AF23344475EE712DD3C9D</password>\n" +
+                "    <status>active</status>\n" +
+                "    <username>karaf</username>\n" +
+                "</user>\n";
+        SyncopeLoginModule syncopeLoginModule = new SyncopeLoginModule();
+        List<String> roles = 
syncopeLoginModule.extractingRoles(syncopeResponse);
+        Assert.assertEquals(2, roles.size());
+        Assert.assertEquals("admin", roles.get(0));
+        Assert.assertEquals("another", roles.get(1));
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/karaf/blob/855d1983/manual/src/main/webapp/developers-guide/security-framework.conf
----------------------------------------------------------------------
diff --git a/manual/src/main/webapp/developers-guide/security-framework.conf 
b/manual/src/main/webapp/developers-guide/security-framework.conf
index 09b1f5c..57352db 100644
--- a/manual/src/main/webapp/developers-guide/security-framework.conf
+++ b/manual/src/main/webapp/developers-guide/security-framework.conf
@@ -270,6 +270,55 @@ The LDAPLoginModule supports the following patterns that 
you can use in the filt
 * {{%fqdn}} is replaced by the user full qualified DN ({{userDN,userBaseDN}}).
 * {{%nsdn}} is replaced by the userDNNamespace (interesting especially for 
ActiveDirectory).
 
+h3. SyncopeLoginModule
+
+The Syncope login module uses the Syncope REST API to authenticate users and 
retrieve the roles.
+
+The Syncope login module just requires one parameter:
+
+|| Name                      || Description ||
+| {{address}}                | Location of the Syncope REST API |
+| {{admin.user}}             | Admin username to administrate Syncope (only 
required by the backend engine) |
+| {{admin.password}}         | Admin password to administrate Syncope (only 
required by the backend engine) |
+
+The following snippet shows how to use Syncope with the karaf realm:
+
+{code}
+<jaas:config name="karaf" rank="2">
+  <jaas:module 
className="org.apache.karaf.jaas.modules.syncope.SyncopeLoginModule" 
flags="required">
+    address=http://localhost:9080/syncope/cxf
+    admin.user=admin
+    admin.password=password
+  </jaas:module>
+</jaas:config>
+{code}
+
+SyncopeLoginModule comes with a backend engine allowing to manipulate users 
and roles. You have to register the
+SyncopeBackendEngineFactory service.
+For instance, the following blueprint descriptor enables the 
SyncopeLoginModule and the backend engine factory:
+
+{code}
+<?xml version="1.0" encoding="UTF-8"?>
+<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0";
+           xmlns:jaas="http://karaf.apache.org/xmlns/jaas/v1.1.0";
+           
xmlns:ext="http://aries.apache.org/blueprint/xmlns/blueprint-ext/v1.0.0";>
+
+    <jaas:config name="karaf" rank="2">
+        <jaas:module 
className="org.apache.karaf.jaas.modules.syncope.SyncopeLoginModule"
+                     flags="required">
+           address=http://localhost:9080/syncope/cxf
+           admin.user=admin
+           admin.password=password
+        </jaas:module>
+    </jaas:config>
+
+    <service interface="org.apache.karaf.jaas.modules.BackingEngineFactory">
+        <bean 
class="org.apache.karaf.jaas.modules.syncope.SyncopeBackingEngineFactory"/>
+    </service>
+
+</blueprint>
+{code}
+
 h2. Encryption service
 
 The 
[EncryptionService|http://svn.apache.org/repos/asf/karaf/trunk/jaas/modules/src/main/java/org/apache/karaf/jaas/modules/EncryptionService.java]
 is a service registered in the OSGi registry providing means to encrypt and 
check encrypted passwords.  This service acts as a factory for 
[Encryption|http://svn.apache.org/repos/asf/karaf/trunk/jaas/modules/src/main/java/org/apache/karaf/jaas/modules/Encryption.java]
 objects actually performing the encryption.

Reply via email to