at.ac.univie.isc.asio.database.MysqlUserRepository.java Source code

Java tutorial

Introduction

Here is the source code for at.ac.univie.isc.asio.database.MysqlUserRepository.java

Source

/*
 * #%L
 * asio server
 * %%
 * Copyright (C) 2013 - 2015 Research Group Scientific Computing, University of Vienna
 * %%
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * #L%
 */
package at.ac.univie.isc.asio.database;

import at.ac.univie.isc.asio.security.Identity;
import at.ac.univie.isc.asio.tool.Pretty;
import com.google.common.base.Charsets;
import com.google.common.base.Joiner;
import com.google.common.base.Preconditions;
import com.google.common.hash.Hashing;
import com.google.common.io.BaseEncoding;
import org.jooq.DSLContext;
import org.jooq.SQLDialect;
import org.jooq.exception.DataAccessException;
import org.jooq.impl.DSL;

import javax.sql.DataSource;
import java.sql.SQLException;
import java.util.Collection;

import static org.jooq.impl.DSL.inline;
import static org.jooq.impl.DSL.name;

/**
 * Manage MySql users, segregated by schema.
 */
public /* final */ class MysqlUserRepository {
    /** only allows login through socket file from same machine */
    static final String USER_HOST = "localhost";
    static final String GRANT_TEMPLATE = "GRANT %s ON {0}.* TO {1}@{2} IDENTIFIED BY {3}";

    private final String grantStatement;
    private final DSLContext create;

    public MysqlUserRepository(final DataSource dataSource, final Collection<String> privileges) {
        Preconditions.checkArgument(!privileges.isEmpty(), "no privileges given");
        create = DSL.using(dataSource, SQLDialect.MYSQL);
        // build grant statement with given privileges
        grantStatement = Pretty.format(GRANT_TEMPLATE, Joiner.on(',').join(privileges));
    }

    @Override
    public String toString() {
        return "MysqlUserRepository{" + "grantStatement='" + grantStatement + '\'' + '}';
    }

    /**
     * Create a user, which has access to the given database only.
     *
     * @param schema name of mysql database
     * @return access credentials
     */
    public Identity createUserFor(final String schema) {
        final Identity user = translateToCredentials(schema);
        // GRANT [privileges] ON {0}.* TO {1}@{2} IDENTIFIED BY {3}
        create.execute(grantStatement, name(schema), name(user.getName()), name(USER_HOST),
                inline(user.getSecret()));
        return user;
    }

    /**
     * Delete the user of the given schema. This will only drop users created by this asio.
     *
     * @param schema name of mysql database
     */
    public void dropUserOf(final String schema) {
        final Identity user = translateToCredentials(schema);
        try {
            create.execute("DROP USER {0}@{1}", name(user.getName()), name(USER_HOST));
        } catch (DataAccessException e) {
            if (indicatesFatalFailure(e)) {
                throw e;
            }
        }
    }

    /** mysql emits this error code if a user does not exist in a DROP USER statement */
    static final int ERROR_CODE_DROP_USER_FAILED = 1396;

    private boolean indicatesFatalFailure(final DataAccessException e) {
        if (e.getCause() != null && e.getCause() instanceof SQLException) {
            final SQLException sqlError = (SQLException) e.getCause();
            return sqlError.getErrorCode() != ERROR_CODE_DROP_USER_FAILED;
        } else {
            return true;
        }
    }

    private static final String USERNAME_PREFIX = "_:";
    private static final String PASSWORD_SALT = "asio:";

    /**
     * Derive a unique username and obfuscated password from the given schema name.
     * The username is the base64 encoded SHA-256 hash of the schema name prefixed with {@code '_:'}
     * and truncated to fit the 16 character limit of MySql.
     * The password is the base64 encoded SHA-256 hash of the schema name, salted with the prefix
     * {@code 'asio:'}.
     *
     * @param schema name of a mysql database
     * @return derived, unique credentials
     */
    private Identity translateToCredentials(final String schema) {
        Preconditions.checkNotNull(schema, "jdbc schema name missing");
        final String hashedSchemaName = BaseEncoding.base64()
                .encode(Hashing.sha256().hashString(schema, Charsets.UTF_8).asBytes());
        final String username = USERNAME_PREFIX + hashedSchemaName.substring(0, 14);
        final String password = BaseEncoding.base64()
                .encode(Hashing.sha256().hashString(PASSWORD_SALT + schema, Charsets.UTF_8).asBytes());
        return Identity.from(username, password);
    }
}