James Moger
2014-07-03 efdb2b3d0c6f03a9aac9e65892cbc8ff755f246f
commit | author | age
04a985 1 /*
JM 2  * Copyright 2012 gitblit.com.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.gitblit.auth;
17
18 import java.io.IOException;
19 import java.io.InputStreamReader;
20 import java.net.HttpURLConnection;
21
efdb2b 22 import org.apache.commons.io.IOUtils;
04a985 23
JM 24 import com.gitblit.Constants;
25 import com.gitblit.Constants.AccountType;
26 import com.gitblit.Keys;
27 import com.gitblit.auth.AuthenticationProvider.UsernamePasswordAuthenticationProvider;
28 import com.gitblit.models.UserModel;
29 import com.gitblit.utils.ConnectionUtils;
30 import com.gitblit.utils.StringUtils;
31 import com.google.gson.Gson;
32
33 /**
34  * Implementation of Redmine authentication.<br>
35  * you can login to gitblit with Redmine user id and api key.
36  */
37 public class RedmineAuthProvider extends UsernamePasswordAuthenticationProvider {
38
39     private String testingJson;
40
41     private class RedmineCurrent {
42         private class RedmineUser {
43             public String login;
44             public String firstname;
45             public String lastname;
46             public String mail;
47         }
48
49         public RedmineUser user;
50     }
51
52     public RedmineAuthProvider() {
53         super("redmine");
54     }
55
56     @Override
57     public void setup() {
58     }
59
60     @Override
61     public boolean supportsCredentialChanges() {
62         return false;
63     }
64
65     @Override
66     public boolean supportsDisplayNameChanges() {
67         return false;
68     }
69
70     @Override
71     public boolean supportsEmailAddressChanges() {
72         return false;
73     }
74
75     @Override
76     public boolean supportsTeamMembershipChanges() {
77         return false;
78     }
79
80      @Override
81     public AccountType getAccountType() {
82         return AccountType.REDMINE;
83     }
84
85     @Override
86     public UserModel authenticate(String username, char[] password) {
87         String jsonString = null;
88         try {
89             // first attempt by username/password
90             jsonString = getCurrentUserAsJson(username, password);
91         } catch (Exception e1) {
92             logger.warn("Failed to authenticate via username/password against Redmine");
93             try {
94                 // second attempt is by apikey
95                 jsonString = getCurrentUserAsJson(null, password);
96                 username = null;
97             } catch (Exception e2) {
98                 logger.error("Failed to authenticate via apikey against Redmine", e2);
99                 return null;
100             }
101         }
102
103         if (StringUtils.isEmpty(jsonString)) {
104             logger.error("Received empty authentication response from Redmine");
105             return null;
106         }
107
108         RedmineCurrent current = null;
109         try {
110             current = new Gson().fromJson(jsonString, RedmineCurrent.class);
111         } catch (Exception e) {
112             logger.error("Failed to deserialize Redmine json response: " + jsonString, e);
113             return null;
114         }
115
116         if (StringUtils.isEmpty(username)) {
117             // if the username has been reset because of apikey authentication
118             // then use the email address of the user. this is the original
119             // behavior as contributed by github/mallowlabs
120             username = current.user.mail;
121         }
122
123         UserModel user = userManager.getUserModel(username);
c1b0e4 124         if (user == null) {
JM 125             // create user object for new authenticated user
04a985 126             user = new UserModel(username.toLowerCase());
c1b0e4 127         }
04a985 128
JM 129         // create a user cookie
c1b0e4 130         setCookie(user, password);
04a985 131
JM 132         // update user attributes from Redmine
133         user.accountType = getAccountType();
134         user.displayName = current.user.firstname + " " + current.user.lastname;
135         user.emailAddress = current.user.mail;
136         user.password = Constants.EXTERNAL_ACCOUNT;
137
138         // TODO consider Redmine group mapping for team membership
139         // http://www.redmine.org/projects/redmine/wiki/Rest_Users
140
141         // push the changes to the backing user service
142         updateUser(user);
143
144         return user;
145     }
146
147     private String getCurrentUserAsJson(String username, char [] password) throws IOException {
148         if (testingJson != null) { // for testing
149             return testingJson;
150         }
151
152         String url = this.settings.getString(Keys.realm.redmine.url, "");
153         if (!url.endsWith("/")) {
154             url = url.concat("/");
155         }
2445d4 156         String apiUrl = url + "users/current.json";
M 157         
04a985 158         HttpURLConnection http;
JM 159         if (username == null) {
160             // apikey authentication
161             String apiKey = String.valueOf(password);
162             http = (HttpURLConnection) ConnectionUtils.openConnection(apiUrl, null, null);
2445d4 163             http.addRequestProperty("X-Redmine-API-Key", apiKey);
04a985 164         } else {
JM 165             // username/password BASIC authentication
166             http = (HttpURLConnection) ConnectionUtils.openConnection(apiUrl, username, password);
167         }
168         http.setRequestMethod("GET");
169         http.connect();
170         InputStreamReader reader = new InputStreamReader(http.getInputStream());
171         return IOUtils.toString(reader);
172     }
173
174     /**
175      * set json response. do NOT invoke from production code.
176      * @param json json
177      */
178     public void setTestingCurrentUserAsJson(String json) {
179         this.testingJson = json;
180     }
181 }