James Moger
2015-11-22 ed552ba47c02779c270ffd62841d6d1048dade70
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;
6e3481 26 import com.gitblit.Constants.Role;
04a985 27 import com.gitblit.Keys;
JM 28 import com.gitblit.auth.AuthenticationProvider.UsernamePasswordAuthenticationProvider;
6e3481 29 import com.gitblit.models.TeamModel;
04a985 30 import com.gitblit.models.UserModel;
JM 31 import com.gitblit.utils.ConnectionUtils;
32 import com.gitblit.utils.StringUtils;
33 import com.google.gson.Gson;
34
35 /**
36  * Implementation of Redmine authentication.<br>
37  * you can login to gitblit with Redmine user id and api key.
38  */
39 public class RedmineAuthProvider extends UsernamePasswordAuthenticationProvider {
40
41     private String testingJson;
42
43     private class RedmineCurrent {
44         private class RedmineUser {
45             public String login;
46             public String firstname;
47             public String lastname;
48             public String mail;
49         }
50
51         public RedmineUser user;
52     }
53
54     public RedmineAuthProvider() {
55         super("redmine");
56     }
57
58     @Override
59     public void setup() {
60     }
61
62     @Override
63     public boolean supportsCredentialChanges() {
64         return false;
65     }
66
67     @Override
68     public boolean supportsDisplayNameChanges() {
69         return false;
70     }
71
72     @Override
73     public boolean supportsEmailAddressChanges() {
74         return false;
75     }
76
77     @Override
78     public boolean supportsTeamMembershipChanges() {
79         return false;
80     }
6e3481 81
JM 82     @Override
83     public boolean supportsRoleChanges(UserModel user, Role role) {
84         return true;
85     }
86
87     @Override
88     public boolean supportsRoleChanges(TeamModel team, Role role) {
89         return true;
90     }
04a985 91
JM 92      @Override
93     public AccountType getAccountType() {
94         return AccountType.REDMINE;
95     }
96
97     @Override
98     public UserModel authenticate(String username, char[] password) {
99         String jsonString = null;
100         try {
101             // first attempt by username/password
102             jsonString = getCurrentUserAsJson(username, password);
103         } catch (Exception e1) {
104             logger.warn("Failed to authenticate via username/password against Redmine");
105             try {
106                 // second attempt is by apikey
107                 jsonString = getCurrentUserAsJson(null, password);
108                 username = null;
109             } catch (Exception e2) {
110                 logger.error("Failed to authenticate via apikey against Redmine", e2);
111                 return null;
112             }
113         }
114
115         if (StringUtils.isEmpty(jsonString)) {
116             logger.error("Received empty authentication response from Redmine");
117             return null;
118         }
119
120         RedmineCurrent current = null;
121         try {
122             current = new Gson().fromJson(jsonString, RedmineCurrent.class);
123         } catch (Exception e) {
124             logger.error("Failed to deserialize Redmine json response: " + jsonString, e);
125             return null;
126         }
127
128         if (StringUtils.isEmpty(username)) {
129             // if the username has been reset because of apikey authentication
130             // then use the email address of the user. this is the original
131             // behavior as contributed by github/mallowlabs
132             username = current.user.mail;
133         }
134
135         UserModel user = userManager.getUserModel(username);
c1b0e4 136         if (user == null) {
JM 137             // create user object for new authenticated user
04a985 138             user = new UserModel(username.toLowerCase());
c1b0e4 139         }
04a985 140
JM 141         // create a user cookie
c1b0e4 142         setCookie(user, password);
04a985 143
JM 144         // update user attributes from Redmine
145         user.accountType = getAccountType();
146         user.displayName = current.user.firstname + " " + current.user.lastname;
147         user.emailAddress = current.user.mail;
148         user.password = Constants.EXTERNAL_ACCOUNT;
149
150         // TODO consider Redmine group mapping for team membership
151         // http://www.redmine.org/projects/redmine/wiki/Rest_Users
152
153         // push the changes to the backing user service
154         updateUser(user);
155
156         return user;
157     }
158
159     private String getCurrentUserAsJson(String username, char [] password) throws IOException {
160         if (testingJson != null) { // for testing
161             return testingJson;
162         }
163
164         String url = this.settings.getString(Keys.realm.redmine.url, "");
165         if (!url.endsWith("/")) {
166             url = url.concat("/");
167         }
2445d4 168         String apiUrl = url + "users/current.json";
6e3481 169
04a985 170         HttpURLConnection http;
JM 171         if (username == null) {
172             // apikey authentication
173             String apiKey = String.valueOf(password);
174             http = (HttpURLConnection) ConnectionUtils.openConnection(apiUrl, null, null);
2445d4 175             http.addRequestProperty("X-Redmine-API-Key", apiKey);
04a985 176         } else {
JM 177             // username/password BASIC authentication
178             http = (HttpURLConnection) ConnectionUtils.openConnection(apiUrl, username, password);
179         }
180         http.setRequestMethod("GET");
181         http.connect();
182         InputStreamReader reader = new InputStreamReader(http.getInputStream());
183         return IOUtils.toString(reader);
184     }
185
186     /**
187      * set json response. do NOT invoke from production code.
188      * @param json json
189      */
190     public void setTestingCurrentUserAsJson(String json) {
191         this.testingJson = json;
192     }
193 }