/** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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. */ package org.apache.cxf.rs.security.oauth2.provider; import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import javax.persistence.EntityManager; import javax.persistence.EntityManagerFactory; import javax.persistence.EntityTransaction; import javax.persistence.TypedQuery; import org.apache.cxf.helpers.CastUtils; import org.apache.cxf.rs.security.oauth2.common.AccessTokenRegistration; import org.apache.cxf.rs.security.oauth2.common.Client; import org.apache.cxf.rs.security.oauth2.common.OAuthPermission; import org.apache.cxf.rs.security.oauth2.common.ServerAccessToken; import org.apache.cxf.rs.security.oauth2.common.UserSubject; import org.apache.cxf.rs.security.oauth2.tokens.bearer.BearerAccessToken; import org.apache.cxf.rs.security.oauth2.tokens.refresh.RefreshToken; /** * Provides a Jpa BMT implementation for OAuthDataProvider. * * If your application runs in a container and if you want to use * container managed persistence, you'll have to override * the following methods : * <ul> * <li> {@link #getEntityManager()}</li> * <li> {@link #commitIfNeeded(EntityManager)}</li> * <li> {@link #closeIfNeeded(EntityManager)}</li> * </ul> */ public class JPAOAuthDataProvider extends AbstractOAuthDataProvider { private static final String CLIENT_QUERY = "SELECT client FROM Client client" + " INNER JOIN client.resourceOwnerSubject ros"; private EntityManagerFactory entityManagerFactory; public void setEntityManagerFactory(EntityManagerFactory emf) { this.entityManagerFactory = emf; } @Override public Client doGetClient(final String clientId) throws OAuthServiceException { return execute(new EntityManagerOperation<Client>() { @Override public Client execute(EntityManager em) { return em.find(Client.class, clientId); } }); } protected <T> T execute(EntityManagerOperation<T> operation) { EntityManager em = getEntityManager(); T value; try { value = operation.execute(em); } finally { closeIfNeeded(em); } return value; } protected <T> T executeInTransaction(EntityManagerOperation<T> operation) { EntityManager em = getEntityManager(); EntityTransaction transaction = null; T value; try { transaction = beginIfNeeded(em); value = operation.execute(em); flushIfNeeded(em); commitIfNeeded(em); } catch (RuntimeException e) { if (transaction != null) { transaction.rollback(); } throw e; } finally { closeIfNeeded(em); } return value; } public void setClient(final Client client) { executeInTransaction(new EntityManagerOperation<Void>() { @Override public Void execute(EntityManager em) { if (client.getResourceOwnerSubject() != null) { UserSubject sub = em.find(UserSubject.class, client.getResourceOwnerSubject().getId()); if (sub == null) { em.persist(client.getResourceOwnerSubject()); } else { client.setResourceOwnerSubject(sub); } } boolean clientExists = em.createQuery("SELECT count(client) from Client client " + "where client.clientId = :id", Long.class) .setParameter("id", client.getClientId()) .getSingleResult() > 0 ? true : false; if (clientExists) { em.merge(client); } else { em.persist(client); } return null; } }); } @Override protected void doRemoveClient(final Client c) { executeInTransaction(new EntityManagerOperation<Void>() { @Override public Void execute(EntityManager em) { Client clientToRemove = em.getReference(Client.class, c.getClientId()); em.remove(clientToRemove); return null; } }); } @Override public List<Client> getClients(final UserSubject resourceOwner) { return execute(new EntityManagerOperation<List<Client>>() { @Override public List<Client> execute(EntityManager em) { return getClientsQuery(resourceOwner, em).getResultList(); } }); } @Override public List<ServerAccessToken> getAccessTokens(final Client c, final UserSubject sub) { return execute(new EntityManagerOperation<List<ServerAccessToken>>() { @Override public List<ServerAccessToken> execute(EntityManager em) { return CastUtils.cast(getTokensQuery(c, sub, em).getResultList()); } }); } @Override public List<RefreshToken> getRefreshTokens(final Client c, final UserSubject sub) { return execute(new EntityManagerOperation<List<RefreshToken>>() { @Override public List<RefreshToken> execute(EntityManager em) { return getRefreshTokensQuery(c, sub, em).getResultList(); } }); } @Override public ServerAccessToken getAccessToken(final String accessToken) throws OAuthServiceException { return execute(new EntityManagerOperation<ServerAccessToken>() { @Override public ServerAccessToken execute(EntityManager em) { return em.find(BearerAccessToken.class, accessToken); } }); } @Override protected void doRevokeAccessToken(final ServerAccessToken at) { executeInTransaction(new EntityManagerOperation<Void>() { @Override public Void execute(EntityManager em) { ServerAccessToken tokenToRemove = em.getReference(at.getClass(), at.getTokenKey()); em.remove(tokenToRemove); return null; } }); } @Override protected void linkRefreshTokenToAccessToken(final RefreshToken rt, final ServerAccessToken at) { super.linkRefreshTokenToAccessToken(rt, at); executeInTransaction(new EntityManagerOperation<Void>() { @Override public Void execute(EntityManager em) { em.merge(at); return null; } }); } @Override protected RefreshToken getRefreshToken(final String refreshTokenKey) { return execute(new EntityManagerOperation<RefreshToken>() { @Override public RefreshToken execute(EntityManager em) { return em.find(RefreshToken.class, refreshTokenKey); } }); } @Override protected void doRevokeRefreshToken(final RefreshToken rt) { executeInTransaction(new EntityManagerOperation<Void>() { @Override public Void execute(EntityManager em) { RefreshToken tokentoRemove = em.getReference(RefreshToken.class, rt.getTokenKey()); em.remove(tokentoRemove); return null; } }); } @Override protected ServerAccessToken doCreateAccessToken(AccessTokenRegistration atReg) { ServerAccessToken at = super.doCreateAccessToken(atReg); // we override this in order to get rid of elementCollections directly injected // from another entity // this can be the case when using multiple cmt dataProvider operation in a single entityManager // lifespan if (at.getAudiences() != null) { at.setAudiences(new ArrayList<>(at.getAudiences())); } if (at.getExtraProperties() != null) { at.setExtraProperties(new HashMap<String, String>(at.getExtraProperties())); } if (at.getScopes() != null) { at.setScopes(new ArrayList<>(at.getScopes())); } if (at.getParameters() != null) { at.setParameters(new HashMap<String, String>(at.getParameters())); } return at; } protected void saveAccessToken(final ServerAccessToken serverToken) { executeInTransaction(new EntityManagerOperation<Void>() { @Override public Void execute(EntityManager em) { List<OAuthPermission> perms = new LinkedList<OAuthPermission>(); for (OAuthPermission perm : serverToken.getScopes()) { OAuthPermission permSaved = em.find(OAuthPermission.class, perm.getPermission()); if (permSaved != null) { perms.add(permSaved); } else { em.persist(perm); perms.add(perm); } } serverToken.setScopes(perms); if (serverToken.getSubject() != null) { UserSubject sub = em.find(UserSubject.class, serverToken.getSubject().getId()); if (sub == null) { em.persist(serverToken.getSubject()); } else { sub = em.merge(serverToken.getSubject()); serverToken.setSubject(sub); } } // ensure we have a managed association // (needed for OpenJPA : InvalidStateException: Encountered unmanaged object) if (serverToken.getClient() != null) { serverToken.setClient(em.find(Client.class, serverToken.getClient().getClientId())); } em.persist(serverToken); return null; } }); } protected void saveRefreshToken(RefreshToken refreshToken) { persistEntity(refreshToken); } protected void persistEntity(final Object entity) { executeInTransaction(new EntityManagerOperation<Void>() { @Override public Void execute(EntityManager em) { em.persist(entity); return null; } }); } protected void removeEntity(final Object entity) { executeInTransaction(new EntityManagerOperation<Void>() { @Override public Void execute(EntityManager em) { em.remove(entity); return null; } }); } protected TypedQuery<Client> getClientsQuery(UserSubject resourceOwnerSubject, EntityManager entityManager) { if (resourceOwnerSubject == null) { return entityManager.createQuery(CLIENT_QUERY, Client.class); } else { return entityManager.createQuery(CLIENT_QUERY + " WHERE ros.login = :login", Client.class). setParameter("login", resourceOwnerSubject.getLogin()); } } protected TypedQuery<BearerAccessToken> getTokensQuery(Client c, UserSubject resourceOwnerSubject, EntityManager entityManager) { if (c == null && resourceOwnerSubject == null) { return entityManager.createQuery("SELECT t FROM BearerAccessToken t", BearerAccessToken.class); } else if (c == null) { return entityManager.createQuery( "SELECT t FROM BearerAccessToken t" + " JOIN t.subject s" + " WHERE s.login = :login", BearerAccessToken.class) .setParameter("login", resourceOwnerSubject.getLogin()); } else if (resourceOwnerSubject == null) { return entityManager.createQuery( "SELECT t FROM BearerAccessToken t" + " JOIN t.client c" + " WHERE c.clientId = :clientId", BearerAccessToken.class) .setParameter("clientId", c.getClientId()); } else { return entityManager.createQuery( "SELECT t FROM BearerAccessToken t" + " JOIN t.subject s" + " JOIN t.client c" + " WHERE s.login = :login AND c.clientId = :clientId", BearerAccessToken.class) .setParameter("login", resourceOwnerSubject.getLogin()) .setParameter("clientId", c.getClientId()); } } protected TypedQuery<RefreshToken> getRefreshTokensQuery(Client c, UserSubject resourceOwnerSubject, EntityManager entityManager) { if (c == null && resourceOwnerSubject == null) { return entityManager.createQuery("SELECT t FROM RefreshToken t", RefreshToken.class); } else if (c == null) { return entityManager.createQuery( "SELECT t FROM RefreshToken t" + " JOIN t.subject s" + " WHERE s.login = :login", RefreshToken.class) .setParameter("login", resourceOwnerSubject.getLogin()); } else if (resourceOwnerSubject == null) { return entityManager.createQuery( "SELECT t FROM RefreshToken t" + " JOIN t.client c" + " WHERE c.clientId = :clientId", RefreshToken.class) .setParameter("clientId", c.getClientId()); } else { return entityManager.createQuery( "SELECT t FROM RefreshToken t" + " JOIN t.subject s" + " JOIN t.client c" + " WHERE s.login = :login AND c.clientId = :clientId", RefreshToken.class) .setParameter("login", resourceOwnerSubject.getLogin()) .setParameter("clientId", c.getClientId()); } } /** * Returns the entityManaged used for the current operation. */ protected EntityManager getEntityManager() { return entityManagerFactory.createEntityManager(); } /** * Begins the current transaction. * * This method needs to be overridden in a CMT environment. */ protected EntityTransaction beginIfNeeded(EntityManager em) { EntityTransaction tx = em.getTransaction(); tx.begin(); return tx; } /** * Flush the current transaction. */ protected void flushIfNeeded(EntityManager em) { em.flush(); } /** * Commits the current transaction. * * This method needs to be overridden in a CMT environment. */ protected void commitIfNeeded(EntityManager em) { em.getTransaction().commit(); } /** * Closes the current em. * * This method needs to be overriden in a CMT environment. */ protected void closeIfNeeded(EntityManager em) { em.close(); } public interface EntityManagerOperation<T> { T execute(EntityManager em); } }