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 }