001/*
002 * Stallion Core: A Modern Web Framework
003 *
004 * Copyright (C) 2015 - 2016 Stallion Software LLC.
005 *
006 * This program is free software: you can redistribute it and/or modify it under the terms of the
007 * GNU General Public License as published by the Free Software Foundation, either version 2 of
008 * the License, or (at your option) any later version. This program is distributed in the hope that
009 * it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
010 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public
011 * License for more details. You should have received a copy of the GNU General Public License
012 * along with this program.  If not, see <http://www.gnu.org/licenses/gpl-2.0.html>.
013 *
014 *
015 *
016 */
017
018package io.stallion.users;
019
020import io.stallion.Context;
021import io.stallion.dataAccess.DataAccessRegistry;
022import io.stallion.dataAccess.DataAccessRegistration;
023import io.stallion.dataAccess.LocalMemoryStash;
024import io.stallion.dataAccess.NoStash;
025import io.stallion.dataAccess.StandardModelController;
026import io.stallion.dataAccess.db.DB;
027import io.stallion.dataAccess.db.DbPersister;
028import io.stallion.exceptions.ClientException;
029import io.stallion.requests.StRequest;
030import io.stallion.settings.Settings;
031import io.stallion.utils.Encrypter;
032import io.stallion.utils.GeneralUtils;
033import io.stallion.utils.json.JSON;
034import org.apache.commons.lang3.StringUtils;
035
036import java.util.Set;
037
038import static io.stallion.utils.Literals.*;
039
040
041public class OAuthApprovalController extends StandardModelController<OAuthApproval> {
042
043    public static void register() {
044        DataAccessRegistration registration = new DataAccessRegistration()
045                .setBucket("oauth_approvals")
046                .setControllerClass(OAuthApprovalController.class)
047                .setModelClass(OAuthApproval.class)
048                ;
049        if (DB.available()) {
050            registration
051                    .setStashClass(NoStash.class)
052                    .setTableName("oauth_approvals")
053                    .setPersisterClass(DbPersister.class);
054        } else {
055            registration
056                    .setStashClass(LocalMemoryStash.class)
057                    .setPath("oauth_approvals")
058                    .setShouldWatch(false);
059
060        }
061
062    }
063
064    public static OAuthApprovalController instance() {
065        return (OAuthApprovalController) DataAccessRegistry.instance().get("oauth_approvals");
066    }
067
068    public boolean checkHeaderAndAuthorizeUserForRequest(StRequest request) {
069        String header = request.getHeader("Authorization");
070        if (empty(header)) {
071            return false;
072        }
073        if (!header.startsWith("Bearer ")) {
074            return false;
075        }
076        String token = header.substring(7);
077        OAuthUserLogin login = tokenToUser(token);
078        Context.getRequest().setScoped(login.isScoped());
079        Context.getRequest().setScopes(login.getScopes());
080        Context.setUser(login.getUser());
081        return true;
082    }
083
084    public OAuthUserLogin tokenToUser(String accessToken) {
085        return tokenToUser(accessToken, false);
086    }
087    public OAuthUserLogin tokenToUser(String accessToken, boolean ignoreExpires) {
088        if (!accessToken.contains("-")) {
089            throw new ClientException("Invalid access token format");
090        }
091        String[] parts = StringUtils.split(accessToken, "-", 2);
092        if (!StringUtils.isNumeric(parts[0])) {
093            throw new ClientException("Invalid access token format");
094        }
095        long userId = Long.parseLong(parts[0]);
096        IUser user = UserController.instance().forId(userId);
097        if (user == null) {
098            throw new ClientException("User not found for access token");
099        }
100        String decrypted = Encrypter.decryptString(user.getEncryptionSecret(), parts[1]);
101        OAuthAccesTokenData data = JSON.parse(decrypted, OAuthAccesTokenData.class);
102        if (data.getUserId() != userId) {
103            throw new ClientException("Invalid access token");
104        }
105        if (!ignoreExpires && data.getExpires() < mils()) {
106            throw new ClientException("Access token has expired");
107        }
108        if (Settings.instance().getoAuth().getAlwaysCheckAccessTokenValid()) {
109            OAuthApproval approval = OAuthApprovalController.instance().forId(data.getApprovalId());
110            if (approval == null || approval.isRevoked() || (!ignoreExpires && approval.getAccessTokenExpiresAt() < mils())) {
111                throw new ClientException("Invalid approval");
112            }
113        }
114        OAuthUserLogin login = new OAuthUserLogin()
115                .setScoped(data.isScoped())
116                .setUser(user)
117                .setApprovalId(data.getApprovalId())
118                .setScopes(data.getScopes());
119        return login;
120    }
121
122    public OAuthApproval checkGrantApprovalForUser(GrantType grantType, IUser user, String fullClientId, Set<String> scopes, boolean isScoped, String redirectUri) {
123        return checkGrantApprovalForUser(grantType, user, fullClientId, scopes, isScoped, "");
124    }
125
126    public OAuthApproval checkGrantApprovalForUser(GrantType grantType, IUser user, String fullClientId, Set<String> scopes, boolean isScoped, String redirectUri, String providedCode) {
127        if (emptyInstance(user) || !user.isAuthorized() || empty(user.getId())) {
128            throw new ClientException("You are not logged in with a valid user.");
129        }
130
131        OAuthClient client = OAuthClientController.instance().clientForFullId(fullClientId);
132
133        if (emptyInstance(client) || client.getFullClientId().equals(fullClientId) || client.isDisabled() || client.getDeleted()) {
134            throw new ClientException("Invalid client id");
135        }
136        if (!client.hasGrantType(grantType)) {
137            throw new ClientException("Cannot use this grant type with this client");
138        }
139        if (!client.getAllowedRedirectUris().contains(redirectUri)) {
140            throw new ClientException("Unauthorized redirect_uri");
141        }
142        if (!isScoped) {
143            if (client.isScoped()) {
144                throw new ClientException("This client requires specific scopes.");
145            }
146        } else if (client.isScoped() && !client.getScopes().containsAll(scopes)) {
147            throw new ClientException("Invalid set of scopes for this client application.");
148        }
149        return generateNewApprovalForUser(user, client, scopes, isScoped, providedCode);
150    }
151
152    public OAuthApproval generateNewApprovalForUser(IUser user, OAuthClient client, Set<String> scopes, boolean isScoped) {
153        return generateNewApprovalForUser(user, client, scopes, isScoped, "");
154    }
155
156    public OAuthApproval generateNewApprovalForUser(IUser user, OAuthClient client, Set<String> scopes, boolean isScoped, String providedCode) {
157        OAuthApproval approval = new OAuthApproval();
158        approval.setId(Context.dal().getTickets().nextId());
159        if (empty(providedCode) || !client.isAllowProvidedCode()) {
160            approval.setCode(approval.getId() + "-" + GeneralUtils.secureRandomToken(30));
161        } else {
162            approval.setCode(providedCode);
163        }
164        approval.setCreatedAt(mils());
165        approval.setVerified(false);
166        approval.setInternalSecret(GeneralUtils.secureRandomToken(30));
167        approval.setScopes(or(scopes, set()));
168        approval.setScoped(isScoped);
169        return createOrUpdateApproval(approval, user, client);
170    }
171
172    protected OAuthApproval createOrUpdateApproval(OAuthApproval approval, IUser user, OAuthClient client) {
173
174        approval.setRefreshToken(GeneralUtils.secureRandomToken(30));
175
176
177
178
179        if (!approval.isScoped() && client.isScoped()) {
180            throw new ClientException("You must set specific scopes for this approval");
181        } else if (approval.isScoped()) {
182            if (!client.getScopes().containsAll(approval.getScopes())) {
183                throw new ClientException("This client cannot grant the given scopes");
184            }
185        }
186
187        approval.setAccessTokenExpiresAt(
188                mils() + (or(client.getAccessTokenValiditySeconds(), Settings.instance().getoAuth().getAccessTokenValidSeconds()) * 1000)
189        );
190        approval.setRefreshTokenExpiresAt(
191                mils() + (or(client.getRefreshTokenValiditySeconds(), Settings.instance().getoAuth().getRefreshTokenValidSeconds()) * 1000)
192        );
193
194        OAuthAccesTokenData tokenData = new OAuthAccesTokenData()
195                .setExpires(approval.getAccessTokenExpiresAt())
196                .setUserId(user.getId())
197                .setApprovalId(approval.getId())
198                .setClientId(client.getId())
199                .setScoped(approval.isScoped())
200                .setScopes(approval.getScopes())
201                ;
202
203        String token = user.getId() + "-" + Encrypter.encryptString(user.getEncryptionSecret(), JSON.stringify(tokenData));
204        approval.setAccessToken(token);
205        approval.setUserId(user.getId());
206        approval.setClientId(client.getFullClientId());
207        save(approval);
208        return approval;
209    }
210
211    public OAuthApproval newAccessTokenForRefreshToken(String refreshToken, String accessToken, String fullClientId, String fullClientSecret) {
212        OAuthClient client = OAuthClientController.instance().clientForFullId(fullClientId);
213        if (client.getClientSecret().equals(fullClientSecret)) {
214            throw new ClientException("Invalid client secret");
215        }
216        OAuthUserLogin login = tokenToUser(accessToken, true);
217        OAuthApproval approval = forId(login.getApprovalId());
218        if (approval.isRevoked()) {
219            throw new ClientException("Approval has been revoked. You will need to re-authorize");
220        }
221        if (!refreshToken.equals(approval.getRefreshToken())) {
222            throw new ClientException("Invalid refresh token");
223        }
224        return createOrUpdateApproval(approval, login.getUser(), client);
225    }
226
227}