public class JwtTokenManager {

    private final JWTSigner jwtSigner;
    private final JWTSigner.Options jwtOptions;
    private final JWTVerifier jwtVerifier;
    private final ObjectMapper jsonMapper;

    public JwtTokenManager(final String jwtSecret, final int jwtExpirySeconds) {
        jwtSigner = new JWTSigner(jwtSecret);
        jwtVerifier = new JWTVerifier(jwtSecret);
        jwtOptions = new JWTSigner.Options().setExpirySeconds(jwtExpirySeconds);

        jsonMapper = new ObjectMapper();
        jsonMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
    }

    public JWTSigner getJwtSigner() {
        return jwtSigner;
    }

    public JWTSigner.Options getJwtOptions() {
        return jwtOptions;
    }

    public JWTVerifier getJwtVerifier() {
        return jwtVerifier;
    }

    @SuppressWarnings("unchecked")
    public Map<String, Object> toJWTClaim(final User user) {
        try {
            return jsonMapper.convertValue(user, Map.class);
        } catch (final IllegalArgumentException e) {
            throw new RuntimeException("Can't JSONify User, something's badly wrong", e);
        }
    }

    public User fromJWTClaim(final Map<String, Object> claim)
            throws IllegalArgumentException {
        final User user = jsonMapper.convertValue(claim, User.class);
        if (user.getId() == null) {
            throw new IllegalStateException("User from claim is missing id: " + claim);
        }
        if (user.getRoles() == null) {
            throw new IllegalStateException("User from claim is missing roles: " + claim);
        }
        if (user.getEmail() == null) {
            throw new IllegalStateException("User from claim is missing email: " + claim);
        }
        if (user.getDisplayName() == null) {
            throw new IllegalStateException("User from claim is missing displayName: " + claim);
        }
        return user;
    }

} public final class PasswordDigest {
    private final String digest;

    private PasswordDigest(final String digest) {
        this.digest = digest;
    }

    public String getDigest() {
        return digest;
    }

    public boolean checkPassword(final String passwordToCheck) {
        return BCrypt.checkpw(passwordToCheck, digest);
    }

    public static PasswordDigest fromDigest(final String digest) {
        return new PasswordDigest(digest);
    }

    public static PasswordDigest generateFromPassword(final int bcryptCost, final String password) {
        final String salt = BCrypt.gensalt(bcryptCost);
        return PasswordDigest.fromDigest(BCrypt.hashpw(password, salt));
    }
} public class PasswordManagementConfiguration {

    public static final int MIN_LENGTH = 8;
    public static final int MAX_LENGTH = 100;

    private int bcryptCost;

    @JsonProperty
    public int getBcryptCost() {
        return bcryptCost;
    }

    @JsonProperty
    public void setBcryptCost(final int bcryptCost) {
        this.bcryptCost = bcryptCost;
    }
} public enum Roles {
    PLAYER {
        @Override
        protected boolean canReadUser(final User principal, final UUID userId) {
            return userId.equals(principal.getId());
        }

        @Override
        protected boolean canWriteUser(final User principal, final UUID userId) {
            return userId.equals(principal.getId());
        }
    },
    RO_ADMIN {
        @Override
        protected boolean canReadUser(final User principal, final UUID userId) {
            return true;
        }

        @Override
        protected boolean canWriteUser(final User principal, final UUID userId) {
            return userId.equals(principal.getId());
        }
    },
    ADMIN {
        @Override
        protected boolean canReadUser(final User principal, final UUID userId) {
            return true;
        }

        @Override
        protected boolean canWriteUser(final User principal, final UUID userId) {
            return true;
        }
    };

    public String getName() {
        return name();
    }

    public boolean canRead(final Class<?> resourceClass, final User principal, final UUID resourceId) {
        if (resourceClass == User.class) {
            return canReadUser(principal, resourceId);
        }
        return false;
    }

    protected abstract boolean canReadUser(User principal, UUID userId);

    public boolean canWrite(final Class<?> resourceClass, final User principal, final UUID resourceId) {
        if (resourceClass == User.class) {
            return canWriteUser(principal, resourceId);
        } else if (resourceClass == Player.class) {
            // TODO: Implement this
            return false;
        }
        return false;
    }

    protected abstract boolean canWriteUser(User principal, UUID userId);
} public final class Exceptions {

    private Exceptions() {
        // util
    }

    public static WebApplicationException webAppException(
            final String message,
            final Response.Status status) {
        return webAppException(message, status, null);
    }

    public static WebApplicationException webAppException(
            final String message,
            final Response.Status status,
            final Throwable cause) {
        final APIError error = new APIError(message);
        final Response response = Response.status(status).entity(error).build();
        return new WebApplicationException(message, cause, response);
    }

} @RegisterMapper(DBUserMapper.class)
@RegisterArgumentFactory({UUIDArgumentFactory.class, PasswordDigestArgumentFactory.class})
public abstract class UsersDao {

    public Optional<DBUser> getUser(final UUID id) {
        return mergeUsers(getUserOrNull(id));
    }

    @SqlQuery("select users.*,userRoles.role "
            + "from users inner join userRoles on users.id=userRoles.userId "
            + "where users.id=:id "
            + "and users.deletedDate is null "
            + "and userRoles.deletedDate is null")
    protected abstract List<DBUser> getUserOrNull(@Bind("id") UUID id);

    public Optional<DBUser> findUserByEmail(final String email) {
        return mergeUsers(findUserByEmailOrNull(email));
    }

    @SqlQuery("select users.*,userRoles.role "
            + "from users inner join userRoles on users.id=userRoles.userId "
            + "where email=:email "
            + "and users.deletedDate is null "
            + "and userRoles.deletedDate is null")
    protected abstract List<DBUser> findUserByEmailOrNull(@Bind("email") String email);

    public Optional<DBUser> findUserByDisplayName(final String displayName) {
        return mergeUsers(findUserByDisplayNameOrNull(displayName));
    }

    @SqlQuery("select users.*,userRoles.role "
              + "from users inner join userRoles on users.id=userRoles.userId "
              + "where displayName=:displayName "
              + "and users.deletedDate is null "
              + "and userRoles.deletedDate is null")
    protected abstract List<DBUser> findUserByDisplayNameOrNull(@Bind("displayName") String displayName);

    @SqlUpdate("update users set lastLoginDate=current_timestamp() where id=:id and deletedDate is null")
    public abstract int updateLastLoginDate(@Bind("id") UUID id);

    @Transaction
    public void createUser(final DBUser user) {
        insertUser(user);
        for (final Roles role : user.getRoles()) {
            insertUserRole(UUID.randomUUID(), user, role);
        }
    }

    @SqlUpdate("insert into users (id, createdDate, modifiedDate, lastPasswordChangeDate, email, displayName, passwordDigest) values "
               + "(:id, current_timestamp(), current_timestamp(), current_timestamp(), :email, :displayName, :passwordDigest)")
    protected abstract void insertUser(@BindBean DBUser user);

    @SqlUpdate("insert into userRoles (id, userId, role, createdDate) values "
               + "(:id, :user.id, :role.name, current_timestamp())")
    protected abstract void insertUserRole(@Bind("id") UUID id, @BindBean("user") DBUser user, @BindBean("role") Roles role);

    @SqlUpdate("update users set email=:email, emailValidatedDate=null, modifiedDate=current_timestamp() where "
            + "id=:id and deletedDate is null")
    public abstract int updateEmail(@Bind("id") UUID id, @Bind("email") String email);

    @SqlUpdate("update users set displayName=:displayName, modifiedDate=current_timestamp() where "
            + "id=:id and deletedDate is null")
    public abstract int updateDisplayName(@Bind("id") String id, @Bind("displayName") String displayName);

    @SqlUpdate("update users set passwordDigest=:passwordDigest, lastPasswordChangeDate=current_timestamp(), "
            + "modifiedDate=current_timestamp() where "
            + "id=:id and deletedDate is null")
    public abstract int changePassword(@Bind("id") UUID id, @Bind("passwordDigest") PasswordDigest passwordDigest);

    @SqlUpdate("update users set emailValidatedDate=current_timestamp() where "
            + "id=:id and deletedDate is null")
    public abstract int flagEmailValidated(@BindBean DBUser user);

    @SqlUpdate("delete from users where id=:id and deletedDate is null")
    public abstract int deleteUser(@Bind("id") UUID id);


    private Optional<DBUser> mergeUsers(final List<DBUser> users) {
        // This is an elaborate workaround to get JDBI to do two things:
        // 1. Return more than 1 row (for users with multiple roles)
        // 2. Throw an exception if a query (on email or displayName) matches more than one user Id
        if (users.isEmpty()) {
            return Optional.empty();
        }

        final DBUser user = users.get(0);
        for (final DBUser otherUser : users) {
            user.getRoles().addAll(otherUser.getRoles());
        }

        return Optional.ofNullable(user);
    }

} public class DBUser {
    private UUID id;
    private Date createdDate;
    private Date modifiedDate;
    private Date deletedDate;
    private Date lastLoginDate;
    private Date lastPasswordChangeDate;
    private Date emailValidatedDate;
    private String email;
    private String displayName;
    private PasswordDigest passwordDigest;
    private Set<Roles> roles;

    // CHECKSTYLE.OFF: ParameterNumber
    public DBUser(final UUID id,
                  @Nullable final Date createdDate,
                  @Nullable final Date modifiedDate,
                  @Nullable final Date deletedDate,
                  @Nullable final Date lastLoginDate,
                  @Nullable final Date lastPasswordChangeDate,
                  @Nullable final Date emailValidatedDate,
                  final String email,
                  final String displayName,
                  final PasswordDigest passwordDigest,
                  final Set<Roles> roles) {
        // CHECKSTYLE.ON: ParameterNumber
        this.id = id;
        this.createdDate = clone(createdDate);
        this.modifiedDate = clone(modifiedDate);
        this.deletedDate = clone(deletedDate);
        this.lastLoginDate = clone(lastLoginDate);
        this.lastPasswordChangeDate = clone(lastPasswordChangeDate);
        this.emailValidatedDate = clone(emailValidatedDate);
        this.email = email;
        this.displayName = displayName;
        this.passwordDigest = passwordDigest;
        this.roles = new HashSet<>(roles);
    }

    public UUID getId() {
        return id;
    }

    public void setId(final UUID id) {
        this.id = id;
    }

    public Date getCreatedDate() {
        return clone(createdDate);
    }

    public void setCreatedDate(final Date createdDate) {
        this.createdDate = clone(createdDate);
    }

    public Date getModifiedDate() {
        return clone(modifiedDate);
    }

    public void setModifiedDate(final Date modifiedDate) {
        this.modifiedDate = clone(modifiedDate);
    }

    public Date getDeletedDate() {
        return clone(deletedDate);
    }

    public void setDeletedDate(final Date deletedDate) {
        this.deletedDate = clone(deletedDate);
    }

    public Date getLastLoginDate() {
        return clone(lastLoginDate);
    }

    public void setLastLoginDate(final Date lastLoginDate) {
        this.lastLoginDate = clone(lastLoginDate);
    }

    public Date getLastPasswordChangeDate() {
        return clone(lastPasswordChangeDate);
    }

    public void setLastPasswordChangeDate(final Date lastPasswordChangeDate) {
        this.lastPasswordChangeDate = clone(lastPasswordChangeDate);
    }

    public Date getEmailValidatedDate() {
        return clone(emailValidatedDate);
    }

    public void setEmailValidatedDate(final Date emailValidatedDate) {
        this.emailValidatedDate = clone(emailValidatedDate);
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(final String email) {
        this.email = email;
    }

    public String getDisplayName() {
        return displayName;
    }

    public void setDisplayName(final String displayName) {
        this.displayName = displayName;
    }

    public PasswordDigest getPasswordDigest() {
        return passwordDigest;
    }

    public void setPasswordDigest(final PasswordDigest passwordDigest) {
        this.passwordDigest = passwordDigest;
    }

    public Set<Roles> getRoles() {
        return roles;
    }

    public void setRoles(final Set<Roles> roles) {
        this.roles = roles;
    }

    private static Date clone(final Date date) {
        if (date == null) {
            return null;
        }
        return new Date(date.getTime());
    }

} public class User implements Principal {

    private final UUID id;
    @NotEmpty
    @Email(regexp = ".+@.+\\..+")
    private final String email;
    @NotEmpty
    @Length(min = 3, max = 20)
    private final String displayName;

    private final Set<Roles> roles;

    @JsonCreator
    public User(@JsonProperty("id") final UUID id,
                @JsonProperty("email") final String email,
                @JsonProperty("displayName") final String displayName,
                @JsonProperty("roles") final Set<Roles> roles) {
        this.id = id;
        this.email = email;
        this.displayName = displayName;
        this.roles = roles == null ? Collections.emptySet() : new HashSet<>(roles);
    }

    public UUID getId() {
        return id;
    }

    public String getEmail() {
        return email;
    }

    public String getDisplayName() {
        return displayName;
    }

    public Set<Roles> getRoles() {
        return roles;
    }

    public static User fromDB(final DBUser user) {
        return new User(
                user.getId(),
                user.getEmail(),
                user.getDisplayName(),
                user.getRoles());
    }

    @JsonIgnore
    @Override
    public String getName() {
        return email;
    }

    @Override
    public boolean implies(final Subject subject) {
        return false;
    }

    public boolean canRead(final Class<?> resourceClass, final UUID userId) {
        for (Roles role : roles) {
            if (role.canRead(resourceClass, this, userId)) {
                return true;
            }
        }
        return false;
    }

    public boolean canModify(final Class<?> resourceClass, final UUID userId) {
        for (Roles role : roles) {
            if (role.canWrite(resourceClass, this, userId)) {
                return true;
            }
        }
        return false;
    }

    @JsonIgnore
    public boolean isAdminForReading() {
        return roles.contains(Roles.ADMIN) || roles.contains(Roles.RO_ADMIN);
    }
} public class UserWithDates extends User {

    private final Date createdDate;
    private final Date modifiedDate;
    private final Date deletedDate;
    private final Date lastLoginDate;
    private final Date lastPasswordChangeDate;
    private final Date emailValidatedDate;

    // CHECKSTYLE.OFF: ParameterNumber
    public UserWithDates(@JsonProperty("id") final UUID id,
                         @JsonProperty("email") final String email,
                         @JsonProperty("displayName") final String displayName,
                         @JsonProperty("roles") final Set<Roles> roles,
                         @JsonProperty("createdDate") final Date createdDate,
                         @JsonProperty("modifiedDate") final Date modifiedDate,
                         @JsonProperty("deletedDate") final Date deletedDate,
                         @JsonProperty("lastLoginDate") final Date lastLoginDate,
                         @JsonProperty("lastPasswordChangeDate") final Date lastPasswordChangeDate,
                         @JsonProperty("emailValidatedDate") final Date emailValidatedDate) {
        // CHECKSTYLE.ON: ParameterNumber
        super(id, email, displayName, roles);
        this.createdDate = clone(createdDate);
        this.modifiedDate = clone(modifiedDate);
        this.deletedDate = clone(deletedDate);
        this.lastLoginDate = clone(lastLoginDate);
        this.lastPasswordChangeDate = clone(lastPasswordChangeDate);
        this.emailValidatedDate = clone(emailValidatedDate);
    }

    @JsonProperty
    public Date getCreatedDate() {
        return clone(createdDate);
    }

    @JsonProperty
    public Date getModifiedDate() {
        return clone(modifiedDate);
    }

    @JsonProperty
    public Date getDeletedDate() {
        return clone(deletedDate);
    }

    @JsonProperty
    public Date getLastLoginDate() {
        return clone(lastLoginDate);
    }

    @JsonProperty
    public Date getLastPasswordChangeDate() {
        return clone(lastPasswordChangeDate);
    }

    @JsonProperty
    public Date getEmailValidatedDate() {
        return clone(emailValidatedDate);
    }

    public static UserWithDates fromDB(final DBUser user) {
        return new UserWithDates(
                user.getId(),
                user.getEmail(),
                user.getDisplayName(),
                user.getRoles(),
                user.getCreatedDate(),
                user.getModifiedDate(),
                user.getDeletedDate(),
                user.getLastLoginDate(),
                user.getLastPasswordChangeDate(),
                user.getEmailValidatedDate());
    }

    private static Date clone(final Date date) {
        if (date == null) {
            return null;
        }
        return new Date(date.getTime());
    }
} public class UpdateUserRequest {

    @Valid
    private final User user;

    private final String password;

    @JsonCreator
    public UpdateUserRequest(@JsonProperty("user") final User user,
                             @JsonProperty("password") final String password) {
        this.user = user;
        this.password = password;
    }

    @JsonProperty
    public User getUser() {
        return user;
    }

    @JsonProperty
    public String getPassword() {
        return password;
    }
}
import com.google.common.collect.Sets;
import com.lewisd.authrite.auth.JwtTokenManager;
import com.lewisd.authrite.auth.PasswordDigest;
import com.lewisd.authrite.auth.PasswordManagementConfiguration;
import com.lewisd.authrite.auth.Roles;
import com.lewisd.authrite.errors.Exceptions;
import com.lewisd.authrite.jdbc.dao.UsersDao;
import com.lewisd.authrite.jdbc.model.DBUser;
import com.lewisd.authrite.resources.model.User;
import com.lewisd.authrite.resources.model.UserWithDates;
import com.lewisd.authrite.resources.requests.UpdateUserRequest;
import org.skife.jdbi.v2.DBI;
import org.skife.jdbi.v2.Handle;
import javax.ws.rs.core.Response;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
package com.lewisd.authrite.services;



public class UsersService {
    public static final String EXPECTED_ONE_ROW_UPDATED_FORMAT = "Expected 1 row updated, was %d";
    public static final String YOU_DO_NOT_HAVE_PERMISSION_TO_UPDATE_THIS_USER = "You do not have permission to update this user";
    public static final String USER_NOT_FOUND = "User not found";
    public static final String INCORRECT_PASSWORD = "Incorrect password";
    private final DBI dbi;
    private final JwtTokenManager jwtTokenManager;
    private final PasswordManagementConfiguration passwordManagementConfiguration;

    public UsersService(final DBI dbi, final JwtTokenManager jwtTokenManager,
                        final PasswordManagementConfiguration passwordManagementConfiguration) {
        this.dbi = dbi;
        this.jwtTokenManager = jwtTokenManager;
        this.passwordManagementConfiguration = passwordManagementConfiguration;
    }

    public User getUser(final User principal, final UUID userId) {
        if (principal.canRead(User.class, userId)) {
            try (final Handle handle = dbi.open()) {
                final UsersDao usersDao = handle.attach(UsersDao.class);
                final Optional<DBUser> maybeUser = usersDao.getUser(userId);
                return maybeUser.map((user) -> {
                    if (principal.isAdminForReading()) {
                        return UserWithDates.fromDB(user);