private double refresh_expires_in;
private String refresh_token;
private String token_type;
+ private String id_token;
public OAuthResponseData() {
}
this.access_token = access_token;
}
+ public void setId_token(String id_token){ this.id_token = id_token;}
+ public String getId_token(){ return this.id_token;}
@Override
public String toString() {
return "OAuthResponseData [access_token=" + access_token + ", expires_in=" + expires_in
private String authorization_endpoint;
private String token_endpoint;
private String userinfo_endpoint;
+
+ private String end_session_endpoint;
private String jwks_uri;
public OpenIdConfigResponseData(){
public void setJwks_uri(String jwks_uri) {
this.jwks_uri = jwks_uri;
}
+
+ public String getEnd_session_endpoint() {
+ return end_session_endpoint;
+ }
+
+ public void setEnd_session_endpoint(String end_session_endpoint) {
+ this.end_session_endpoint = end_session_endpoint;
+ }
+
}
public class UserTokenPayload {
+ public static final String PROVIDERID_INTERNAL="Internal";
+
private List<String> roles;
private String preferredUsername;
private String givenName;
private long exp;
private long iat;
+ private String providerId;
public long getExp() {
return exp;
this.roles = roles;
}
- public static UserTokenPayload create(String username, List<String> roles) {
+ public void setProviderId(String providerId){ this.providerId = providerId;}
+
+ public String getProviderId(){ return this.providerId;}
+
+ public static UserTokenPayload createInternal(String username, List<String> roles) {
UserTokenPayload data = new UserTokenPayload();
data.setPreferredUsername(username);
data.setRoles(roles);
+ data.setProviderId(PROVIDERID_INTERNAL);
return data;
}
+ public boolean isInternal() {
+ return PROVIDERID_INTERNAL.equals(this.providerId);
+ }
}
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
+import java.io.IOException;
import java.util.*;
import java.util.concurrent.ExecutionException;
import javax.servlet.Filter;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.authz.AuthorizationFilter;
import org.opendaylight.aaa.shiro.web.env.ThreadLocals;
final Object mappedValue) {
checkArgument(request instanceof HttpServletRequest, "Expected HttpServletRequest, received {}", request);
+
+ final boolean defaultReturnValue=false;
final Subject subject = getSubject(request, response);
final HttpServletRequest httpServletRequest = (HttpServletRequest)request;
final String requestURI = httpServletRequest.getRequestURI();
// The authorization container does not exist-- hence no authz rules are present
// Allow access.
LOG.debug("Authorization Container does not exist");
- return true;
+ return defaultReturnValue;
}
final HttpAuthorization httpAuthorization = authorizationOptional.get();
List<Policies> policiesList = policies != null ? policies.getPolicies() : null;
if (policiesList == null || policiesList.isEmpty()) {
// The authorization container exists, but no rules are present. Allow access.
- LOG.debug("Exiting successfully early since no authorization rules exist");
- return true;
+ LOG.debug("Exiting early since no authorization rules exist");
+ sendError(response, 403, "");
+ return defaultReturnValue;
}
// Sort the Policies list based on index
final String resource = policy.getResource();
final boolean pathsMatch = pathsMatch(resource, requestURI);
if (pathsMatch) {
- LOG.debug("paths match for pattern={} and requestURI={}", resource, requestURI);
+ LOG.debug("paths match for policy {} pattern={} and requestURI={}", policy.getIndex(), resource, requestURI);
final String method = httpServletRequest.getMethod();
LOG.trace("method={}", method);
List<Permissions> permissions = policy.getPermissions();
+ LOG.trace("perm={}", permissions);
if(permissions !=null) {
for (Permissions permission : permissions) {
final String role = permission.getRole();
LOG.trace("no permissions found");
}
LOG.debug("couldn't authorize the user for access");
+ sendError(response, 403, "");
return false;
}
}
- LOG.debug("successfully authorized the user for access");
- return true;
+ LOG.debug("no path found that matches {}", requestURI);
+ sendError(response, 403, "");
+ return defaultReturnValue;
+ }
+
+ private void sendError(ServletResponse response, int code, String message) {
+ if(response instanceof HttpServletResponse){
+ try {
+ ((HttpServletResponse)response).sendError(code, message);
+ } catch (IOException e) {
+ LOG.warn("unable to send {} {} response: ", code, message, e);
+ }
+ }
+ else{
+ LOG.warn("unable to send {} {} response", code, message);
+ }
}
}
private static final String DEFAULT_DOMAIN = "sdn";
private static final String HEAEDER_AUTHORIZATION = "Authorization";
+ private static final String LOGOUT_REDIRECT_URL_PARAMETER = "redirect_uri";
private static final String CLASSNAME_ODLBASICAUTH =
"org.opendaylight.aaa.shiro.filters.ODLHttpAuthenticationFilter";
private static final String CLASSNAME_ODLBEARERANDBASICAUTH =
}
private void handleLogout(HttpServletRequest req, HttpServletResponse resp) throws IOException {
+ final String bearerToken = this.tokenCreator.getBearerToken(req, true);
+ UserTokenPayload userInfo = this.tokenCreator.decode(bearerToken);
+ if (bearerToken != null && userInfo!=null && !userInfo.isInternal()) {
+ AuthService provider = this.providerStore.getOrDefault(userInfo.getProviderId(), null);
+ if (provider != null) {
+ String redirectUrl = req.getParameter(LOGOUT_REDIRECT_URL_PARAMETER);
+ if (redirectUrl == null) {
+ redirectUrl = this.config.getPublicUrl();
+ }
+ provider.sendLogoutRedirectResponse(bearerToken, resp, redirectUrl);
+ return;
+ }
+ }
this.logout();
- this.sendResponse(resp, HttpServletResponse.SC_OK, "");
+ this.sendResponse(resp, HttpServletResponse.SC_OK);
}
private void handleLoginRedirect(HttpServletRequest req, HttpServletResponse resp) throws IOException {
username = String.format("%s@%s", username, domain);
}
List<String> roles = odlIdentityService.listRoles(username, domain);
- return UserTokenPayload.create(username, roles);
+ return UserTokenPayload.createInternal(username, roles);
}
}
return null;
}
-
+ private void sendResponse(HttpServletResponse resp, int code) throws IOException {
+ this.sendResponse(resp, code, null);
+ }
private void sendResponse(HttpServletResponse resp, int code, Object data) throws IOException {
byte[] output = data != null ? mapper.writeValueAsString(data).getBytes() : new byte[0];
// output
protected final OAuthProviderConfig config;
protected final TokenCreator tokenCreator;
private final String redirectUri;
- private final String tokenEndpoint;
- private final String authEndpoint;
+ private final String tokenEndpointRelative;
+ private final String authEndpointAbsolute;
+ private final String logoutEndpointAbsolute;
+ private final Map<String, String> logoutTokenMap;
protected abstract String getTokenVerifierUri();
protected abstract Map<String, String> getAdditionalTokenVerifierParams();
throws JsonMappingException, JsonProcessingException;
protected abstract String getLoginUrl(String callbackUrl);
+ protected abstract String getLogoutUrl();
protected abstract UserTokenPayload requestUserRoles(String access_token, long issued_at, long expires_at);
this.mapper = new ObjectMapper();
this.mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
this.httpClient = new MappingBaseHttpClient(this.config.getUrlOrInternal(), this.config.trustAll());
+ this.logoutTokenMap = new HashMap<>();
if (this.config.hasToBeConfigured()){
Optional<MappedBaseHttpResponse<OpenIdConfigResponseData>> oresponse = this.httpClient.sendMappedRequest(
this.config.getOpenIdConfigUrl(), "GET", null, null, OpenIdConfigResponseData.class);
if(!response.isSuccess()){
throw new UnableToConfigureOAuthService(this.config.getOpenIdConfigUrl(), response.code);
}
- this.tokenEndpoint = response.body.getToken_endpoint();
- this.authEndpoint = response.body.getAuthorization_endpoint();
+ this.tokenEndpointRelative = trimUrl(this.config.getUrlOrInternal(),response.body.getToken_endpoint());
+ this.authEndpointAbsolute = extendUrl(this.config.getUrlOrInternal(),response.body.getAuthorization_endpoint());
+ this.logoutEndpointAbsolute = extendUrl(this.config.getUrlOrInternal(),response.body.getEnd_session_endpoint());
}
else{
- this.tokenEndpoint = null;
- this.authEndpoint = null;
+ this.tokenEndpointRelative = null;
+ this.authEndpointAbsolute = null;
+ this.logoutEndpointAbsolute = null;
}
}
+ public static String trimUrl(String baseUrl, String endpoint) {
+ if(endpoint.startsWith(baseUrl)){
+ return endpoint.substring(baseUrl.length());
+ }
+ if(endpoint.startsWith("http")){
+ return endpoint.substring(endpoint.indexOf("/",8));
+ }
+ return endpoint;
+ }
+ public static String extendUrl(String baseUrl, String endpoint) {
+ if(endpoint.startsWith("http")){
+ endpoint= endpoint.substring(endpoint.indexOf("/",8));
+ }
+ if(baseUrl.endsWith("/")){
+ baseUrl=baseUrl.substring(0,baseUrl.length()-2);
+ }
+ return baseUrl+endpoint;
+ }
+
public PublicOAuthProviderConfig getConfig() {
return new PublicOAuthProviderConfig(this);
}
public void sendLoginRedirectResponse(HttpServletResponse resp, String callbackUrl) {
resp.setStatus(HttpServletResponse.SC_MOVED_TEMPORARILY);
- String url = this.authEndpoint!=null?String.format(
+ String url = this.authEndpointAbsolute !=null?String.format(
"%s?client_id=%s&response_type=code&scope=%s&redirect_uri=%s",
- this.authEndpoint, urlEncode(this.config.getClientId()), this.config.getScope(),
+ this.authEndpointAbsolute, urlEncode(this.config.getClientId()), this.config.getScope(),
urlEncode(callbackUrl)):this.getLoginUrl(callbackUrl);
resp.setHeader("Location", url);
}
+ public void sendLogoutRedirectResponse(String token, HttpServletResponse resp, String redirectUrl)
+ throws IOException {
+ String idToken = this.logoutTokenMap.getOrDefault(token, null);
+ String logoutEndpoint = this.logoutEndpointAbsolute!=null?this.logoutEndpointAbsolute:this.getLogoutUrl();
+ if(idToken==null) {
+ LOG.debug("unable to find token in map. Do unsafe logout.");
+ resp.sendRedirect(this.logoutEndpointAbsolute);
+ return;
+ }
+ LOG.debug("id token found. redirect to specific logout");
+ resp.sendRedirect(String.format("%s?id_token_hint=%s&post_logout_redirect_uri=%s",logoutEndpoint, idToken,
+ urlEncode(redirectUrl)));
+ }
+
+
private static void sendErrorResponse(HttpServletResponse resp, String message) throws IOException {
resp.sendError(HttpServletResponse.SC_NOT_FOUND, message);
}
if (response != null) {
if (this.doSeperateRolesRequest()) {
- //long expiresAt = this.tokenCreator.getDefaultExp(Math.round(response.getExpires_in()));
+ LOG.debug("do a seperate role request");
long expiresAt = this.tokenCreator.getDefaultExp();
long issuedAt = this.tokenCreator.getDefaultIat();
UserTokenPayload data = this.requestUserRoles(response.getAccess_token(), issuedAt, expiresAt);
if (data != null) {
- this.handleUserInfoToken(data, resp, host);
+ BearerToken createdToken = this.handleUserInfoToken(data, resp, host);
+ this.logoutTokenMap.put(createdToken.getToken(),response.getId_token());
} else {
sendErrorResponse(resp, "unable to verify user");
}
} else {
- this.handleUserInfoToken(response.getAccess_token(), resp, host);
+ BearerToken createdToken = this.handleUserInfoToken(response.getAccess_token(), resp, host);
+ this.logoutTokenMap.put(createdToken.getToken(),response.getId_token());
}
} else {
sendErrorResponse(resp, "unable to verify code");
}
}
- private void handleUserInfoToken(UserTokenPayload data, HttpServletResponse resp, String localHostUrl)
+ private BearerToken handleUserInfoToken(UserTokenPayload data, HttpServletResponse resp, String localHostUrl)
throws IOException {
BearerToken onapToken = this.tokenCreator.createNewJWT(data);
sendTokenResponse(resp, onapToken, localHostUrl);
+ return onapToken;
}
- private void handleUserInfoToken(String accessToken, HttpServletResponse resp, String localHostUrl)
+ private BearerToken handleUserInfoToken(String accessToken, HttpServletResponse resp, String localHostUrl)
throws IOException {
try {
DecodedJWT jwt = JWT.decode(accessToken);
String spayload = base64Decode(jwt.getPayload());
LOG.debug("payload in jwt='{}'", spayload);
UserTokenPayload data = this.mapAccessToken(spayload);
- this.handleUserInfoToken(data, resp, localHostUrl);
+ return this.handleUserInfoToken(data, resp, localHostUrl);
} catch (JWTDecodeException | JsonProcessingException e) {
LOG.warn("unable to decode jwt token {}: ", accessToken, e);
sendErrorResponse(resp, e.getMessage());
}
+ return null;
}
resp.setStatus(200);
resp.setContentLength(output.length);
resp.setContentType("application/json");
+ resp.addCookie(this.tokenCreator.createAuthCookie(data));
ServletOutputStream os = null;
os = resp.getOutputStream();
os.write(output);
} else {
resp.setStatus(HttpServletResponse.SC_MOVED_TEMPORARILY);
resp.setHeader("Location", assembleUrl(localHostUrl, this.redirectUri, data.getToken()));
+ resp.addCookie(this.tokenCreator.createAuthCookie(data));
}
}
Map<String, String> headers = new HashMap<>();
headers.put("Content-Type", "application/x-www-form-urlencoded");
+ headers.put("Accept", "application/json");
Map<String, String> params = this.getAdditionalTokenVerifierParams();
params.put("code", code);
params.put("client_id", this.config.getClientId());
body.append(String.format("%s=%s&", p.getKey(), urlEncode(p.getValue())));
}
- String url = this.tokenEndpoint!=null?this.tokenEndpoint:this.getTokenVerifierUri();
+ String url = this.tokenEndpointRelative !=null?this.tokenEndpointRelative :this.getTokenVerifierUri();
Optional<MappedBaseHttpResponse<OAuthResponseData>> response =
this.httpClient.sendMappedRequest(url, "POST",
body.substring(0, body.length() - 1), headers, OAuthResponseData.class);
return URLEncoder.encode(s, StandardCharsets.UTF_8);
}
+
+
public enum ResponseType {
CODE, TOKEN, SESSION_STATE
}
this.config.getUrl(), urlEncode(this.config.getClientId()), this.createRandomId(), callbackUrl);
}
+ @Override
+ protected String getLogoutUrl() {
+ return String.format("%s/oauth/logout", this.config.getUrl());
+ }
+
private String createRandomId() {
String rnd = null;
while(true) {
this.config.getScope(), urlEncode(callbackUrl));
}
+ @Override
+ protected String getLogoutUrl() {
+ return String.format("%s/auth/realms/%s/protocol/openid-connect/logout", this.config.getUrl(),
+ urlEncode(this.config.getRealmName()));
+ }
+
@Override
protected List<String> mapRoles(List<String> data) {
final Map<String, String> map = this.config.getRoleMapping();
data.setExp(payload.getExp() * 1000L);
data.setFamilyName(payload.getFamilyName());
data.setGivenName(payload.getGivenName());
+ data.setProviderId(this.config.getId());
data.setPreferredUsername(payload.getPreferredUsername());
data.setRoles(this.mapRoles(payload.getRealmAccess().getRoles()));
return data;
return null;
}
+ @Override
+ protected String getLogoutUrl() {
+ return null;
+ }
+
@Override
protected UserTokenPayload requestUserRoles(String access_token, long issued_at, long expires_at) {
// TODO Auto-generated method stub
import java.security.Security;
import java.util.Arrays;
import java.util.Date;
+import java.util.Optional;
+import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.onap.ccsdk.features.sdnr.wt.oauthprovider.data.Config;
private static final String ROLES_CLAIM = "roles";
private static final String FAMILYNAME_CLAIM = "family_name";
private static final String NAME_CLAIM = "name";
+ private static final String PROVIDERID_CLAIM = "provider_id";
+ private static final String COOKIE_NAME_AUTH = "token";
static {
Security.addProvider(
final String token = JWT.create().withIssuer(issuer).withExpiresAt(new Date(data.getExp()))
.withIssuedAt(new Date(data.getIat())).withSubject(data.getPreferredUsername())
.withClaim(NAME_CLAIM, data.getGivenName()).withClaim(FAMILYNAME_CLAIM, data.getFamilyName())
+ .withClaim(PROVIDERID_CLAIM, data.getProviderId())
.withArrayClaim(ROLES_CLAIM, data.getRoles().toArray(new String[data.getRoles().size()]))
.sign(this.algorithm);
LOG.trace("token created: {}", token);
return new Date().getTime();
}
- public UserTokenPayload decode(HttpServletRequest req) throws JWTDecodeException {
+ public String getBearerToken(HttpServletRequest req) {
+ return this.getBearerToken(req, false);
+ }
+ public String getBearerToken(HttpServletRequest req, boolean checkCookie) {
final String authHeader = req.getHeader("Authorization");
- if (authHeader == null || !authHeader.startsWith("Bearer")) {
+ if ((authHeader == null || !authHeader.startsWith("Bearer")) && checkCookie) {
+ Optional<Cookie> ocookie =
+ Arrays.stream(req.getCookies()).filter(c -> COOKIE_NAME_AUTH.equals(c.getName())).findFirst();
+ if(ocookie.isEmpty()) {
+ return null;
+ }
+ return ocookie.get().getValue();
+ }
+ return authHeader.substring(7);
+ }
+ public UserTokenPayload decode(HttpServletRequest req) throws JWTDecodeException {
+ final String token = this.getBearerToken(req);
+ return token!=null?this.decode(token):null;
+ }
+ public UserTokenPayload decode(String token){
+ if(token == null){
return null;
}
- DecodedJWT jwt = JWT.decode(authHeader.substring(7));
+ DecodedJWT jwt = JWT.decode(token);
UserTokenPayload data = new UserTokenPayload();
data.setRoles(Arrays.asList(jwt.getClaim(ROLES_CLAIM).asArray(String.class)));
data.setExp(jwt.getExpiresAt().getTime());
data.setFamilyName(jwt.getClaim(FAMILYNAME_CLAIM).asString());
data.setGivenName(jwt.getClaim(NAME_CLAIM).asString());
data.setPreferredUsername(jwt.getClaim(NAME_CLAIM).asString());
-
+ data.setProviderId(jwt.getClaim(PROVIDERID_CLAIM).asString());
return data;
}
+ public Cookie createAuthCookie(BearerToken data) {
+ Cookie cookie = new Cookie(COOKIE_NAME_AUTH, data.getToken());
+ cookie.setMaxAge((int)this.tokenLifetimeSeconds);
+ cookie.setPath("/");
+ cookie.setHttpOnly(true);
+ cookie.setSecure(true);
+ return cookie;
+ }
}
import javax.servlet.http.HttpServletResponse;
import org.junit.AfterClass;
import org.junit.BeforeClass;
+import org.junit.Ignore;
import org.junit.Test;
import org.onap.ccsdk.features.sdnr.wt.oauthprovider.data.Config;
import org.onap.ccsdk.features.sdnr.wt.oauthprovider.data.OAuthProviderConfig;
public void test2() {
oauthService.sendLoginRedirectResponse(null, null);
}
-
+ @Ignore
+ @Test
+ public void test3() {
+ HttpServletResponse resp = mock(HttpServletResponse.class);
+ String token = "";
+ try {
+ oauthService.sendLogoutRedirectResponse(token, resp,"http://sdnr.onap/odlux/index.html");
+ verify(resp).setStatus(302);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
public static class KeycloakProviderServiceToTest extends KeycloakProviderService {
public KeycloakProviderServiceToTest(OAuthProviderConfig config, String redirectUri,
public static class MyHandler implements HttpHandler {
private static final String KEYCLOAK_TOKEN_ENDPOINT = "/auth/realms/onap/protocol/openid-connect/token";
+ private static final String KEYCLOAK_LOGOUT_ENDPOINT = "/auth/realms/onap/protocol/openid-connect/logout";
private static final String KEYCLOAK_TOKEN_RESPONSE =
loadResourceFileContent("src/test/resources/oauth/keycloak-token-response.json");
System.out.println(String.format("req received: %s %s", method, t.getRequestURI()));
OutputStream os = null;
try {
- if (method.equals("POST")) {
+ if("GET".equals(method)){
+ if(KEYCLOAK_LOGOUT_ENDPOINT.equals(uri)){
+ t.sendResponseHeaders(200, 0);
+ }
+ }
+ else if ("POST".equals(method)) {
if (uri.equals(KEYCLOAK_TOKEN_ENDPOINT)) {
t.sendResponseHeaders(200, KEYCLOAK_TOKEN_RESPONSE.length());
os = t.getResponseBody();
import org.onap.ccsdk.features.sdnr.wt.oauthprovider.OAuth2Realm;
import org.onap.ccsdk.features.sdnr.wt.oauthprovider.data.Config;
import org.onap.ccsdk.features.sdnr.wt.oauthprovider.data.UserTokenPayload;
+import org.onap.ccsdk.features.sdnr.wt.oauthprovider.providers.AuthService;
import org.onap.ccsdk.features.sdnr.wt.oauthprovider.providers.TokenCreator;
import org.opendaylight.aaa.api.shiro.principal.ODLPrincipal;
import org.opendaylight.aaa.shiro.web.env.ThreadLocals;
}
+ @Test
+ public void testUrlTrimming(){
+ final String internalUrl="https://test.identity.onap:49333";
+ final String externalUrl="https://test.identity.onap:49333";
+ final String testUrl1 = "/my/token/endpoint";
+ final String testUrl2 = internalUrl+testUrl1;
+ final String testUrl3 = externalUrl+testUrl1;
+
+ assertEquals(testUrl1, AuthService.trimUrl(internalUrl, testUrl1));
+ assertEquals(testUrl1, AuthService.trimUrl(internalUrl, testUrl2));
+ assertEquals(testUrl1, AuthService.trimUrl(externalUrl, testUrl3));
+
+ assertEquals(testUrl2, AuthService.extendUrl(internalUrl, testUrl3));
+
+
+
+ }
@Test
public void testAssertCredentialsMatch() {
//bearer token use case