Coverage Summary for Class: SQLPasswordAuthenticationProvider (com.acciente.oacc.sql.internal)
Class | Class, % | Method, % | Line, % |
---|---|---|---|
SQLPasswordAuthenticationProvider | 100% (1/ 1) | 88.9% (16/ 18) | 86.9% (86/ 99) |
1 /*
2 * Copyright 2009-2018, Acciente LLC
3 *
4 * Acciente LLC licenses this file to you under the
5 * Apache License, Version 2.0 (the "License"); you
6 * may not use this file except in compliance with the
7 * License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in
12 * writing, software distributed under the License is
13 * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
14 * OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing
16 * permissions and limitations under the License.
17 */
18 package com.acciente.oacc.sql.internal;
19
20 import com.acciente.oacc.AuthenticationProvider;
21 import com.acciente.oacc.Credentials;
22 import com.acciente.oacc.IncorrectCredentialsException;
23 import com.acciente.oacc.InvalidCredentialsException;
24 import com.acciente.oacc.PasswordCredentials;
25 import com.acciente.oacc.Resource;
26 import com.acciente.oacc.UnsupportedCredentialsException;
27 import com.acciente.oacc.encryptor.PasswordEncryptor;
28 import com.acciente.oacc.sql.internal.persister.ResourcePasswordPersister;
29 import com.acciente.oacc.sql.internal.persister.SQLConnection;
30 import com.acciente.oacc.sql.internal.persister.SQLPasswordStrings;
31
32 import javax.sql.DataSource;
33 import java.io.Serializable;
34 import java.sql.Connection;
35 import java.sql.SQLException;
36
37 public class SQLPasswordAuthenticationProvider implements AuthenticationProvider, Serializable {
38 private static final long serialVersionUID = 2L;
39
40 // database
41 private transient DataSource dataSource;
42 private transient Connection connection;
43
44 // password encryptor
45 private final PasswordEncryptor passwordEncryptor;
46
47 // persisters
48 private final ResourcePasswordPersister resourcePasswordPersister;
49
50 // protected constructors/methods
51 protected SQLPasswordAuthenticationProvider(Connection connection,
52 String schemaName,
53 PasswordEncryptor passwordEncryptor) {
54 this(schemaName, passwordEncryptor);
55 this.connection = connection;
56 }
57
58 protected SQLPasswordAuthenticationProvider(DataSource dataSource,
59 String schemaName,
60 PasswordEncryptor passwordEncryptor) {
61 this(schemaName, passwordEncryptor);
62 this.dataSource = dataSource;
63 }
64
65 private SQLPasswordAuthenticationProvider(String schemaName,
66 PasswordEncryptor passwordEncryptor) {
67 this.passwordEncryptor = passwordEncryptor;
68
69 // generate all the SQLs the persisters need based on the database dialect
70 SQLPasswordStrings sqlPasswordStrings = SQLPasswordStrings.getSQLPasswordStrings(schemaName);
71
72 // setup persisters
73 resourcePasswordPersister = new ResourcePasswordPersister(sqlPasswordStrings);
74 }
75
76 /**
77 * Re-initializes the transient data source after deserialization.
78 * <p/>
79 * This method is only intended to be called after successful deserialization, in order to reset
80 * a transient data source to a database that was not serialized. If the method is called when a
81 * data source or connection has already been initialized, the method will throw an IllegalStateException.
82 *
83 * @param dataSource the database dataSource to be reset
84 * @throws IllegalStateException if a dataSource or connection is already set
85 */
86 protected void postDeserialize(DataSource dataSource) {
87 if (this.dataSource != null || this.connection != null) {
88 throw new IllegalStateException("Cannot re-initialize an already initialized SQLPasswordAuthenticationProvider");
89 }
90 this.dataSource = dataSource;
91 this.connection = null;
92 }
93
94 /**
95 * Re-initializes the transient connection after deserialization.
96 * <p/>
97 * This method is only intended to be called after successful deserialization, in order to reset
98 * a transient connection to a database that was not serialized. If the method is called when a
99 * data source or connection has already been initialized, the method will throw an IllegalStateException.
100 *
101 * @param connection the database connection to be reset
102 * @throws IllegalStateException if a dataSource or connection is already set
103 */
104 protected void postDeserialize(Connection connection) {
105 if (this.dataSource != null || this.connection != null) {
106 throw new IllegalStateException("Cannot re-initialize an already initialized SQLPasswordAuthenticationProvider");
107 }
108 this.dataSource = null;
109 this.connection = connection;
110 }
111
112 @Override
113 public void authenticate(Resource resource, Credentials credentials) {
114 assertCredentialSpecified(credentials);
115 assertSupportedCredentials(credentials);
116
117 final PasswordCredentials passwordCredentials = ((PasswordCredentials) credentials);
118
119 if (passwordCredentials.getPassword() == null) {
120 throw new InvalidCredentialsException("Password required, none specified");
121 }
122
123 SQLConnection connection = null;
124 try {
125 connection = getConnection();
126
127 __authenticate(connection, resource, passwordCredentials.getPassword());
128 }
129 finally {
130 closeConnection(connection);
131 }
132 }
133
134 @Override
135 public void authenticate(Resource resource) {
136 throw new UnsupportedOperationException(
137 "The built-in password authentication provider does not support authentication without credentials");
138 }
139
140 @Override
141 public Resource authenticate(Credentials credentials) {
142 throw new UnsupportedOperationException(
143 "The built-in password authentication provider does not support authentication using *only* credentials");
144 }
145
146 private void __authenticate(SQLConnection connection, Resource resource, char[] password) {
147 // first locate the resource
148 final String encryptedBoundPassword = resourcePasswordPersister.getEncryptedBoundPasswordByResourceId(connection, resource);
149
150 char[] plainBoundPassword = null;
151 try {
152 plainBoundPassword = PasswordUtils.computeBoundPassword(resource, password);
153
154 if (!passwordEncryptor.checkPassword(plainBoundPassword, encryptedBoundPassword)) {
155 throw new IncorrectCredentialsException("Invalid password for resource " + resource);
156 }
157 }
158 finally {
159 PasswordUtils.cleanPassword(plainBoundPassword);
160 }
161 }
162
163 @Override
164 public void validateCredentials(String resourceClassName, String domainName, Credentials credentials) {
165 if (credentials == null) {
166 // instead of a NullPointerException we explicitly throw the InvalidCredentialsException
167 // to distinguish from a programming error the indication that this implementation
168 // does not support null credentials
169 throw new InvalidCredentialsException("Credentials required, none specified");
170 }
171
172 assertSupportedCredentials(credentials);
173
174 final char[] password = ((PasswordCredentials) credentials).getPassword();
175
176 if (password == null) {
177 throw new InvalidCredentialsException("Password required, none specified");
178 }
179
180 if (password.length == 0) {
181 throw new InvalidCredentialsException("Password cannot be zero length");
182 }
183
184 if (isBlank(password)) {
185 throw new InvalidCredentialsException("Password cannot be blank");
186 }
187 }
188
189 @Override
190 public void setCredentials(Resource resource, Credentials credentials) {
191 assertCredentialSpecified(credentials);
192 assertSupportedCredentials(credentials);
193
194 final PasswordCredentials passwordCredentials = ((PasswordCredentials) credentials);
195
196 SQLConnection connection = null;
197 try {
198 connection = getConnection();
199
200 __setResourcePassword(connection,
201 resource,
202 passwordCredentials.getPassword());
203 }
204 finally {
205 closeConnection(connection);
206 }
207 }
208
209 @Override
210 public void deleteCredentials(Resource resource) {
211 SQLConnection connection = null;
212 try {
213 connection = getConnection();
214
215 resourcePasswordPersister.removeEncryptedBoundPasswordByResourceId(connection, resource);
216 }
217 finally {
218 closeConnection(connection);
219 }
220 }
221
222 private void __setResourcePassword(SQLConnection connection, Resource resource, char[] newPassword) {
223 char[] newBoundPassword = null;
224 try {
225 newBoundPassword = PasswordUtils.computeBoundPassword(resource, newPassword);
226 final String newEncryptedBoundPassword = passwordEncryptor.encryptPassword(newBoundPassword);
227 resourcePasswordPersister.setEncryptedBoundPasswordByResourceId(connection,
228 resource,
229 newEncryptedBoundPassword);
230 }
231 finally {
232 PasswordUtils.cleanPassword(newBoundPassword);
233 }
234 }
235
236 private void assertCredentialSpecified(Credentials credentials) {
237 if (credentials == null) {
238 throw new NullPointerException("Credentials required, none specified");
239 }
240 }
241
242 private void assertSupportedCredentials(Credentials credentials) {
243 if (!(credentials instanceof PasswordCredentials)) {
244 throw new UnsupportedCredentialsException(credentials.getClass());
245 }
246 }
247
248 private boolean isBlank(char[] charArray) {
249 for (char c : charArray) {
250 if (!Character.isWhitespace(c)) {
251 return false;
252 }
253 }
254 return true;
255 }
256
257 // private connection management helper methods
258
259 private SQLConnection getConnection() {
260 if (dataSource != null) {
261 try {
262 return new SQLConnection(dataSource.getConnection());
263 }
264 catch (SQLException e) {
265 throw new RuntimeException(e);
266 }
267 }
268 else if (connection != null) {
269 return new SQLConnection(connection);
270 }
271 else {
272 throw new IllegalStateException("Not initialized! No data source or connection - don't forget to re-initialize after deserialization!");
273 }
274 }
275
276 private void closeConnection(SQLConnection connection) {
277 // only close the connection if we got it from a pool, otherwise just leave the connection open
278 if (dataSource != null) {
279 if (connection != null) {
280 try {
281 connection.close();
282 }
283 catch (SQLException e) {
284 throw new RuntimeException(e);
285 }
286 }
287 }
288 }
289
290 }