All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.aoindustries.aoserv.client.account.User Maven / Gradle / Ivy

/*
 * aoserv-client - Java client for the AOServ Platform.
 * Copyright (C) 2000-2013, 2016, 2017, 2018, 2019, 2020  AO Industries, Inc.
 *     [email protected]
 *     7262 Bull Pen Cir
 *     Mobile, AL 36695
 *
 * This file is part of aoserv-client.
 *
 * aoserv-client is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * aoserv-client is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with aoserv-client.  If not, see .
 */
package com.aoindustries.aoserv.client.account;

import com.aoindustries.aoserv.client.CannotRemoveReason;
import com.aoindustries.aoserv.client.Disablable;
import com.aoindustries.aoserv.client.Removable;
import static com.aoindustries.aoserv.client.account.ApplicationResources.accessor;
import com.aoindustries.aoserv.client.billing.Package;
import com.aoindustries.aoserv.client.linux.Group;
import com.aoindustries.aoserv.client.linux.PosixPath;
import com.aoindustries.aoserv.client.linux.Shell;
import com.aoindustries.aoserv.client.linux.User.Gecos;
import com.aoindustries.aoserv.client.linux.UserType;
import com.aoindustries.aoserv.client.password.PasswordChecker;
import com.aoindustries.aoserv.client.password.PasswordProtected;
import com.aoindustries.aoserv.client.schema.AoservProtocol;
import com.aoindustries.aoserv.client.schema.Table;
import com.aoindustries.dto.DtoFactory;
import com.aoindustries.io.FastExternalizable;
import com.aoindustries.io.FastObjectInput;
import com.aoindustries.io.FastObjectOutput;
import com.aoindustries.io.stream.StreamableInput;
import com.aoindustries.io.stream.StreamableOutput;
import com.aoindustries.net.Email;
import com.aoindustries.util.Internable;
import com.aoindustries.validation.InvalidResult;
import com.aoindustries.validation.ValidResult;
import com.aoindustries.validation.ValidationException;
import com.aoindustries.validation.ValidationResult;
import java.io.IOException;
import java.io.InvalidObjectException;
import java.io.ObjectInput;
import java.io.ObjectInputValidation;
import java.io.ObjectOutput;
import java.sql.Date;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

/**
 * Each Username is unique across all systems and must
 * be allocated to a Package before use in any of the
 * account types.
 *
 * @see  Administrator
 * @see  User
 * @see  User
 * @see  User
 *
 * @author  AO Industries, Inc.
 */
final public class User extends CachedObjectUserNameKey implements PasswordProtected, Removable, Disablable {

	/**
	 * Represents the most general form of a user name.  This is has the fewest constraints; other types of accounts
	 * constrain this further.  User names must:
	 * 
    *
  • Be non-null
  • *
  • Be non-empty
  • *
  • Be between 1 and 255 characters
  • *
  • Must start with [a-z]
  • *
  • Uses only ASCII 0x21 through 0x7f, excluding {@code space , : ( ) [ ] ' " | & ; A-Z \ /}
  • *
  • * If contains any @ symbol, must also be a valid email address. Please note that the * reverse is not implied - email addresses may exist that are not valid user ids. *
  • *
*

* TODO: Should we allow Unicode here, since we now have a more restrictive {@link com.aoindustries.aoserv.client.linux.User.Name} for shell accounts? *

* * @see com.aoindustries.aoserv.client.linux.User.Name * @see com.aoindustries.aoserv.client.mysql.User.Name * @see com.aoindustries.aoserv.client.postgresql.User.Name * * @author AO Industries, Inc. */ public static class Name implements Comparable, FastExternalizable, ObjectInputValidation, DtoFactory, Internable { public static final int MAX_LENGTH = 255; /** * Validates a {@link User} name. */ public static ValidationResult validate(String name) { if(name==null) return new InvalidResult(accessor, "User.Name.validate.isNull"); int len = name.length(); if(len==0) return new InvalidResult(accessor, "User.Name.validate.isEmpty"); if(len > MAX_LENGTH) return new InvalidResult(accessor, "User.Name.validate.tooLong", MAX_LENGTH, len); // The first character must be [a-z] char ch = name.charAt(0); if(ch < 'a' || ch > 'z') return new InvalidResult(accessor, "User.Name.validate.startAToZ"); // The rest may have additional characters boolean hasAt = false; for (int c = 1; c < len; c++) { ch = name.charAt(c); if(ch==' ') return new InvalidResult(accessor, "User.Name.validate.noSpace"); if(ch<=0x21 || ch>0x7f) return new InvalidResult(accessor, "User.Name.validate.specialCharacter"); if(ch>='A' && ch<='Z') return new InvalidResult(accessor, "User.Name.validate.noCapital"); switch(ch) { case ',' : return new InvalidResult(accessor, "User.Name.validate.comma"); case ':' : return new InvalidResult(accessor, "User.Name.validate.colon"); case '(' : return new InvalidResult(accessor, "User.Name.validate.leftParen"); case ')' : return new InvalidResult(accessor, "User.Name.validate.rightParen"); case '[' : return new InvalidResult(accessor, "User.Name.validate.leftSquare"); case ']' : return new InvalidResult(accessor, "User.Name.validate.rightSquare"); case '\'' : return new InvalidResult(accessor, "User.Name.validate.apostrophe"); case '"' : return new InvalidResult(accessor, "User.Name.validate.quote"); case '|' : return new InvalidResult(accessor, "User.Name.validate.verticalBar"); case '&' : return new InvalidResult(accessor, "User.Name.validate.ampersand"); case ';' : return new InvalidResult(accessor, "User.Name.validate.semicolon"); case '\\' : return new InvalidResult(accessor, "User.Name.validate.backslash"); case '/' : return new InvalidResult(accessor, "User.Name.validate.slash"); case '@' : hasAt = true; break; } } if(hasAt) { // Must also be a valid email address ValidationResult result = Email.validate(name); if(!result.isValid()) return result; } return ValidResult.getInstance(); } private static final ConcurrentMap interned = new ConcurrentHashMap<>(); /** * @param name when {@code null}, returns {@code null} */ public static Name valueOf(String name) throws ValidationException { if(name == null) return null; //Name existing = interned.get(name); //return existing!=null ? existing : new Name(name); return new Name(name, true); } /* public static Name valueOfInterned(String name) throws ValidationException { Name existing = interned.get(name); return existing!=null ? existing : new Name(name).intern(); }*/ protected String name; protected Name(String name, boolean validate) throws ValidationException { this.name = name; if(validate) validate(); } /** * @param name Does not validate, should only be used with a known valid value. */ protected Name(String name) { ValidationResult result; assert (result = validate(name)).isValid() : result.toString(); this.name = name; } protected void validate() throws ValidationException { ValidationResult result = validate(name); if(!result.isValid()) throw new ValidationException(result); } @Override final public boolean equals(Object O) { return O!=null // TODO: These nulls checks are unnecessary given instanceof that follows && O instanceof Name && name.equals(((Name)O).name) ; } @Override final public int hashCode() { return name.hashCode(); } @Override final public int compareTo(Name other) { return this==other ? 0 : name.compareTo(other.name); } @Override final public String toString() { return name; } /** * Interns this name much in the same fashion as String.intern(). *

* Because this has subtypes, two {@link Name} that are {@link #equals(java.lang.Object)} * may not necessarily return the same instance object after interning. Thus, * unless you know objects are of the same class, {@link #equals(java.lang.Object)} should * still be used for equality check instead of the {@code obj1 == obj2} shortcut. *

*

* To more efficiently check post-interned equivalence, one could also do * {@code obj1 == obj2 || (obj1.getClass() != obj2.getClass() && obj1.equals(obj2))}, * but is it worth it? *

*

* And then if we abuse the fact that interned user ids have an interned name, one * could check equivalence of post-interned user ids as {@code obj1.getId() == obj2.getId()}, * but once again, is it worth it? Just call {@link #equals(java.lang.Object)}. *

* * @see String#intern() */ @Override public Name intern() { Name existing = interned.get(name); if(existing==null) { String internedId = name.intern(); Name addMe = (name == internedId) ? this : new Name(internedId); existing = interned.putIfAbsent(internedId, addMe); if(existing==null) existing = addMe; } return existing; } @Override public com.aoindustries.aoserv.client.dto.UserName getDto() { return new com.aoindustries.aoserv.client.dto.UserName(name); } // private static final long serialVersionUID = -837866431257794645L; public Name() { } @Override public long getSerialVersionUID() { return serialVersionUID; } @Override public void writeExternal(ObjectOutput out) throws IOException { FastObjectOutput fastOut = FastObjectOutput.wrap(out); try { fastOut.writeFastUTF(name); } finally { fastOut.unwrap(); } } @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { if(name!=null) throw new IllegalStateException(); FastObjectInput fastIn = FastObjectInput.wrap(in); try { name = fastIn.readFastUTF(); } finally { fastIn.unwrap(); } } @Override public void validateObject() throws InvalidObjectException { try { validate(); } catch(ValidationException err) { InvalidObjectException newErr = new InvalidObjectException(err.getMessage()); newErr.initCause(err); throw newErr; } } // } static final int COLUMN_USERNAME=0, COLUMN_PACKAGE=1 ; static final String COLUMN_USERNAME_name = "username"; Account.Name packageName; int disable_log; public void addAdministrator( String name, String title, Date birthday, boolean isPrivate, String workPhone, String homePhone, String cellPhone, String fax, Email email, String address1, String address2, String city, String state, String country, String zip, boolean enableEmailSupport ) throws IOException, SQLException { table.getConnector().getAccount().getAdministrator().addAdministrator( this, name, title, birthday, isPrivate, workPhone, homePhone, cellPhone, fax, email, address1, address2, city, state, country, zip, enableEmailSupport ); } public void addLinuxAccount( Group primaryGroup, Gecos name, Gecos office_location, Gecos office_phone, Gecos home_phone, UserType typeObject, Shell shellObject ) throws IOException, SQLException { addLinuxAccount( primaryGroup.getName(), name, office_location, office_phone, home_phone, typeObject.getName(), shellObject.getPath() ); } public void addLinuxAccount( Group.Name primaryGroup, Gecos name, Gecos office_location, Gecos office_phone, Gecos home_phone, String type, PosixPath shell ) throws IOException, SQLException { table.getConnector().getLinux().getUser().addLinuxAccount( this, primaryGroup, name, office_location, office_phone, home_phone, type, shell ); } public void addMySQLUser() throws IOException, SQLException { try { table.getConnector().getMysql().getUser().addMySQLUser(com.aoindustries.aoserv.client.mysql.User.Name.valueOf(pkey.toString())); } catch(ValidationException e) { throw new SQLException(e); } } public void addPostgresUser() throws IOException, SQLException { try { table.getConnector().getPostgresql().getUser().addPostgresUser(com.aoindustries.aoserv.client.postgresql.User.Name.valueOf(pkey.toString())); } catch(ValidationException e) { throw new SQLException(e); } } @Override public int arePasswordsSet() throws IOException, SQLException { // Build the array of objects List pps=new ArrayList<>(); Administrator ba=getAdministrator(); if(ba!=null) pps.add(ba); com.aoindustries.aoserv.client.linux.User la=getLinuxAccount(); if(la!=null) pps.add(la); com.aoindustries.aoserv.client.mysql.User mu=getMySQLUser(); if(mu!=null) pps.add(mu); com.aoindustries.aoserv.client.postgresql.User pu=getPostgresUser(); if(pu!=null) pps.add(pu); return User.groupPasswordsSet(pps); } @Override public boolean canDisable() throws IOException, SQLException { if(disable_log!=-1) return false; com.aoindustries.aoserv.client.linux.User la=getLinuxAccount(); if(la!=null && !la.isDisabled()) return false; com.aoindustries.aoserv.client.mysql.User mu=getMySQLUser(); if(mu!=null && !mu.isDisabled()) return false; com.aoindustries.aoserv.client.postgresql.User pu=getPostgresUser(); if(pu!=null && !pu.isDisabled()) return false; return true; } @Override public boolean canEnable() throws SQLException, IOException { DisableLog dl=getDisableLog(); if(dl==null) return false; else return dl.canEnable() && !getPackage().isDisabled(); } /** * Checks the strength of a password as used by this Username. */ @Override public List checkPassword(String password) throws IOException, SQLException { Administrator ba=getAdministrator(); if(ba!=null) { List results=ba.checkPassword(password); if(PasswordChecker.hasResults(results)) return results; } com.aoindustries.aoserv.client.linux.User la=getLinuxAccount(); if(la!=null) { List results=la.checkPassword(password); if(PasswordChecker.hasResults(results)) return results; } com.aoindustries.aoserv.client.mysql.User mu=getMySQLUser(); if(mu!=null) { List results=mu.checkPassword(password); if(PasswordChecker.hasResults(results)) return results; } com.aoindustries.aoserv.client.postgresql.User pu=getPostgresUser(); if(pu!=null) { List results=pu.checkPassword(password); if(PasswordChecker.hasResults(results)) return results; } return PasswordChecker.getAllGoodResults(); } @Override public void disable(DisableLog dl) throws IOException, SQLException { table.getConnector().requestUpdateIL(true, AoservProtocol.CommandID.DISABLE, Table.TableID.USERNAMES, dl.getPkey(), pkey); } @Override public void enable() throws IOException, SQLException { table.getConnector().requestUpdateIL(true, AoservProtocol.CommandID.ENABLE, Table.TableID.USERNAMES, pkey); } // TODO: See where used, and favor direct lookup in other tables: public Administrator getAdministrator() throws IOException, SQLException { return table.getConnector().getAccount().getAdministrator().get(pkey); } @Override protected Object getColumnImpl(int i) { switch(i) { case COLUMN_USERNAME: return pkey; case COLUMN_PACKAGE: return packageName; case 2: return disable_log==-1?null:disable_log; default: throw new IllegalArgumentException("Invalid index: " + i); } } @Override public boolean isDisabled() { return disable_log!=-1; } @Override public DisableLog getDisableLog() throws SQLException, IOException { if(disable_log==-1) return null; DisableLog obj=table.getConnector().getAccount().getDisableLog().get(disable_log); if(obj==null) throw new SQLException("Unable to find DisableLog: "+disable_log); return obj; } // TODO: See where used, and favor direct lookup in other tables: public com.aoindustries.aoserv.client.linux.User getLinuxAccount() throws IOException, SQLException { String username = pkey.toString(); if(com.aoindustries.aoserv.client.linux.User.Name.validate(username).isValid()) { try { return table.getConnector().getLinux().getUser().get(com.aoindustries.aoserv.client.linux.User.Name.valueOf(username)); } catch(ValidationException e) { throw new AssertionError("Already validated", e); } } else { return null; } } // TODO: See where used, and favor direct lookup in other tables: public com.aoindustries.aoserv.client.mysql.User getMySQLUser() throws IOException, SQLException { String username = pkey.toString(); if(com.aoindustries.aoserv.client.mysql.User.Name.validate(username).isValid()) { try { return table.getConnector().getMysql().getUser().get(com.aoindustries.aoserv.client.mysql.User.Name.valueOf(username)); } catch(ValidationException e) { throw new AssertionError("Already validated", e); } } else { return null; } } public Account.Name getPackage_name() { return packageName; } public Package getPackage() throws SQLException, IOException { Package packageObject=table.getConnector().getBilling().getPackage().get(packageName); if (packageObject == null) throw new SQLException("Unable to find Package: " + packageName); return packageObject; } // TODO: See where used, and favor direct lookup in other tables: public com.aoindustries.aoserv.client.postgresql.User getPostgresUser() throws IOException, SQLException { String username = pkey.toString(); if(com.aoindustries.aoserv.client.postgresql.User.Name.validate(username).isValid()) { try { return table.getConnector().getPostgresql().getUser().get(com.aoindustries.aoserv.client.postgresql.User.Name.valueOf(username)); } catch(ValidationException e) { throw new AssertionError("Already validated", e); } } else { return null; } } @Override public Table.TableID getTableID() { return Table.TableID.USERNAMES; } public User.Name getUsername() { return pkey; } public static int groupPasswordsSet(List pps) throws IOException, SQLException { int totalAll=0; for(int c=0;c> getCannotRemoveReasons() throws SQLException, IOException { List> reasons = new ArrayList<>(); com.aoindustries.aoserv.client.linux.User la=getLinuxAccount(); if(la!=null) reasons.add(new CannotRemoveReason<>("Used by Linux account: "+la.getUsername().getUsername(), la)); Administrator ba=getAdministrator(); if(ba!=null) reasons.add(new CannotRemoveReason<>("Used by Administrator: "+ba.getUsername().getUsername(), ba)); com.aoindustries.aoserv.client.mysql.User mu=getMySQLUser(); if(mu!=null) reasons.add(new CannotRemoveReason<>("Used by MySQL user: "+mu.getUsername().getUsername(), mu)); com.aoindustries.aoserv.client.postgresql.User pu=getPostgresUser(); if(pu!=null) reasons.add(new CannotRemoveReason<>("Used by PostgreSQL user: "+pu.getUsername().getUsername(), pu)); return reasons; } @Override public void remove() throws IOException, SQLException { table.getConnector().requestUpdateIL( true, AoservProtocol.CommandID.REMOVE, Table.TableID.USERNAMES, pkey ); } @Override public void setPassword(String password) throws SQLException, IOException { Administrator ba=getAdministrator(); if(ba!=null) ba.setPassword(password); com.aoindustries.aoserv.client.linux.User la=getLinuxAccount(); if(la!=null) la.setPassword(password); com.aoindustries.aoserv.client.mysql.User mu=getMySQLUser(); if(mu!=null) mu.setPassword(password); com.aoindustries.aoserv.client.postgresql.User pu=getPostgresUser(); if(pu!=null) pu.setPassword(password); } @Override public boolean canSetPassword() throws IOException, SQLException { if(disable_log!=-1) return false; Administrator ba=getAdministrator(); if(ba!=null && !ba.canSetPassword()) return false; com.aoindustries.aoserv.client.linux.User la=getLinuxAccount(); if(la!=null && !la.canSetPassword()) return false; com.aoindustries.aoserv.client.mysql.User mu=getMySQLUser(); if(mu!=null && !mu.canSetPassword()) return false; com.aoindustries.aoserv.client.postgresql.User pu=getPostgresUser(); if(pu!=null && !pu.canSetPassword()) return false; return ba!=null || la!=null || mu!=null || pu!=null; } @Override public void write(StreamableOutput out, AoservProtocol.Version protocolVersion) throws IOException { out.writeUTF(pkey.toString()); out.writeUTF(packageName.toString()); out.writeCompressedInt(disable_log); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy