001/* 002 * The contents of this file are subject to the terms of the Common Development and 003 * Distribution License (the License). You may not use this file except in compliance with the 004 * License. 005 * 006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the 007 * specific language governing permission and limitations under the License. 008 * 009 * When distributing Covered Software, include this CDDL Header Notice in each file and include 010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL 011 * Header, with the fields enclosed by brackets [] replaced by your own identifying 012 * information: "Portions copyright [year] [name of copyright owner]". 013 * 014 * Copyright 2014-2015 ForgeRock AS. 015 */ 016 017package org.forgerock.openig.filter.oauth2.resolver; 018 019import static java.util.concurrent.TimeUnit.*; 020 021import java.util.Collections; 022import java.util.HashSet; 023import java.util.Map; 024import java.util.Set; 025 026import org.forgerock.json.JsonValue; 027import org.forgerock.json.JsonValueException; 028import org.forgerock.openig.filter.oauth2.AccessToken; 029import org.forgerock.openig.filter.oauth2.OAuth2TokenException; 030import org.forgerock.util.time.TimeService; 031 032/** 033 * Models an {@link AccessToken} as returned by the OpenAM {@literal tokeninfo} endpoint. 034 * <pre> 035 * {@code 036 * curl https://openam.example.com:8443/openam/oauth2/tokeninfo?access_token=70e5776c-b0fa-4c70-9962-defb0e9c3cd6 037 * } 038 * </pre> 039 * 040 * Example of OpenAM returned Json value (for the previous request): 041 * <pre> 042 * {@code 043 * { 044 * "scope": [ 045 * "email", 046 * "profile" 047 * ], 048 * "grant_type": "password", 049 * "realm": "/", 050 * "token_type": "Bearer", 051 * "expires_in": 471, 052 * "access_token": "70e5776c-b0fa-4c70-9962-defb0e9c3cd6", 053 * "email": "", 054 * "profile": "" 055 * } 056 * } 057 * </pre> 058 */ 059public class OpenAmAccessToken implements AccessToken { 060 061 private final JsonValue rawInfo; 062 private final String token; 063 private final Set<String> scopes; 064 private final long expiresAt; 065 066 /** 067 * Builds a {@link AccessToken} with the result of a call to the {@literal tokeninfo} endpoint. 068 * 069 * @param rawInfo 070 * raw response message. 071 * @param token 072 * token identifier 073 * @param scopes 074 * scopes of the token 075 * @param expiresAt 076 * When this token will expires 077 */ 078 public OpenAmAccessToken(final JsonValue rawInfo, 079 final String token, 080 final Set<String> scopes, 081 final long expiresAt) { 082 this.rawInfo = rawInfo; 083 this.token = token; 084 this.scopes = Collections.unmodifiableSet(scopes); 085 this.expiresAt = expiresAt; 086 } 087 088 @Override 089 public Map<String, Object> getInfo() { 090 return rawInfo.asMap(); 091 } 092 093 @Override 094 public JsonValue asJsonValue() { 095 return rawInfo; 096 } 097 098 @Override 099 public String getToken() { 100 return token; 101 } 102 103 @Override 104 public Set<String> getScopes() { 105 return scopes; 106 } 107 108 @Override 109 public long getExpiresAt() { 110 return expiresAt; 111 } 112 113 /** 114 * Build helper for {@link OpenAmAccessToken}. 115 */ 116 public static class Builder { 117 118 private final TimeService time; 119 120 /** 121 * Creates a new Builder with the given {@link TimeService}. 122 * 123 * @param time time service used to compute the expiration date 124 */ 125 public Builder(final TimeService time) { 126 this.time = time; 127 } 128 129 /** 130 * Builds a {@link OpenAmAccessToken} from a raw JSON response returned by the {@literal /oauth2/tokeninfo} 131 * endpoint. 132 * 133 * @param raw 134 * JSON response 135 * @return a new {@link OpenAmAccessToken} 136 * @throws OAuth2TokenException 137 * if the JSON response is not formatted correctly. 138 */ 139 public OpenAmAccessToken build(final JsonValue raw) throws OAuth2TokenException { 140 try { 141 long expiresIn = raw.get("expires_in").required().asLong(); 142 Set<String> scopes = new HashSet<>(raw.get("scope").required().asList(String.class)); 143 String token = raw.get("access_token").required().asString(); 144 return new OpenAmAccessToken(raw, 145 token, 146 scopes, 147 getExpirationTime(expiresIn)); 148 } catch (JsonValueException e) { 149 throw new OAuth2TokenException("Cannot build AccessToken from the given JSON: invalid format", e); 150 } 151 } 152 153 private long getExpirationTime(final long delayInSeconds) { 154 return time.now() + MILLISECONDS.convert(delayInSeconds, SECONDS); 155 } 156 157 } 158}