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

org.cloudfoundry.identity.uaa.oauth.token.JdbcRevocableTokenProvisioning Maven / Gradle / Ivy

/*******************************************************************************
 *     Cloud Foundry
 *     Copyright (c) [2009-2016] Pivotal Software, Inc. All Rights Reserved.
 *
 *     This product is licensed to you under the Apache License, Version 2.0 (the "License").
 *     You may not use this product except in compliance with the License.
 *
 *     This product includes a number of subcomponents with
 *     separate copyright notices and license terms. Your use of these
 *     subcomponents is subject to the terms and conditions of the
 *     subcomponent's license, as noted in the LICENSE file.
 *******************************************************************************/
package org.cloudfoundry.identity.uaa.oauth.token;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.cloudfoundry.identity.uaa.audit.event.SystemDeletable;
import org.cloudfoundry.identity.uaa.resources.jdbc.LimitSqlAdapter;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.util.StringUtils;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import java.util.concurrent.atomic.AtomicLong;
import java.util.stream.Collectors;

import static org.cloudfoundry.identity.uaa.oauth.token.RevocableToken.TokenType.REFRESH_TOKEN;
import static org.springframework.util.StringUtils.isEmpty;

public class JdbcRevocableTokenProvisioning implements RevocableTokenProvisioning, SystemDeletable {

    private final static String REFRESH_TOKEN_RESPONSE_TYPE = REFRESH_TOKEN.toString();
    protected final static String FIELDS = "token_id,client_id,user_id,format,response_type,issued_at,expires_at,scope,data,identity_zone_id";
    protected static final String UPDATE_FIELDS = FIELDS.substring(FIELDS.indexOf(',')+1, FIELDS.lastIndexOf(',')).replace(",","=?,") + "=?";
    protected final static String TABLE = "revocable_tokens";
    protected final static String GET_QUERY = "SELECT " + FIELDS +" FROM "+TABLE + " WHERE token_id=? AND identity_zone_id=?";
    protected final static String GET_BY_USER_QUERY = "SELECT " + FIELDS +" FROM "+TABLE + " WHERE user_id=? AND identity_zone_id=?";
    protected final static String GET_BY_CLIENT_QUERY = "SELECT " + FIELDS +" FROM "+TABLE + " WHERE client_id=? AND identity_zone_id=?";
    protected final static String UPDATE_QUERY = "UPDATE "+TABLE+" SET "+UPDATE_FIELDS+" WHERE token_id=? and identity_zone_id=?";
    protected final static String INSERT_QUERY = "INSERT INTO " + TABLE + " ("+FIELDS+") VALUES (?,?,?,?,?,?,?,?,?,?)";
    protected final static String DELETE_QUERY = "DELETE FROM " + TABLE + " WHERE token_id=? and identity_zone_id=?";
    protected final static String DELETE_EXPIRED_QUERY = "DELETE FROM " + TABLE + " WHERE expires_at < ?";
    protected final static String DELETE_REFRESH_TOKEN_QUERY = "DELETE FROM " + TABLE + " WHERE user_id=? AND client_id=? AND response_type='" +REFRESH_TOKEN_RESPONSE_TYPE+ "' AND identity_zone_id=?";
    protected final static String DELETE_BY_CLIENT_QUERY = "DELETE FROM " + TABLE + " WHERE client_id = ? AND identity_zone_id=?";
    protected final static String DELETE_BY_USER_QUERY = "DELETE FROM " + TABLE + " WHERE user_id = ? AND identity_zone_id=?";
    protected final static String DELETE_BY_ZONE_QUERY = "DELETE FROM " + TABLE + " WHERE identity_zone_id=?";


    protected static final Log logger = LogFactory.getLog(JdbcRevocableTokenProvisioning.class);
    protected final RowMapper rowMapper;
    protected final JdbcTemplate template;
    protected final LimitSqlAdapter limitSqlAdapter;

    protected AtomicLong lastExpiredCheck = new AtomicLong(0);
    protected long expirationCheckInterval = 30000; //30 seconds
    private long maxExpirationRuntime = 2500l;

    public JdbcRevocableTokenProvisioning(JdbcTemplate jdbcTemplate, LimitSqlAdapter limitSqlAdapter) {
        this.rowMapper =  new RevocableTokenRowMapper();
        this.template = jdbcTemplate;
        this.limitSqlAdapter = limitSqlAdapter;
    }

    @Override
    public List retrieveAll(String zoneId) {
        return null;
    }


    public RevocableToken retrieve(String id, boolean checkExpired, String zoneId) {
        if (checkExpired) {
            checkExpired();
        }
        RevocableToken result = template.queryForObject(GET_QUERY, rowMapper, id, zoneId);
        if (checkExpired && result.getExpiresAt() < System.currentTimeMillis()) {
            delete(id, 0, zoneId);
            throw new EmptyResultDataAccessException("Token expired.", 1);
        }
        return result;
    }

    @Override
    public RevocableToken retrieve(String id, String zoneId) {
        return retrieve(id, true, zoneId);
    }


    @Override
    public int deleteRefreshTokensForClientAndUserId(String clientId, String userId, String zoneId) {
        int deleted_rows = template.update(DELETE_REFRESH_TOKEN_QUERY, userId, clientId, zoneId);
        return deleted_rows;
    }



    @Override
    public RevocableToken create(RevocableToken t, String zoneId) {
        checkExpired();
        template.update(INSERT_QUERY,
                        t.getTokenId(),
                        t.getClientId(),
                        t.getUserId(),
                        t.getFormat(),
                        t.getResponseType().toString(),
                        t.getIssuedAt(),
                        t.getExpiresAt(),
                        t.getScope(),
                        t.getValue(),
                        zoneId);
        return retrieve(t.getTokenId(), false, zoneId);
    }

    @Override
    public RevocableToken update(String id, RevocableToken t, String zoneId) {
        template.update(UPDATE_QUERY,
                        t.getClientId(),
                        t.getUserId(),
                        t.getFormat(),
                        t.getResponseType().toString(),
                        t.getIssuedAt(),
                        t.getExpiresAt(),
                        t.getScope(),
                        t.getValue(),
                        id,
                        zoneId);
        return retrieve(id, false, zoneId);
    }

    @Override
    public RevocableToken delete(String id, int version, String zoneId) {
        RevocableToken previous = retrieve(id, false, zoneId);
        template.update(DELETE_QUERY, id, zoneId);
        return previous;
    }

    @Override
    public int deleteByIdentityZone(String zoneId) {
        return template.update(DELETE_BY_ZONE_QUERY, zoneId);
    }

    @Override
    public int deleteByClient(String clientId, String zoneId) {
        return template.update(DELETE_BY_CLIENT_QUERY, clientId, zoneId);
    }

    @Override
    public int deleteByUser(String userId, String zoneId) {
        return template.update(DELETE_BY_USER_QUERY, userId, zoneId);
    }

    @Override
    public Log getLogger() {
        return logger;
    }

    @Override
    public List getUserTokens(String userId, String zoneId) {
        return template.query(GET_BY_USER_QUERY, rowMapper, userId, zoneId);
    }

    @Override
    public List getUserTokens(String userId, String clientId, String zoneId) {
        if (isEmpty(clientId)) {
            throw new NullPointerException("Client ID can not be null when retrieving tokens.");
        }
        return getUserTokens(userId, zoneId).stream().filter(r -> clientId.equals(r.getClientId())).collect(Collectors.toList());
    }

    @Override
    public List getClientTokens(String clientId, String zoneId) {
        return template.query(GET_BY_CLIENT_QUERY, rowMapper, clientId, zoneId);
    }

    public long getExpirationCheckInterval() {
        return expirationCheckInterval;
    }

    public void setExpirationCheckInterval(long expirationCheckInterval) {
        this.expirationCheckInterval = expirationCheckInterval;
    }

    public long getMaxExpirationRuntime() {
        return maxExpirationRuntime;
    }

    public void checkExpired() {
        long now = System.currentTimeMillis();
        long lastCheck = lastExpiredCheck.get();
        if ((now - lastCheck) > getExpirationCheckInterval() && lastExpiredCheck.compareAndSet(lastCheck, now)) {
            if (runDeleteExpired(now)) {
                lastExpiredCheck.set(0);
            }
        }

    }

    /**
     * @param now
     * @return true if the last delete action deleted the max rows, this also signals there could be more rows to be deleted.
     */
    protected boolean runDeleteExpired(long now) {
        final int maxRows = 500;
        String sql = limitSqlAdapter.getDeleteExpiredQuery(
            TABLE, "token_id", "expires_at", maxRows
        );
        int removed;
        do {
            removed = template.update(sql, now);
            logger.info("Removed " + removed + " expired revocable tokens.");
        } while (removed > 0 && (System.currentTimeMillis()-now)< maxExpirationRuntime);
        return removed >= maxRows;
    }

    public long getLastExpiredRun() {
        return lastExpiredCheck.get();
    }

    public void setMaxExpirationRuntime(long maxExpirationRuntime) {
        this.maxExpirationRuntime = maxExpirationRuntime;
    }

    protected static final class RevocableTokenRowMapper implements RowMapper {

        @Override
        public RevocableToken mapRow(ResultSet rs, int rowNum) throws SQLException {
            int pos = 1;

            RevocableToken revocableToken = new RevocableToken();
            revocableToken.setTokenId(rs.getString(pos++));
            revocableToken.setClientId(rs.getString(pos++));
            revocableToken.setUserId(rs.getString(pos++));
            revocableToken.setFormat(rs.getString(pos++));
            String responseType = rs.getString(pos++);
            if(StringUtils.hasText(responseType)) {
                revocableToken.setResponseType(RevocableToken.TokenType.valueOf(responseType));
            }
            revocableToken.setIssuedAt(rs.getLong(pos++));
            revocableToken.setExpiresAt(rs.getLong(pos++));
            revocableToken.setScope(rs.getString(pos++));
            revocableToken.setValue(rs.getString(pos++));
            revocableToken.setZoneId(rs.getString(pos++));
            return revocableToken;
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy