Skip to content

Commit 19c3498

Browse files
committed
Fix #33886: allow to use serverName in access-expression
1 parent 3c5df36 commit 19c3498

12 files changed

Lines changed: 129 additions & 56 deletions

src/main/java/eu/openanalytics/containerproxy/auth/impl/OpenIDAuthenticationBackend.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -277,7 +277,7 @@ public LogoutSuccessHandler getLogoutSuccessHandler() {
277277
return (httpServletRequest, httpServletResponse, authentication) -> {
278278
String resolvedLogoutUrl;
279279
if (authentication != null) {
280-
SpecExpressionContext context = SpecExpressionContext.create(authentication.getPrincipal(), authentication.getCredentials());
280+
SpecExpressionContext context = SpecExpressionContext.create(authentication.getPrincipal(), authentication.getCredentials()).build();
281281
resolvedLogoutUrl = specExpressionResolver.evaluateToString(getLogoutSuccessURL(), context);
282282
} else {
283283
resolvedLogoutUrl = getLogoutSuccessURL();

src/main/java/eu/openanalytics/containerproxy/auth/impl/WebServiceAuthenticationBackend.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ private User createUser(String username, String body) {
153153
try {
154154
jsonResponse = objectMapper.readTree(body);
155155
if (groupsExpression != null) {
156-
SpecExpressionContext context = SpecExpressionContext.create(jsonResponse);
156+
SpecExpressionContext context = SpecExpressionContext.create(jsonResponse).build();
157157
List<String> groups = specExpressionResolver.evaluateToList(List.of(groupsExpression), context);
158158
for (String role: groups) {
159159
String mappedRole = role.toUpperCase().startsWith("ROLE_") ? role : "ROLE_" + role;

src/main/java/eu/openanalytics/containerproxy/backend/dispatcher/proxysharing/ProxySharingScaler.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -400,7 +400,7 @@ private Runnable createDelegateProxyJob(DelegateProxy originalDelegateProxy) {
400400
DelegateProxy delegateProxy = originalDelegateProxy.toBuilder().proxy(proxy).build();
401401
delegateProxyStore.updateDelegateProxy(delegateProxy);
402402

403-
SpecExpressionContext context = SpecExpressionContext.create(proxy, proxySpec);
403+
SpecExpressionContext context = SpecExpressionContext.create(proxy, proxySpec).build();
404404
ProxySpec resolvedSpec = proxySpec.firstResolve(expressionResolver, context);
405405
context = context.copy(resolvedSpec, proxy);
406406
resolvedSpec = resolvedSpec.finalResolve(expressionResolver, context);

src/main/java/eu/openanalytics/containerproxy/backend/strategy/IProxyLogoutStrategy.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,26 @@
2020
*/
2121
package eu.openanalytics.containerproxy.backend.strategy;
2222

23+
import org.springframework.security.core.Authentication;
24+
2325
/**
2426
* Defines a strategy for deciding what to do with a user's proxies when
2527
* the user logs out.
2628
*/
2729
public interface IProxyLogoutStrategy {
2830

31+
/**
32+
* Handles the proxies owned by this user and verifies whether the user can still access them.
33+
* Only handles proxies for which the spec can be accessed by the current session.
34+
* @param authentication authentication object of the user
35+
*/
36+
void onLogout(Authentication authentication);
37+
38+
/**
39+
* Handles the proxies owned by this user and verifies whether the user can still access them.
40+
* Handles all proxies of the user, even if the current session cannot access the spec.
41+
* @param userId id of the user
42+
*/
2943
void onLogout(String userId);
3044

3145
}

src/main/java/eu/openanalytics/containerproxy/backend/strategy/impl/DefaultProxyLogoutStrategy.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import eu.openanalytics.containerproxy.spec.IProxySpecProvider;
2929
import org.springframework.context.annotation.Lazy;
3030
import org.springframework.core.env.Environment;
31+
import org.springframework.security.core.Authentication;
3132
import org.springframework.stereotype.Component;
3233

3334
import javax.annotation.PostConstruct;
@@ -62,6 +63,20 @@ public void init() {
6263
defaultStopProxyOnLogout = environment.getProperty(PROP_DEFAULT_STOP_PROXIES_ON_LOGOUT, Boolean.class, true);
6364
}
6465

66+
@Override
67+
public void onLogout(Authentication authentication) {
68+
for (Proxy proxy : proxyService.getUserProxies(authentication)) {
69+
if (shouldBeStopped(proxy)) {
70+
asyncProxyService.stopProxy(proxy, true);
71+
}
72+
}
73+
}
74+
75+
/**
76+
* Should only be used if authentication is none.
77+
* In this case all proxies can be stopped, since they can't belong to a different domain (#33886).
78+
* @param userId id of the user
79+
*/
6580
@Override
6681
public void onLogout(String userId) {
6782
for (Proxy proxy : proxyService.getUserProxies(userId)) {

src/main/java/eu/openanalytics/containerproxy/service/AccessControlEvaluationService.java

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@
2525
import eu.openanalytics.containerproxy.model.spec.ProxySpec;
2626
import eu.openanalytics.containerproxy.spec.expression.SpecExpressionContext;
2727
import eu.openanalytics.containerproxy.spec.expression.SpecExpressionResolver;
28-
import org.apache.commons.lang3.ArrayUtils;
2928
import org.springframework.context.annotation.Lazy;
3029
import org.springframework.security.authentication.AnonymousAuthenticationToken;
3130
import org.springframework.security.core.Authentication;
@@ -48,10 +47,12 @@ public AccessControlEvaluationService(@Lazy IAuthenticationBackend authBackend,
4847
public boolean checkAccess(Authentication auth, ProxySpec spec, AccessControl accessControl, Object... objects) {
4948
if (auth instanceof AnonymousAuthenticationToken) {
5049
// if anonymous -> only allow access if the backend has no authorization enabled
51-
return !authBackend.hasAuthorization();
50+
if (authBackend.hasAuthorization()) {
51+
return false;
52+
}
5253
}
5354

54-
if (hasAccessControl(accessControl)) {
55+
if (hasNoAccessControl(accessControl)) {
5556
return true;
5657
}
5758

@@ -66,7 +67,7 @@ public boolean checkAccess(Authentication auth, ProxySpec spec, AccessControl ac
6667
return allowedByExpression(auth, spec, accessControl, objects);
6768
}
6869

69-
public boolean hasAccessControl(AccessControl accessControl) {
70+
public boolean hasNoAccessControl(AccessControl accessControl) {
7071
if (accessControl == null) {
7172
return true;
7273
}
@@ -107,14 +108,15 @@ public boolean allowedByExpression(Authentication auth, ProxySpec spec, AccessCo
107108
// no expression defined -> this user has no access based on the expression
108109
return false;
109110
}
110-
Object[] args;
111-
if (auth == null) {
112-
args = ArrayUtils.addAll(new Object[]{spec}, objects);
113-
} else {
114-
args = ArrayUtils.addAll(new Object[]{auth, auth.getPrincipal(), auth.getCredentials(), spec}, objects);
111+
SpecExpressionContext.SpecExpressionContextBuilder contextBuilder = SpecExpressionContext
112+
.create(objects)
113+
.addServerName()
114+
.extend(spec);
115+
116+
if (auth != null) {
117+
contextBuilder.extend(auth, auth.getPrincipal(), auth.getCredentials());
115118
}
116-
SpecExpressionContext context = SpecExpressionContext.create(args);
117-
return specExpressionResolver.evaluateToBoolean(accessControl.getExpression(), context);
119+
return specExpressionResolver.evaluateToBoolean(accessControl.getExpression(), contextBuilder.build());
118120
}
119121

120122
}

src/main/java/eu/openanalytics/containerproxy/service/ProxyAccessControlService.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ public boolean canAccess(Authentication auth, String specId) {
7171

7272
/**
7373
* @param auth the current user
74-
* @param specId the specId the user is trying to access
74+
* @param context the request context, should include the specId the user is trying to access
7575
* @return whether the user can access the given specId or when this spec does not exist whether the user already
7676
* has a proxy with this spec id
7777
*/

src/main/java/eu/openanalytics/containerproxy/service/ProxyService.java

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
import org.slf4j.Logger;
4747
import org.slf4j.LoggerFactory;
4848
import org.springframework.context.ApplicationEventPublisher;
49+
import org.springframework.context.annotation.Lazy;
4950
import org.springframework.core.env.Environment;
5051
import org.springframework.data.util.Pair;
5152
import org.springframework.security.access.AccessDeniedException;
@@ -111,6 +112,9 @@ public class ProxyService {
111112
private SpecExpressionResolver expressionResolver;
112113
@Inject
113114
private LogService logService;
115+
@Inject
116+
@Lazy
117+
private ProxyAccessControlService proxyAccessControlService;
114118
private boolean stopAppsOnShutdown;
115119
private Pair<String, Instant> lastStop = null;
116120
private int requestTimeout;
@@ -199,15 +203,29 @@ public Stream<Proxy> getUserProxiesBySpecId(String specId) {
199203

200204
/**
201205
* Get all proxies that are owned by the current user.
206+
* Checks whether the user can still access the spec.
202207
*
203208
* @return A List of matching proxies, may be empty.
204209
*/
205210
public List<Proxy> getUserProxies() {
206-
return proxyStore.getUserProxies(userService.getCurrentUserId());
211+
return getUserProxies(userService.getCurrentAuth());
212+
}
213+
214+
/**
215+
* Get all proxies that are owned by the given user.
216+
* Checks whether the user can still access the spec.
217+
*
218+
* @return A List of matching proxies, may be empty.
219+
*/
220+
public List<Proxy> getUserProxies(Authentication authentication) {
221+
return proxyStore.getUserProxies(authentication.getName())
222+
.stream()
223+
.filter(p -> proxyAccessControlService.canAccess(authentication, p.getSpecId())).toList();
207224
}
208225

209226
/**
210227
* Get all proxies that are owned by the given user.
228+
* Does **not** check whether the user can still access the spec.
211229
*
212230
* @return A List of matching proxies, may be empty.
213231
*/
@@ -470,7 +488,7 @@ private Pair<ProxySpec, Proxy> prepareProxyForStart(Authentication user, Proxy p
470488
spec,
471489
user,
472490
user.getPrincipal(),
473-
user.getCredentials());
491+
user.getCredentials()).build();
474492

475493
// resolve SpEL expression in spec
476494
spec = spec.firstResolve(expressionResolver, context);

src/main/java/eu/openanalytics/containerproxy/service/UserService.java

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,9 @@ public void logout(Authentication auth) {
224224
if (authNull(auth)) return;
225225
String userId = auth.getName();
226226

227-
if (logoutStrategy != null) logoutStrategy.onLogout(userId);
227+
if (logoutStrategy != null) {
228+
logoutStrategy.onLogout(auth);
229+
}
228230
log.info(String.format("User logged out [user: %s]", userId));
229231

230232
HttpSession session = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest().getSession();
@@ -267,7 +269,9 @@ public void onHttpSessionDestroyedEvent(HttpSessionDestroyedEvent event) {
267269

268270
Authentication auth = securityContext.getAuthentication();
269271
String userId = securityContext.getAuthentication().getName();
270-
if (logoutStrategy != null) logoutStrategy.onLogout(userId);
272+
if (logoutStrategy != null) {
273+
logoutStrategy.onLogout(auth);
274+
}
271275

272276
log.info(String.format("User logged out [user: %s]", userId));
273277
applicationEventPublisher.publishEvent(new UserLogoutEvent(
@@ -278,8 +282,12 @@ public void onHttpSessionDestroyedEvent(HttpSessionDestroyedEvent event) {
278282
));
279283
} else if (authBackend.getName().equals("none")) {
280284
String userId = NoAuthenticationBackend.extractUserId(event.getSession());
281-
if (userId == null) return;
282-
if (logoutStrategy != null) logoutStrategy.onLogout(userId);
285+
if (userId == null) {
286+
return;
287+
}
288+
if (logoutStrategy != null) {
289+
logoutStrategy.onLogout(userId);
290+
}
283291

284292
log.info(String.format("Anonymous user logged out [user: %s]", userId));
285293
applicationEventPublisher.publishEvent(new UserLogoutEvent(

src/main/java/eu/openanalytics/containerproxy/spec/expression/SpecExpressionContext.java

Lines changed: 48 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@
3535
import org.springframework.context.ApplicationEvent;
3636
import org.springframework.security.core.Authentication;
3737
import org.springframework.security.ldap.userdetails.LdapUserDetails;
38+
import org.springframework.web.context.request.RequestContextHolder;
39+
import org.springframework.web.context.request.ServletRequestAttributes;
3840

3941
import java.util.Arrays;
4042
import java.util.Collections;
@@ -57,39 +59,11 @@ public class SpecExpressionContext {
5759
String userId;
5860
JsonNode json;
5961
ApplicationEvent event;
62+
String serverName;
6063

61-
public static SpecExpressionContext create(Object... objects) {
64+
public static SpecExpressionContextBuilder create(Object... objects) {
6265
SpecExpressionContextBuilder builder = SpecExpressionContext.builder();
63-
return create(builder, objects);
64-
}
65-
66-
public static SpecExpressionContext create(SpecExpressionContextBuilder builder, Object... objects) {
67-
for (Object o : objects) {
68-
if (o instanceof ContainerSpec) {
69-
builder.containerSpec = (ContainerSpec) o;
70-
} else if (o instanceof ProxySpec) {
71-
builder.proxySpec = (ProxySpec) o;
72-
} else if (o instanceof Proxy) {
73-
builder.proxy = (Proxy) o;
74-
} else if (o instanceof OpenIDAuthenticationBackend.CustomNameOidcUser) {
75-
builder.oidcUser = (OpenIDAuthenticationBackend.CustomNameOidcUser) o;
76-
} else if (o instanceof ResponseAuthenticationConverter.Saml2AuthenticatedPrincipal) {
77-
builder.samlCredential = (ResponseAuthenticationConverter.Saml2AuthenticatedPrincipal) o;
78-
} else if (o instanceof LdapUserDetails) {
79-
builder.ldapUser = (LdapUserDetails) o;
80-
} else if (o instanceof WebServiceAuthenticationBackend.WebServiceUser) {
81-
builder.webServiceUser = (WebServiceAuthenticationBackend.WebServiceUser) o;
82-
} else if (o instanceof JsonNode) {
83-
builder.json = (JsonNode) o;
84-
} else if (o instanceof ApplicationEvent) {
85-
builder.event = (ApplicationEvent) o;
86-
}
87-
if (o instanceof Authentication) {
88-
builder.groups = UserService.getGroups((Authentication) o);
89-
builder.userId = ((Authentication) o).getName();
90-
}
91-
}
92-
return builder.build();
66+
return builder.extend(objects);
9367
}
9468

9569
/**
@@ -150,7 +124,49 @@ public boolean isOneOfIgnoreCase(String attribute, String... allowedValues) {
150124
}
151125

152126
public SpecExpressionContext copy(Object... objects) {
153-
return create(toBuilder(), objects);
127+
return toBuilder().extend(objects).build();
154128
}
155129

130+
public static class SpecExpressionContextBuilder {
131+
public SpecExpressionContextBuilder extend(Object... objects) {
132+
for (Object o : objects) {
133+
if (o instanceof ContainerSpec) {
134+
containerSpec = (ContainerSpec) o;
135+
} else if (o instanceof ProxySpec) {
136+
proxySpec = (ProxySpec) o;
137+
} else if (o instanceof Proxy) {
138+
proxy = (Proxy) o;
139+
} else if (o instanceof OpenIDAuthenticationBackend.CustomNameOidcUser) {
140+
oidcUser = (OpenIDAuthenticationBackend.CustomNameOidcUser) o;
141+
} else if (o instanceof ResponseAuthenticationConverter.Saml2AuthenticatedPrincipal) {
142+
samlCredential = (ResponseAuthenticationConverter.Saml2AuthenticatedPrincipal) o;
143+
} else if (o instanceof LdapUserDetails) {
144+
ldapUser = (LdapUserDetails) o;
145+
} else if (o instanceof WebServiceAuthenticationBackend.WebServiceUser) {
146+
webServiceUser = (WebServiceAuthenticationBackend.WebServiceUser) o;
147+
} else if (o instanceof JsonNode) {
148+
json = (JsonNode) o;
149+
} else if (o instanceof ApplicationEvent) {
150+
event = (ApplicationEvent) o;
151+
}
152+
if (o instanceof Authentication) {
153+
groups = UserService.getGroups((Authentication) o);
154+
userId = ((Authentication) o).getName();
155+
}
156+
}
157+
return this;
158+
}
159+
160+
public SpecExpressionContextBuilder addServerName() {
161+
try {
162+
ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();
163+
serverName = servletRequestAttributes.getRequest().getServerName();
164+
} catch (IllegalStateException ignored) {
165+
// not in a request
166+
}
167+
return this;
168+
}
169+
}
170+
171+
156172
}

0 commit comments

Comments
 (0)