Skip to content

Commit 9a05d20

Browse files
committed
Support impersonation via Java client API
SVN r65329 |2020-04-07 04:57:23 +0000
1 parent 26e2e68 commit 9a05d20

File tree

6 files changed

+190
-11
lines changed

6 files changed

+190
-11
lines changed

.gitattributes

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,10 +135,14 @@ java/src/org/labkey/remoteapi/security/GetGroupPermsResponse.java -text
135135
java/src/org/labkey/remoteapi/security/GetUsersCommand.java -text
136136
java/src/org/labkey/remoteapi/security/GetUsersResponse.java -text
137137
java/src/org/labkey/remoteapi/security/GroupMembersCommand.java -text
138+
java/src/org/labkey/remoteapi/security/ImpersonateUserCommand.java -text
138139
java/src/org/labkey/remoteapi/security/LogoutCommand.java -text
139140
java/src/org/labkey/remoteapi/security/RemoveGroupMembersCommand.java -text
140141
java/src/org/labkey/remoteapi/security/RenameGroupCommand.java -text
141142
java/src/org/labkey/remoteapi/security/RenameGroupResponse.java -text
143+
java/src/org/labkey/remoteapi/security/StopImpersonatingCommand.java -text
144+
java/src/org/labkey/remoteapi/security/WhoAmICommand.java -text
145+
java/src/org/labkey/remoteapi/security/WhoAmIResponse.java -text
142146
java/src/org/labkey/remoteapi/study/CheckForStudyReloadCommand.java -text
143147
java/src/org/labkey/remoteapi/study/ParticipantGroup.java -text
144148
java/src/org/labkey/remoteapi/study/UpdateParticipantGroupCommand.java -text

java/src/org/labkey/remoteapi/Connection.java

Lines changed: 78 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,10 @@
3535
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
3636
import org.apache.http.impl.cookie.BasicClientCookie;
3737
import org.labkey.remoteapi.security.EnsureLoginCommand;
38+
import org.labkey.remoteapi.security.ImpersonateUserCommand;
3839
import org.labkey.remoteapi.security.LogoutCommand;
40+
import org.labkey.remoteapi.security.StopImpersonatingCommand;
41+
import org.labkey.remoteapi.security.WhoAmICommand;
3942

4043
import java.io.IOException;
4144
import java.net.URI;
@@ -44,6 +47,7 @@
4447
import java.security.KeyStoreException;
4548
import java.security.NoSuchAlgorithmException;
4649
import java.util.Date;
50+
import java.util.Objects;
4751

4852
/**
4953
* Represents connection information for a particular LabKey Server.
@@ -108,6 +112,11 @@ public class Connection
108112
private int _timeout = DEFAULT_TIMEOUT;
109113
private String _proxyHost;
110114
private Integer _proxyPort;
115+
private String _csrf;
116+
117+
// The user email when impersonating a user
118+
private String _impersonateUser;
119+
private String _impersonatePath;
111120

112121
/**
113122
* Constructs a new Connection object given a base URL and a credentials provider.
@@ -221,24 +230,21 @@ protected HttpClientBuilder clientBuilder()
221230

222231

223232

224-
private String csrf = null;
225-
226-
protected void beforeExecute(HttpRequest request)
233+
protected void beforeExecute(HttpRequest request) throws IOException, CommandException
227234
{
228-
if (null == csrf && request instanceof HttpPost)
235+
if (null == _csrf && request instanceof HttpPost)
229236
{
230-
// need to preemptively login
231-
// we're not really using the login form, just getting a JSESSIONID
237+
// make a request to get a JSESSIONID
232238
try
233239
{
234-
new Command("login", "login").execute(this, "/");
240+
new WhoAmICommand().execute(this, "/");
235241
}
236242
catch (Exception ignored)
237243
{
238244
}
239245
}
240-
if (null != csrf)
241-
request.setHeader("X-LABKEY-CSRF", csrf);
246+
if (null != _csrf)
247+
request.setHeader("X-LABKEY-CSRF", _csrf);
242248
}
243249

244250

@@ -248,7 +254,7 @@ protected void afterExecute()
248254
for (Cookie c : _httpClientContext.getCookieStore().getCookies())
249255
{
250256
if ("JSESSIONID".equals(c.getName()))
251-
csrf = c.getValue();
257+
_csrf = c.getValue();
252258
}
253259
}
254260

@@ -278,7 +284,68 @@ public CloseableHttpClient logout() throws IOException, CommandException
278284
return getHttpClient();
279285
}
280286

281-
CloseableHttpResponse executeRequest(HttpUriRequest request, Integer timeout) throws IOException, URISyntaxException, AuthenticationException
287+
/**
288+
* For site-admins only, start impersonating a user.
289+
*
290+
* Admins may impersonate other users to perform actions on their behalf.
291+
* Site admins may impersonate any user in any project. Project admins must
292+
* provide a <code>projectPath</code> and may only impersonate within the
293+
* project in which they have admin permission.
294+
*
295+
* @param email The user to impersonate
296+
* @see Connection#stopImpersonate()
297+
*/
298+
public Connection impersonate(/*@NotNull*/ String email) throws IOException, CommandException
299+
{
300+
return impersonate(email, null);
301+
}
302+
303+
/**
304+
* For site-admins or project-admins only, start impersonating a user.
305+
*
306+
* Admins may impersonate other users to perform actions on their behalf.
307+
* Site admins may impersonate any user in any project. Project admins must
308+
* provide a <code>projectPath</code> and may only impersonate within the
309+
* project in which they have admin permission.
310+
*
311+
* @param email The user to impersonate
312+
* @param projectPath The project path within which the user will be impersonated.
313+
* @see Connection#stopImpersonate()
314+
*/
315+
public Connection impersonate(/*@NotNull*/ String email, /*@Nullable*/ String projectPath) throws IOException, CommandException
316+
{
317+
Objects.requireNonNull(email, "email");
318+
319+
CommandResponse resp = new ImpersonateUserCommand(email).execute(this, projectPath);
320+
if (resp.getStatusCode() != 200)
321+
throw new CommandException("Failed to impersonate user");
322+
323+
_impersonateUser = email;
324+
_impersonatePath = projectPath;
325+
return this;
326+
}
327+
328+
/**
329+
* Stop impersonating a user.
330+
*/
331+
public Connection stopImpersonate() throws IOException, CommandException
332+
{
333+
if (_impersonateUser != null)
334+
{
335+
CommandResponse resp = new StopImpersonatingCommand().execute(this, _impersonatePath);
336+
337+
// on success, a 302 redirect response is returned
338+
if (resp.getStatusCode() != 302)
339+
throw new CommandException("Failed to stop impersonating");
340+
341+
_impersonateUser = null;
342+
_impersonatePath = null;
343+
}
344+
345+
return this;
346+
}
347+
348+
CloseableHttpResponse executeRequest(HttpUriRequest request, Integer timeout) throws IOException, URISyntaxException, AuthenticationException, CommandException
282349
{
283350
// Delegate authentication setup to CredentialsProvider
284351
_credentialsProvider.configureRequest(getBaseUrl(), request, _httpClientContext);
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package org.labkey.remoteapi.security;
2+
3+
import org.json.simple.JSONObject;
4+
import org.labkey.remoteapi.Command;
5+
import org.labkey.remoteapi.CommandException;
6+
import org.labkey.remoteapi.CommandResponse;
7+
import org.labkey.remoteapi.Connection;
8+
import org.labkey.remoteapi.PostCommand;
9+
10+
import java.io.IOException;
11+
12+
/**
13+
* For site-admins or project-admins only, start impersonating a user.
14+
*
15+
* Admins may impersonate other users to perform actions on their behalf.
16+
* Site users may impersonate any user in any project. Project admins must
17+
* execute this command in a project in which they have admin permission
18+
* and may impersonate any user that has access to the project.
19+
*
20+
* To finish an impersonation session use either {@link LogoutCommand} to
21+
* log the original user out or use {@link StopImpersonatingCommand} to stop
22+
* impersonating while keeping the original user logged in.
23+
*/
24+
public class ImpersonateUserCommand extends PostCommand<CommandResponse>
25+
{
26+
public ImpersonateUserCommand(int userId)
27+
{
28+
super("user", "impersonateUser.api");
29+
getParameters().put("userId", userId);
30+
}
31+
32+
public ImpersonateUserCommand(String email)
33+
{
34+
super("user", "impersonateUser.api");
35+
getParameters().put("email", email);
36+
}
37+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package org.labkey.remoteapi.security;
2+
3+
import org.labkey.remoteapi.CommandResponse;
4+
import org.labkey.remoteapi.PostCommand;
5+
6+
/**
7+
* Stop impersonating a user.
8+
*/
9+
public class StopImpersonatingCommand extends PostCommand<CommandResponse>
10+
{
11+
public StopImpersonatingCommand()
12+
{
13+
super("login", "stopImpersonating.api");
14+
}
15+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package org.labkey.remoteapi.security;
2+
3+
import org.json.simple.JSONObject;
4+
import org.labkey.remoteapi.Command;
5+
6+
public class WhoAmICommand extends Command<WhoAmIResponse>
7+
{
8+
public WhoAmICommand()
9+
{
10+
super("login", "whoami.api");
11+
}
12+
13+
@Override
14+
protected WhoAmIResponse createResponse(String text, int status, String contentType, JSONObject json)
15+
{
16+
return new WhoAmIResponse(text, status, contentType, json, this);
17+
}
18+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package org.labkey.remoteapi.security;
2+
3+
import org.json.simple.JSONObject;
4+
import org.labkey.remoteapi.Command;
5+
import org.labkey.remoteapi.CommandResponse;
6+
7+
public class WhoAmIResponse extends CommandResponse
8+
{
9+
public WhoAmIResponse(String text, int statusCode, String contentType, JSONObject json, WhoAmICommand cmd)
10+
{
11+
super(text, statusCode, contentType, json, cmd);
12+
}
13+
14+
public Number getUserId()
15+
{
16+
return getProperty("id");
17+
}
18+
19+
public String getEmail()
20+
{
21+
return getProperty("email");
22+
}
23+
24+
public String getDisplayName()
25+
{
26+
return getProperty("displayName");
27+
}
28+
29+
public boolean isImpersonated()
30+
{
31+
return (Boolean)getProperty("impersonated");
32+
}
33+
34+
public String getCSRF()
35+
{
36+
return getProperty("CSRF");
37+
}
38+
}

0 commit comments

Comments
 (0)