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 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.fluent.JsonValue; 027import org.forgerock.json.fluent.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 * curl https://openam.example.com:8443/openam/oauth2/tokeninfo?access_token=70e5776c-b0fa-4c70-9962-defb0e9c3cd6 036 * </pre> 037 * 038 * Example of OpenAM returned Json value (for the previous request): 039 * <pre> 040 * { 041 * "scope": [ 042 * "email", 043 * "profile" 044 * ], 045 * "grant_type": "password", 046 * "realm": "/", 047 * "token_type": "Bearer", 048 * "expires_in": 471, 049 * "access_token": "70e5776c-b0fa-4c70-9962-defb0e9c3cd6", 050 * "email": "", 051 * "profile": "" 052 * } 053 * </pre> 054 */ 055public class OpenAmAccessToken implements AccessToken { 056 057 private final JsonValue rawInfo; 058 private final String token; 059 private final Set<String> scopes; 060 private final long expiresAt; 061 062 /** 063 * Builds a {@link AccessToken} with the result of a call to the {@literal tokeninfo} endpoint. 064 * 065 * @param rawInfo 066 * raw response message. 067 * @param token 068 * token identifier 069 * @param scopes 070 * scopes of the token 071 * @param expiresAt 072 * When this token will expires 073 */ 074 public OpenAmAccessToken(final JsonValue rawInfo, 075 final String token, 076 final Set<String> scopes, 077 final long expiresAt) { 078 this.rawInfo = rawInfo; 079 this.token = token; 080 this.scopes = Collections.unmodifiableSet(scopes); 081 this.expiresAt = expiresAt; 082 } 083 084 @Override 085 public Map<String, Object> getInfo() { 086 return rawInfo.asMap(); 087 } 088 089 @Override 090 public JsonValue asJsonValue() { 091 return rawInfo; 092 } 093 094 @Override 095 public String getToken() { 096 return token; 097 } 098 099 @Override 100 public Set<String> getScopes() { 101 return scopes; 102 } 103 104 @Override 105 public long getExpiresAt() { 106 return expiresAt; 107 } 108 109 /** 110 * Build helper for {@link OpenAmAccessToken}. 111 */ 112 public static class Builder { 113 114 private final TimeService time; 115 116 /** 117 * Creates a new Builder with the given {@link TimeService}. 118 * 119 * @param time time service used to compute the expiration date 120 */ 121 public Builder(final TimeService time) { 122 this.time = time; 123 } 124 125 /** 126 * Builds a {@link OpenAmAccessToken} from a raw JSON response returned by the {@literal /oauth2/tokeninfo} 127 * endpoint. 128 * 129 * @param raw 130 * JSON response 131 * @return a new {@link OpenAmAccessToken} 132 * @throws OAuth2TokenException 133 * if the JSON response is not formatted correctly. 134 */ 135 public OpenAmAccessToken build(final JsonValue raw) throws OAuth2TokenException { 136 try { 137 long expiresIn = raw.get("expires_in").required().asLong(); 138 Set<String> scopes = new HashSet<String>(raw.get("scope").required().asList(String.class)); 139 String token = raw.get("access_token").required().asString(); 140 return new OpenAmAccessToken(raw, 141 token, 142 scopes, 143 getExpirationTime(expiresIn)); 144 } catch (JsonValueException e) { 145 throw new OAuth2TokenException("Cannot build AccessToken from the given JSON: invalid format", e); 146 } 147 } 148 149 private long getExpirationTime(final long delayInSeconds) { 150 return time.now() + MILLISECONDS.convert(delayInSeconds, SECONDS); 151 } 152 153 } 154}