Skip to content
This repository was archived by the owner on Sep 28, 2022. It is now read-only.

Commit 3fcae88

Browse files
dBucikDominik František Bučík
authored andcommitted
refactor: 💡 Refactored token introspection endpoint code
1 parent f58b7c7 commit 3fcae88

File tree

1 file changed

+101
-107
lines changed

1 file changed

+101
-107
lines changed

perun-oidc-server/src/main/java/cz/muni/ics/oauth2/web/endpoint/IntrospectionEndpoint.java

Lines changed: 101 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -17,174 +17,168 @@
1717
*******************************************************************************/
1818
package cz.muni.ics.oauth2.web.endpoint;
1919

20-
import com.google.common.base.Strings;
2120
import com.google.common.collect.ImmutableMap;
2221
import cz.muni.ics.oauth2.model.ClientDetailsEntity;
2322
import cz.muni.ics.oauth2.model.OAuth2AccessTokenEntity;
2423
import cz.muni.ics.oauth2.model.OAuth2RefreshTokenEntity;
2524
import cz.muni.ics.oauth2.service.ClientDetailsEntityService;
2625
import cz.muni.ics.oauth2.service.IntrospectionResultAssembler;
2726
import cz.muni.ics.oauth2.service.OAuth2TokenEntityService;
28-
import cz.muni.ics.oauth2.service.SystemScopeService;
2927
import cz.muni.ics.oauth2.web.AuthenticationUtilities;
3028
import cz.muni.ics.openid.connect.model.UserInfo;
3129
import cz.muni.ics.openid.connect.service.UserInfoService;
3230
import cz.muni.ics.openid.connect.view.HttpCodeView;
3331
import cz.muni.ics.openid.connect.view.JsonEntityView;
34-
import java.util.HashSet;
32+
33+
import java.util.HashMap;
3534
import java.util.Map;
3635
import java.util.Set;
3736
import lombok.extern.slf4j.Slf4j;
3837
import org.springframework.beans.factory.annotation.Autowired;
3938
import org.springframework.http.HttpStatus;
4039
import org.springframework.security.core.Authentication;
4140
import org.springframework.security.oauth2.common.exceptions.InvalidTokenException;
42-
import org.springframework.security.oauth2.provider.OAuth2Authentication;
4341
import org.springframework.stereotype.Controller;
4442
import org.springframework.ui.Model;
43+
import org.springframework.util.StringUtils;
4544
import org.springframework.web.bind.annotation.RequestMapping;
4645
import org.springframework.web.bind.annotation.RequestParam;
4746

4847
@Controller
4948
@Slf4j
5049
public class IntrospectionEndpoint {
5150

52-
/**
53-
*
54-
*/
5551
public static final String URL = "introspect";
5652

57-
@Autowired
58-
private OAuth2TokenEntityService tokenServices;
59-
60-
@Autowired
61-
private ClientDetailsEntityService clientService;
53+
public static final String PARAM_TOKEN = "token";
54+
public static final String PARAM_TOKEN_TYPE_HINT = "token_type_hint";
6255

63-
@Autowired
64-
private IntrospectionResultAssembler introspectionResultAssembler;
56+
private final OAuth2TokenEntityService tokenServices;
57+
private final ClientDetailsEntityService clientService;
58+
private final IntrospectionResultAssembler introspectionResultAssembler;
59+
private final UserInfoService userInfoService;
6560

6661
@Autowired
67-
private UserInfoService userInfoService;
68-
69-
public IntrospectionEndpoint() {
70-
71-
}
72-
73-
public IntrospectionEndpoint(OAuth2TokenEntityService tokenServices) {
62+
public IntrospectionEndpoint(OAuth2TokenEntityService tokenServices,
63+
ClientDetailsEntityService clientService,
64+
IntrospectionResultAssembler introspectionResultAssembler,
65+
UserInfoService userInfoService)
66+
{
7467
this.tokenServices = tokenServices;
68+
this.clientService = clientService;
69+
this.introspectionResultAssembler = introspectionResultAssembler;
70+
this.userInfoService = userInfoService;
7571
}
7672

7773
@RequestMapping("/" + URL)
78-
public String verify(@RequestParam("token") String tokenValue,
79-
@RequestParam(value = "token_type_hint", required = false) String tokenType,
80-
Authentication auth, Model model) {
81-
82-
ClientDetailsEntity authClient = null;
83-
Set<String> authScopes = new HashSet<>();
74+
public String introspect(@RequestParam(PARAM_TOKEN) String token,
75+
@RequestParam(value = PARAM_TOKEN_TYPE_HINT, required = false) String tokenTypeHint,
76+
Authentication auth,
77+
Model model)
78+
{
79+
if (auth == null) {
80+
log.error("No authentication object available in the introspection endpoint");
81+
return codeErrorResponse(model, HttpStatus.UNAUTHORIZED);
82+
}
8483

85-
if (auth instanceof OAuth2Authentication) {
86-
// the client authenticated with OAuth, do our UMA checks
87-
AuthenticationUtilities.ensureOAuthScope(auth, SystemScopeService.UMA_PROTECTION_SCOPE);
84+
String authClientId = auth.getName();
85+
if (!StringUtils.hasText(authClientId)) {
86+
log.error("No client_id object available in the introspection endpoint");
87+
return codeErrorResponse(model, HttpStatus.INTERNAL_SERVER_ERROR);
88+
}
8889

89-
// get out the client that was issued the access token (not the token being introspected)
90-
OAuth2Authentication o2a = (OAuth2Authentication) auth;
90+
ClientDetailsEntity authClient = clientService.loadClientByClientId(authClientId);
91+
if (authClient == null) {
92+
log.error("No client found for client_id '{}'", authClientId);
93+
return codeErrorResponse(model, HttpStatus.BAD_REQUEST);
94+
} else if (!AuthenticationUtilities.hasRole(auth, "ROLE_CLIENT") || !authClient.isAllowIntrospection()) {
95+
log.error("Client '{}' is not allowed to call introspection endpoint", authClient.getClientId());
96+
return codeErrorResponse(model, HttpStatus.FORBIDDEN);
97+
}
9198

92-
String authClientId = o2a.getOAuth2Request().getClientId();
93-
authClient = clientService.loadClientByClientId(authClientId);
99+
return introspectToken(model, token, tokenTypeHint, authClient);
100+
}
94101

95-
// the owner is the user who authorized the token in the first place
96-
String ownerId = o2a.getUserAuthentication().getName();
102+
private String introspectToken(Model model, String token, String tokenTypeHint, ClientDetailsEntity authClient) {
103+
Map<String, Object> entity;
104+
if (!StringUtils.hasText(token)) {
105+
log.error("Token introspection failed; token ('{}') not provided", token);
106+
entity = introspectUnknownToken();
107+
return jsonResponse(model, entity);
108+
}
97109

98-
authScopes.addAll(authClient.getScope());
110+
if ("refresh_token".equals(tokenTypeHint)) {
111+
entity = introspectRefreshToken(token, authClient.getScope());
112+
if (entity != null) {
113+
return jsonResponse(model, entity);
114+
} else {
115+
entity = introspectAccessToken(token, authClient.getScope());
116+
}
117+
} else if (tokenTypeHint.equals("access_token")) {
118+
entity = introspectAccessToken(token, authClient.getScope());
119+
if (entity != null) {
120+
return jsonResponse(model, entity);
121+
} else {
122+
entity = introspectRefreshToken(token, authClient.getScope());
123+
}
99124
} else {
100-
// the client authenticated directly, make sure it's got the right access
101-
102-
String authClientId = auth.getName(); // direct authentication puts the client_id into the authentication's name field
103-
authClient = clientService.loadClientByClientId(authClientId);
104-
105-
// directly authenticated clients get a subset of any scopes that they've registered for
106-
authScopes.addAll(authClient.getScope());
107-
108-
if (!AuthenticationUtilities.hasRole(auth, "ROLE_CLIENT")
109-
|| !authClient.isAllowIntrospection()) {
110-
111-
// this client isn't allowed to do direct introspection
112-
113-
log.error("Client " + authClient.getClientId() + " is not allowed to call introspection endpoint");
114-
model.addAttribute("code", HttpStatus.FORBIDDEN);
115-
return HttpCodeView.VIEWNAME;
116-
125+
entity = introspectAccessToken(token, authClient.getScope());
126+
if (entity != null) {
127+
return jsonResponse(model, entity);
128+
} else {
129+
entity = introspectRefreshToken(token, authClient.getScope());
117130
}
118-
119131
}
120132

121-
// by here we're allowed to introspect, now we need to look up the token in our token stores
122-
123-
// first make sure the token is there
124-
if (Strings.isNullOrEmpty(tokenValue)) {
125-
log.error("Verify failed; token value is null");
126-
Map<String,Boolean> entity = ImmutableMap.of("active", Boolean.FALSE);
127-
model.addAttribute(JsonEntityView.ENTITY, entity);
128-
return JsonEntityView.VIEWNAME;
133+
if (entity == null) {
134+
entity = introspectUnknownToken();
129135
}
136+
return jsonResponse(model, entity);
137+
}
130138

131-
OAuth2AccessTokenEntity accessToken = null;
132-
OAuth2RefreshTokenEntity refreshToken = null;
133-
ClientDetailsEntity tokenClient;
134-
UserInfo user;
139+
private Map<String, Object> introspectUnknownToken() {
140+
return ImmutableMap.of(IntrospectionResultAssembler.ACTIVE, false);
141+
}
135142

143+
private Map<String, Object> introspectAccessToken(String token, Set<String> callerScopes) {
136144
try {
137-
138145
// check access tokens first (includes ID tokens)
139-
accessToken = tokenServices.readAccessToken(tokenValue);
140-
141-
tokenClient = accessToken.getClient();
146+
OAuth2AccessTokenEntity accessToken = tokenServices.readAccessToken(token);
147+
ClientDetailsEntity tokenClient = accessToken.getClient();
142148

143149
// get the user information of the user that authorized this token in the first place
144150
String userName = accessToken.getAuthenticationHolder().getAuthentication().getName();
145-
user = userInfoService.get(userName, tokenClient.getClientId(),
146-
authScopes, accessToken.getAuthenticationHolder().getUserAuth());
147-
151+
UserInfo user = userInfoService.get(userName, tokenClient.getClientId(),
152+
callerScopes, accessToken.getAuthenticationHolder().getUserAuth());
153+
return introspectionResultAssembler.assembleFrom(accessToken, user, callerScopes);
148154
} catch (InvalidTokenException e) {
149-
log.info("Invalid access token. Checking refresh token.");
150-
try {
151-
152-
// check refresh tokens next
153-
refreshToken = tokenServices.getRefreshToken(tokenValue);
154-
155-
tokenClient = refreshToken.getClient();
156-
157-
// get the user information of the user that authorized this token in the first place
158-
String userName = refreshToken.getAuthenticationHolder().getAuthentication().getName();
159-
user = userInfoService.get(userName, tokenClient.getClientId(), authScopes,
160-
refreshToken.getAuthenticationHolder().getUserAuth());
161-
162-
} catch (InvalidTokenException e2) {
163-
log.error("Invalid refresh token");
164-
Map<String,Boolean> entity = ImmutableMap.of(IntrospectionResultAssembler.ACTIVE, Boolean.FALSE);
165-
model.addAttribute(JsonEntityView.ENTITY, entity);
166-
return JsonEntityView.VIEWNAME;
167-
}
155+
return null;
168156
}
157+
}
169158

170-
// if it's a valid token, we'll print out information on it
159+
private Map<String, Object> introspectRefreshToken(String token, Set<String> callerScopes) {
160+
try {
161+
OAuth2RefreshTokenEntity refreshToken = tokenServices.getRefreshToken(token);
162+
ClientDetailsEntity tokenClient = refreshToken.getClient();
171163

172-
if (accessToken != null) {
173-
Map<String, Object> entity = introspectionResultAssembler.assembleFrom(accessToken, user, authScopes);
174-
model.addAttribute(JsonEntityView.ENTITY, entity);
175-
} else if (refreshToken != null) {
176-
Map<String, Object> entity = introspectionResultAssembler.assembleFrom(refreshToken, user, authScopes);
177-
model.addAttribute(JsonEntityView.ENTITY, entity);
178-
} else {
179-
// no tokens were found (we shouldn't get here)
180-
log.error("Verify failed; Invalid access/refresh token");
181-
Map<String,Boolean> entity = ImmutableMap.of(IntrospectionResultAssembler.ACTIVE, Boolean.FALSE);
182-
model.addAttribute(JsonEntityView.ENTITY, entity);
183-
return JsonEntityView.VIEWNAME;
164+
// get the user information of the user that authorized this token in the first place
165+
String userName = refreshToken.getAuthenticationHolder().getAuthentication().getName();
166+
UserInfo user = userInfoService.get(userName, tokenClient.getClientId(), callerScopes,
167+
refreshToken.getAuthenticationHolder().getUserAuth());
168+
return introspectionResultAssembler.assembleFrom(refreshToken, user, callerScopes);
169+
} catch (InvalidTokenException e2) {
170+
return null;
184171
}
172+
}
185173

186-
return JsonEntityView.VIEWNAME;
174+
private String codeErrorResponse(Model model, HttpStatus code) {
175+
model.addAttribute(HttpCodeView.CODE, code);
176+
return HttpCodeView.VIEWNAME;
177+
}
187178

179+
private String jsonResponse(Model model, Map<String, Object> entity) {
180+
model.addAttribute(JsonEntityView.ENTITY, entity);
181+
return JsonEntityView.VIEWNAME;
188182
}
189183

190184
}

0 commit comments

Comments
 (0)