1 package de.juplo.facebook.token;
2
3
4 import java.io.IOException;
5 import java.io.UnsupportedEncodingException;
6 import java.security.InvalidKeyException;
7 import java.security.NoSuchAlgorithmException;
8 import java.util.Date;
9 import java.util.HashMap;
10 import java.util.Map;
11 import java.util.regex.Matcher;
12 import java.util.regex.Pattern;
13 import javax.crypto.Mac;
14 import javax.crypto.spec.SecretKeySpec;
15 import org.apache.commons.codec.binary.Base64;
16 import org.codehaus.jackson.JsonNode;
17 import org.codehaus.jackson.map.ObjectMapper;
18 import org.slf4j.Logger;
19 import org.slf4j.LoggerFactory;
20 import org.springframework.security.oauth2.client.resource.OAuth2ProtectedResourceDetails;
21 import org.springframework.security.oauth2.client.resource.UserRedirectRequiredException;
22 import org.springframework.security.oauth2.client.token.AccessTokenRequest;
23 import org.springframework.security.oauth2.client.token.grant.code.AuthorizationCodeAccessTokenProvider;
24 import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
25 import org.springframework.security.oauth2.common.OAuth2AccessToken;
26
27
28
29
30
31
32
33
34
35 public class SignedRequestAwareAuthorizationCodeAccessTokenProvider
36 extends AuthorizationCodeAccessTokenProvider
37 {
38 private final Logger log =
39 LoggerFactory.getLogger(SignedRequestAwareAuthorizationCodeAccessTokenProvider.class);
40 private final static Pattern pattern =
41 Pattern.compile("([a-zA-Z0-9_-]+)\\.([a-zA-Z0-9_-]+)");
42
43 public final static String PARAM_SIGNED_REQUEST = "signed_request";
44
45
46 private String secret;
47 private ObjectMapper objectMapper;
48
49
50 @Override
51 public OAuth2AccessToken obtainAccessToken(
52 OAuth2ProtectedResourceDetails details,
53 AccessTokenRequest parameters
54 )
55 {
56 try
57 {
58 return super.obtainAccessToken(details, parameters);
59 }
60 catch (UserRedirectRequiredException redirect)
61 {
62 log.debug("no valid access-token available: checking for signed request");
63
64 if (!parameters.containsKey(PARAM_SIGNED_REQUEST))
65 {
66 log.info(
67 "parameter " + PARAM_SIGNED_REQUEST + " is not present"
68 );
69 throw redirect;
70 }
71
72 String signed_request = parameters.get(PARAM_SIGNED_REQUEST).get(0);
73
74 Matcher matcher = pattern.matcher(signed_request);
75 if (!matcher.matches())
76 {
77 log.error("invalid signed_request: {}", signed_request);
78 throw redirect;
79 }
80
81 String signature = matcher.group(1);
82 String rawdata = matcher.group(2);
83
84 String data;
85 try
86 {
87 data = new String(Base64.decodeBase64(rawdata), "UTF-8");
88 log.debug("JSON-data: {}", data);
89 }
90 catch (UnsupportedEncodingException e)
91 {
92 log.error("error while decoding data: {}", e.getMessage());
93 throw redirect;
94 }
95
96 JsonNode json;
97 try
98 {
99 json = objectMapper.readTree(data);
100 }
101 catch (IOException e)
102 {
103 log.error("error \"{}\" while parsing JSON-data: {}", e, data);
104 throw redirect;
105 }
106
107 String algorithm = "";
108 try
109 {
110 algorithm = json.get("algorithm").asText();
111 }
112 catch (NullPointerException e) {}
113 if (algorithm.isEmpty())
114 {
115 log.error("field \"algorithm\" is missing: {}", data);
116 throw redirect;
117 }
118 algorithm = algorithm.replaceAll("-", "");
119
120 String check;
121 try
122 {
123 SecretKeySpec key = new SecretKeySpec(secret.getBytes("UTF-8"), algorithm);
124 Mac mac = Mac.getInstance(algorithm);
125 mac.init(key);
126 byte[] hmacData = mac.doFinal(rawdata.getBytes("UTF-8"));
127 check = new String(Base64.encodeBase64URLSafe(hmacData), "UTF-8");
128 }
129 catch (
130 UnsupportedEncodingException |
131 NoSuchAlgorithmException |
132 InvalidKeyException |
133 IllegalStateException e
134 )
135 {
136 log.error("signature check failed!", e);
137 throw redirect;
138 }
139 if (!check.equals(signature))
140 {
141 log.error("signature does not match!");
142 throw redirect;
143 }
144
145
146
147
148
149
150
151
152 Map<String,Object> additionalInformation = new HashMap<>();
153 try
154 {
155 additionalInformation.put(
156 "issued_at",
157 new Date(json.get("issued_at").getLongValue()*1000L)
158 );
159 Map<String,Object> user = new HashMap<>();
160 user.put(
161 "country",
162 json.get("user").get("country").asText()
163 );
164 user.put(
165 "locale",
166 json.get("user").get("locale").asText()
167 );
168 user.put(
169 "age_min",
170 json.get("user").get("age").get("min").getNumberValue()
171 );
172 if (json.get("user") != null && json.get("user").get("max") != null)
173 user.put(
174 "age_max",
175 json.get("user").get("age").get("max").getNumberValue()
176 );
177 additionalInformation.put("user", user);
178 if (json.get("app_data") != null)
179 additionalInformation.put("app_data", json.get("app_data").asText());
180 if (json.get("page") != null)
181 {
182 Map<String,Object> page = new HashMap<>();
183 page.put("id", json.get("page").get("id").asText());
184 page.put("liked", json.get("page").get("liked").asBoolean());
185 page.put("admin", json.get("page").get("admin").asBoolean());
186 additionalInformation.put("page", page);
187 }
188 }
189 catch (NullPointerException e)
190 {
191 log.warn("expected additional data is missing: {}", data);
192 }
193
194 DefaultOAuth2AccessToken token = null;
195 try
196 {
197 String value = json.get("oauth_token").asText();
198 if (value.isEmpty())
199 {
200 log.error("field \"oauth_token\" is missing: {}", data);
201 throw redirect;
202 }
203 token = new DefaultOAuth2AccessToken(value);
204 token.setExpiration(new Date(json.get("expires").getLongValue()*1000L));
205
206 additionalInformation.put(
207 "user_id",
208 json.get("user_id").asText()
209 );
210
211 token.setAdditionalInformation(additionalInformation);
212 }
213 catch (NullPointerException e)
214 {
215 if (token == null)
216 {
217 log.error("field \"oauth_token\" is missing: {}", data);
218 throw redirect;
219 }
220 else
221 log.warn("expected additional data is missing: {}", data);
222 }
223
224 return token;
225 }
226 }
227
228
229 public String getSecret()
230 {
231 return secret;
232 }
233
234 public void setSecret(String secret)
235 {
236 this.secret = secret;
237 }
238
239 public ObjectMapper getObjectMapper()
240 {
241 return objectMapper;
242 }
243
244 public void setObjectMapper(ObjectMapper objectMapper)
245 {
246 this.objectMapper = objectMapper;
247 }
248 }