001package com.nimbusds.openid.connect.provider.spi.grants;
002
003
004import java.util.ArrayList;
005import java.util.Date;
006import java.util.List;
007
008import com.nimbusds.oauth2.sdk.ParseException;
009import com.nimbusds.oauth2.sdk.Scope;
010import com.nimbusds.oauth2.sdk.id.Subject;
011import com.nimbusds.oauth2.sdk.util.JSONObjectUtils;
012import com.nimbusds.openid.connect.sdk.claims.ACR;
013import com.nimbusds.openid.connect.sdk.claims.AMR;
014import net.jcip.annotations.Immutable;
015import net.minidev.json.JSONObject;
016
017
018/**
019 * OAuth 2.0 / OpenID Connect authorisation produced by a {@link GrantHandler}
020 * specifying a subject (end-user) and permitting ID token issue.
021 *
022 * <p>Required authorisation details:
023 *
024 * <ul>
025 *     <li>The subject (end-user).
026 *     <li>The authorised scope.
027 * </ul>
028 *
029 * <p>All other parameters are optional or have suitable defaults.
030 */
031@Immutable
032public class SubjectAuthorization extends GrantAuthorization {
033
034
035        /**
036         * The identifier of the authorised subject.
037         */
038        private final Subject subject;
039
040
041        /**
042         * The ID token specification.
043         */
044        private final IDTokenSpec idTokenSpec;
045
046
047        /**
048         * The OpenID Connect claims spec.
049         */
050        private final ClaimsSpec claimsSpec;
051
052
053        /**
054         * Creates a new authorisation for the specified subject.
055         *
056         * @param subject         The subject (end-user) identifier. Must not
057         *                        be {@code null}.
058         * @param scope           The authorised scope values. Must not be
059         *                        {@code null}.
060         * @param accessTokenSpec The access token specification. Must not be
061         *                        {@code null}.
062         * @param idTokenSpec     The ID token specification. Must not be
063         *                        {@code null}.
064         * @param claimsSpec      The claims specification. Must not be
065         *                        {@code null}.
066         * @param data            Additional data as a JSON object,
067         *                        {@code null} if not specified.
068         */
069        public SubjectAuthorization(final Subject subject,
070                                    final Scope scope,
071                                    final AccessTokenSpec accessTokenSpec,
072                                    final IDTokenSpec idTokenSpec,
073                                    final ClaimsSpec claimsSpec,
074                                    final JSONObject data) {
075
076                super(scope, accessTokenSpec, data);
077
078                if (subject == null) {
079                        throw new IllegalArgumentException("The subject must not be null");
080                }
081
082                this.subject = subject;
083
084                if (idTokenSpec == null) {
085                        throw new IllegalArgumentException("The ID token specification must not be null");
086                }
087
088                this.idTokenSpec = idTokenSpec;
089
090                if (claimsSpec == null) {
091                        throw new IllegalArgumentException("The claim specification must not be null");
092                }
093
094                this.claimsSpec = claimsSpec;
095        }
096
097
098        /**
099         * Returns the subject (end-user) identifier.
100         *
101         * @return The subject identifier.
102         */
103        public Subject getSubject() {
104
105                return subject;
106        }
107
108
109        /**
110         * Returns the time of the subject authentication.
111         *
112         * @return The time of the subject authentication. If {@code null} it
113         *         will be set to now. Applies only if an ID token is issued.
114         */
115        public Date getAuthTime() {
116
117                return getIDTokenSpec().getAuthTime();
118        }
119
120
121        /**
122         * Returns the Authentication Context Class Reference (ACR).
123         *
124         * @return The Authentication Context Class Reference (ACR),
125         *         {@code null} if not specified. Applies only if an ID token
126         *         is issued.
127         */
128        public ACR getACR() {
129
130                return getIDTokenSpec().getACR();
131        }
132
133
134        /**
135         * Returns The Authentication Methods Reference (AMR) list.
136         *
137         * @return The Authentication Methods Reference (AMR) list,
138         *         {@code null} if not specified. Applies only if an ID token
139         *         is issued.
140         */
141        public List<AMR> getAMRList() {
142
143                return getIDTokenSpec().getAMRList();
144        }
145
146
147        /**
148         * Returns the ID token specification.
149         *
150         * @return The ID token specification.
151         */
152        public IDTokenSpec getIDTokenSpec() {
153
154                return idTokenSpec;
155        }
156
157
158        /**
159         * Returns the claims specification.
160         *
161         * @return The claims specification.
162         */
163        public ClaimsSpec getClaimsSpec() {
164
165                return claimsSpec;
166        }
167
168
169        @Override
170        public JSONObject toJSONObject() {
171
172                JSONObject o = super.toJSONObject();
173
174                o.put("sub", subject.getValue());
175
176                if (idTokenSpec.issue()) {
177                        JSONObject idTokenJSONObject = idTokenSpec.toJSONObject();
178                        // Move auth_time, acr and amr to top level
179                        if (idTokenJSONObject.get("auth_time") != null) {
180                                o.put("auth_time", idTokenJSONObject.remove("auth_time"));
181                        }
182                        if (idTokenJSONObject.get("acr") != null) {
183                                o.put("acr", idTokenJSONObject.remove("acr"));
184                        }
185                        if (idTokenJSONObject.get("amr") != null) {
186                                o.put("amr", idTokenJSONObject.remove("amr"));
187                        }
188                        o.put("id_token", idTokenJSONObject);
189                }
190
191                o.putAll(claimsSpec.toJSONObject());
192
193                return o;
194        }
195
196
197        /**
198         * Parses a subject authorisation from the specified JSON object.
199         *
200         * @param jsonObject The JSON object to parse. Must not be {@code null}.
201         *
202         * @return The subject authorisation.
203         *
204         * @throws ParseException If parsing failed.
205         */
206        public static SubjectAuthorization parse(final JSONObject jsonObject)
207                throws ParseException {
208
209                GrantAuthorization basicAuthz = GrantAuthorization.parse(jsonObject);
210
211                Subject sub = new Subject(JSONObjectUtils.getString(jsonObject, "sub"));
212
213                // Backward API compatibility
214                Date topLevelAuthTime = null;
215
216                if (jsonObject.containsKey("auth_time")) {
217                        topLevelAuthTime = new Date(JSONObjectUtils.getLong(jsonObject, "auth_time") * 1000L);
218                }
219
220                ACR topLevelACR = null;
221
222                if (jsonObject.containsKey("acr")) {
223                        topLevelACR = new ACR(JSONObjectUtils.getString(jsonObject, "acr"));
224                }
225
226                List<AMR> topLevelAMRList = null;
227
228                if (jsonObject.containsKey("amr")) {
229                        String[] sa = JSONObjectUtils.getStringArray(jsonObject, "amr");
230                        topLevelAMRList = new ArrayList<>(sa.length);
231                        for (String s: sa) {
232                                topLevelAMRList.add(new AMR(s));
233                        }
234                }
235
236                IDTokenSpec idSpec = new IDTokenSpec();
237
238                if (jsonObject.containsKey("id_token")) {
239
240                        idSpec = IDTokenSpec.parse(JSONObjectUtils.getJSONObject(jsonObject, "id_token"));
241
242                        // Override with top-level params where set
243                        idSpec = new IDTokenSpec(
244                                idSpec.issue(),
245                                idSpec.getLifetime(),
246                                topLevelAuthTime != null ? topLevelAuthTime : idSpec.getAuthTime(),
247                                topLevelACR != null ? topLevelACR : idSpec.getACR(),
248                                topLevelAMRList != null ? topLevelAMRList : idSpec.getAMRList(),
249                                idSpec.getImpersonatedSubject());
250                }
251
252                ClaimsSpec clSpec = ClaimsSpec.parse(jsonObject);
253
254                return new SubjectAuthorization(
255                        sub,
256                        basicAuthz.getScope(),
257                        basicAuthz.getAccessTokenSpec(),
258                        idSpec,
259                        clSpec,
260                        basicAuthz.getData());
261        }
262
263
264        /**
265         * Parses a subject authorisation from the specified JSON object
266         * string.
267         *
268         * @param json The JSON object string to parse. Must not be
269         *             {@code null}.
270         *
271         * @return The subject authorisation.
272         *
273         * @throws ParseException If parsing failed.
274         */
275        public static SubjectAuthorization parse(final String json)
276                throws ParseException {
277
278                return parse(JSONObjectUtils.parse(json));
279        }
280}