001/*
002 * (C) Copyright 2014 Nuxeo SA (http://nuxeo.com/) and others.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 *
016 * Contributors:
017 *     Arnaud Kervern
018 */
019package org.nuxeo.ecm.platform.oauth2.request;
020
021import static org.apache.commons.lang.StringUtils.isBlank;
022import static org.nuxeo.ecm.platform.ui.web.auth.oauth2.NuxeoOAuth2Filter.ERRORS.invalid_request;
023import static org.nuxeo.ecm.platform.ui.web.auth.oauth2.NuxeoOAuth2Filter.ERRORS.server_error;
024import static org.nuxeo.ecm.platform.ui.web.auth.oauth2.NuxeoOAuth2Filter.ERRORS.unauthorized_client;
025import static org.nuxeo.ecm.platform.ui.web.auth.oauth2.NuxeoOAuth2Filter.ERRORS.unsupported_response_type;
026
027import java.io.UnsupportedEncodingException;
028import java.util.Date;
029import java.util.Iterator;
030import java.util.Map;
031import java.util.concurrent.ConcurrentHashMap;
032
033import javax.servlet.http.HttpServletRequest;
034
035import org.apache.commons.lang.RandomStringUtils;
036import org.apache.commons.logging.Log;
037import org.apache.commons.logging.LogFactory;
038import org.nuxeo.ecm.directory.DirectoryException;
039import org.nuxeo.ecm.platform.oauth2.clients.ClientRegistry;
040import org.nuxeo.runtime.api.Framework;
041
042/**
043 * @author <a href="mailto:ak@nuxeo.com">Arnaud Kervern</a>
044 * @since 5.9.2
045 */
046public class AuthorizationRequest extends Oauth2Request {
047    private static final Log log = LogFactory.getLog(AuthorizationRequest.class);
048
049    protected static Map<String, AuthorizationRequest> requests = new ConcurrentHashMap<>();
050
051    protected String responseType;
052
053    protected String scope;
054
055    protected String state;
056
057    protected String sessionId;
058
059    protected Date creationDate;
060
061    protected String authorizationCode;
062
063    protected String authorizationKey;
064
065    protected String username;
066
067    public static final String RESPONSE_TYPE = "response_type";
068
069    public static final String SCOPE = "scope";
070
071    public static final String STATE = "state";
072
073    public AuthorizationRequest() {
074    }
075
076    public AuthorizationRequest(HttpServletRequest request) {
077        super(request);
078        responseType = request.getParameter(RESPONSE_TYPE);
079
080        scope = request.getParameter(SCOPE);
081        state = request.getParameter(STATE);
082        sessionId = request.getSession(true).getId();
083
084        creationDate = new Date();
085        authorizationKey = RandomStringUtils.random(6, true, false);
086    }
087
088    public String checkError() {
089        // Check mandatory fields
090        if (isBlank(responseType) || isBlank(clientId) || isBlank(redirectUri)) {
091            return invalid_request.toString();
092        }
093
094        // Check if client exists
095        try {
096            ClientRegistry registry = Framework.getLocalService(ClientRegistry.class);
097            if (!registry.hasClient(clientId)) {
098                return unauthorized_client.toString();
099            }
100        } catch (DirectoryException e) {
101            log.warn(e, e);
102            return server_error.toString();
103        }
104
105        // Check request type
106        if (!"code".equals(responseType)) {
107            return unsupported_response_type.toString();
108        }
109        return null;
110    }
111
112    public boolean isExpired() {
113        // RFC 4.1.2, Authorization code lifetime is 10
114        return new Date().getTime() - creationDate.getTime() > 10 * 60 * 1000;
115    }
116
117    public boolean isValidState(HttpServletRequest request) {
118        return isBlank(getState()) || request.getParameter(STATE).equals(getState());
119    }
120
121    public String getUsername() {
122        return username;
123    }
124
125    public String getResponseType() {
126        return responseType;
127    }
128
129    public String getScope() {
130        return scope;
131    }
132
133    public String getState() {
134        return state;
135    }
136
137    public String getAuthorizationCode() {
138        if (isBlank(authorizationCode)) {
139            authorizationCode = RandomStringUtils.random(10, true, true);
140        }
141        return authorizationCode;
142    }
143
144    public String getAuthorizationKey() {
145        return authorizationKey;
146    }
147
148    private static void deleteExpiredRequests() {
149        Iterator<AuthorizationRequest> iterator = requests.values().iterator();
150        AuthorizationRequest req;
151        while (iterator.hasNext() && (req = iterator.next()) != null) {
152            if (req.isExpired()) {
153                requests.remove(req.sessionId);
154            }
155        }
156    }
157
158    public static AuthorizationRequest from(HttpServletRequest request) throws UnsupportedEncodingException {
159        deleteExpiredRequests();
160
161        String sessionId = request.getSession(true).getId();
162        if (requests.containsKey(sessionId)) {
163            AuthorizationRequest authRequest = requests.get(sessionId);
164            if (!authRequest.isExpired() && authRequest.isValidState(request)) {
165                return authRequest;
166            }
167        }
168
169        AuthorizationRequest authRequest = new AuthorizationRequest(request);
170        requests.put(sessionId, authRequest);
171        return authRequest;
172    }
173
174    public static AuthorizationRequest fromCode(String authorizationCode) {
175        for (AuthorizationRequest auth : requests.values()) {
176            if (auth.authorizationCode != null && auth.authorizationCode.equals(authorizationCode)) {
177                if (auth.sessionId != null) {
178                    requests.remove(auth.sessionId);
179                }
180                return auth.isExpired() ? null : auth;
181            }
182        }
183        return null;
184    }
185
186    public void setUsername(String username) {
187        this.username = username;
188    }
189}