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}