From 8d6217d27bcd171d1d8276360e261bcaf8df9272 Mon Sep 17 00:00:00 2001
From: James Moger <james.moger@gitblit.com>
Date: Mon, 10 Sep 2012 07:51:55 -0400
Subject: [PATCH] Merge pull request #34 from mallowlabs/redmine

---
 tests/com/gitblit/tests/RedmineUserServiceTest.java |   52 +++++++++++++
 distrib/gitblit.properties                          |   11 ++
 src/com/gitblit/RedmineUserService.java             |  130 ++++++++++++++++++++++++++++++++
 3 files changed, 193 insertions(+), 0 deletions(-)

diff --git a/distrib/gitblit.properties b/distrib/gitblit.properties
index c7f0ae3..fe7692b 100644
--- a/distrib/gitblit.properties
+++ b/distrib/gitblit.properties
@@ -301,6 +301,7 @@
 #
 # Alternative user services:
 #    com.gitblit.LdapUserService
+#    com.gitblit.RedmineUserService
 #
 # Any custom user service implementation must have a public default constructor.
 #
@@ -941,6 +942,16 @@
 # SINCE 1.0.0
 realm.ldap.email = email
 
+# The RedmineUserService must be backed by another user service for standard user
+# and team management.
+# default: users.conf
+#
+# RESTART REQUIRED
+realm.redmine.backingUserService = users.conf
+
+# URL of the Redmine.
+realm.redmine.url = http://example.com/redmine
+
 #
 # Server Settings
 #
diff --git a/src/com/gitblit/RedmineUserService.java b/src/com/gitblit/RedmineUserService.java
new file mode 100644
index 0000000..b890f21
--- /dev/null
+++ b/src/com/gitblit/RedmineUserService.java
@@ -0,0 +1,130 @@
+package com.gitblit;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.HttpURLConnection;
+
+import org.apache.wicket.util.io.IOUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.gitblit.models.UserModel;
+import com.gitblit.utils.ConnectionUtils;
+import com.gitblit.utils.StringUtils;
+import com.google.gson.Gson;
+
+/**
+ * Implementation of an Redmine user service.<br>
+ * you can login to gitblit with Redmine user id and api key.
+ */
+public class RedmineUserService extends GitblitUserService {
+
+    private final Logger logger = LoggerFactory.getLogger(RedmineUserService.class);
+
+    private IStoredSettings settings;
+
+    private String testingJson;
+
+    private class RedmineCurrent {
+        private class RedmineUser {
+            public String login;
+            public String firstname;
+            public String lastname;
+            public String mail;
+        }
+
+        public RedmineUser user;
+    }
+
+    public RedmineUserService() {
+        super();
+    }
+
+    @Override
+    public void setup(IStoredSettings settings) {
+        this.settings = settings;
+
+        String file = settings.getString(Keys.realm.redmine.backingUserService, "users.conf");
+        File realmFile = GitBlit.getFileOrFolder(file);
+
+        serviceImpl = createUserService(realmFile);
+        logger.info("Redmine User Service backed by " + serviceImpl.toString());
+    }
+
+    @Override
+    public boolean supportsCredentialChanges() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsDisplayNameChanges() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsEmailAddressChanges() {
+        return false;
+    }
+
+    @Override
+    public boolean supportsTeamMembershipChanges() {
+        return false;
+    }
+
+    @Override
+    public UserModel authenticate(String username, char[] password) {
+        String urlText = this.settings.getString(Keys.realm.redmine.url, "");
+        if (!urlText.endsWith("/")) {
+            urlText.concat("/");
+        }
+        String apiKey = String.valueOf(password);
+
+        try {
+            String jsonString = getCurrentUserAsJson(urlText, apiKey);
+
+            RedmineCurrent current = new Gson().fromJson(jsonString, RedmineCurrent.class);
+            String login = current.user.login;
+
+            boolean canAdmin = true;
+            // non admin user can not get login name
+            if (StringUtils.isEmpty(login)) {
+                canAdmin = false;
+                login = current.user.mail;
+            }
+
+            UserModel userModel = new UserModel(login);
+            userModel.canAdmin = canAdmin;
+            userModel.displayName = current.user.firstname + " " + current.user.lastname;
+            userModel.emailAddress = current.user.mail;
+            userModel.cookie = StringUtils.getSHA1(userModel.username + new String(password));
+
+            return userModel;
+        } catch (IOException e) {
+            logger.error("authenticate", e);
+        }
+        return null;
+    }
+
+    private String getCurrentUserAsJson(String url, String apiKey) throws IOException {
+        if (testingJson != null) { // for testing
+            return testingJson;
+        }
+
+        String apiUrl = url + "users/current.json?key=" + apiKey;
+        HttpURLConnection http = (HttpURLConnection) ConnectionUtils.openConnection(apiUrl, null, null);
+        http.setRequestMethod("GET");
+        http.connect();
+        InputStreamReader reader = new InputStreamReader(http.getInputStream());
+        return IOUtils.toString(reader);
+    }
+
+    /**
+     * set json response. do NOT invoke from production code.
+     * @param json json
+     */
+    public void setTestingCurrentUserAsJson(String json) {
+        this.testingJson = json;
+    }
+
+}
diff --git a/tests/com/gitblit/tests/RedmineUserServiceTest.java b/tests/com/gitblit/tests/RedmineUserServiceTest.java
new file mode 100644
index 0000000..30a8fb2
--- /dev/null
+++ b/tests/com/gitblit/tests/RedmineUserServiceTest.java
@@ -0,0 +1,52 @@
+package com.gitblit.tests;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertThat;
+
+import java.util.HashMap;
+
+import org.junit.Test;
+
+import com.gitblit.RedmineUserService;
+import com.gitblit.models.UserModel;
+import com.gitblit.tests.mock.MemorySettings;
+
+public class RedmineUserServiceTest {
+
+    private static final String JSON = "{\"user\":{\"created_on\":\"2011-03-28T00:41:29Z\",\"lastname\":\"foo\","
+        + "\"last_login_on\":\"2012-09-06T23:59:26Z\",\"firstname\":\"baz\","
+        + "\"id\":4,\"login\":\"RedmineUserId\",\"mail\":\"baz@example.com\"}}";
+
+    private static final String NOT_ADMIN_JSON = "{\"user\":{\"lastname\":\"foo\","
+        + "\"last_login_on\":\"2012-09-08T13:59:01Z\",\"created_on\":\"2009-03-17T14:25:50Z\","
+        + "\"mail\":\"baz@example.com\",\"id\":5,\"firstname\":\"baz\"}}";
+
+    @Test
+    public void testAuthenticate() throws Exception {
+        RedmineUserService redmineUserService = new RedmineUserService();
+        redmineUserService.setup(new MemorySettings(new HashMap<String, Object>()));
+        redmineUserService.setTestingCurrentUserAsJson(JSON);
+        UserModel userModel = redmineUserService.authenticate("RedmineUserId", "RedmineAPIKey".toCharArray());
+        assertThat(userModel.getName(), is("RedmineUserId"));
+        assertThat(userModel.getDisplayName(), is("baz foo"));
+        assertThat(userModel.emailAddress, is("baz@example.com"));
+        assertNotNull(userModel.cookie);
+        assertThat(userModel.canAdmin, is(true));
+    }
+
+    @Test
+    public void testAuthenticateNotAdminUser() throws Exception {
+        RedmineUserService redmineUserService = new RedmineUserService();
+        redmineUserService.setup(new MemorySettings(new HashMap<String, Object>()));
+        redmineUserService.setTestingCurrentUserAsJson(NOT_ADMIN_JSON);
+        UserModel userModel = redmineUserService.authenticate("RedmineUserId", "RedmineAPIKey".toCharArray());
+        assertThat(userModel.getName(), is("baz@example.com"));
+        assertThat(userModel.getDisplayName(), is("baz foo"));
+        assertThat(userModel.emailAddress, is("baz@example.com"));
+        assertNotNull(userModel.cookie);
+        assertThat(userModel.canAdmin, is(false));
+    }
+
+}

--
Gitblit v1.9.1