001/*
002 *   Copyright 2024 Vonage
003 *
004 *   Licensed under the Apache License, Version 2.0 (the "License");
005 *   you may not use this file except in compliance with the License.
006 *   You may obtain a copy of the License at
007 *
008 *        http://www.apache.org/licenses/LICENSE-2.0
009 *
010 *   Unless required by applicable law or agreed to in writing, software
011 *   distributed under the License is distributed on an "AS IS" BASIS,
012 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 *   See the License for the specific language governing permissions and
014 *   limitations under the License.
015 */
016package com.vonage.client.auth;
017
018import com.vonage.client.auth.hashutils.HashUtil;
019import java.util.*;
020
021/**
022 * Internal class, managing a collection of {@link AuthMethod}s.
023 * <p>
024 * This holds a collection of AuthMethod instances, in order of preference, and
025 * allow for simple selection of an appropriate AuthMethod for a particular REST endpoint.
026 */
027public class AuthCollection {
028    private final SortedSet<AuthMethod> authList;
029
030    /**
031     * Create a new AuthCollection with an empty set of AuthMethods.
032     */
033    public AuthCollection() {
034        this(new TreeSet<>());
035    }
036
037    public AuthCollection(AuthMethod... authMethods) {
038        this(new TreeSet<>(Arrays.asList(authMethods)));
039    }
040
041    public AuthCollection(SortedSet<AuthMethod> authMethods) {
042        authList = authMethods;
043    }
044
045    public AuthCollection(UUID applicationId, byte[] privateKeyContents, String key, String secret, HashUtil.HashType hashType, String signature) {
046        this();
047
048        if (key != null && secret == null && signature == null) {
049            throw new IllegalStateException(
050                    "You must provide an API secret or signature secret in addition to your API key.");
051        }
052        if (secret != null && key == null) {
053            throw new IllegalStateException("You must provide an API key in addition to your API secret.");
054        }
055        if (signature != null && key == null) {
056            throw new IllegalStateException("You must provide an API key in addition to your signature secret.");
057        }
058        if (applicationId == null && privateKeyContents != null) {
059            throw new IllegalStateException("You must provide an application ID in addition to your private key.");
060        }
061        if (applicationId != null && privateKeyContents == null) {
062            throw new IllegalStateException("You must provide a private key in addition to your application id.");
063        }
064
065        authList.add(new NoAuthMethod());
066        if (key != null && secret != null) {
067            authList.add(new ApiKeyHeaderAuthMethod(key, secret));
068            authList.add(new ApiKeyQueryParamsAuthMethod(key, secret));
069        }
070        if (key != null && signature != null) {
071            authList.add(new SignatureAuthMethod(key, signature, hashType));
072        }
073        if (applicationId != null) {
074            authList.add(new JWTAuthMethod(applicationId.toString(), privateKeyContents));
075        }
076    }
077
078    /**
079     * Add a new {@link AuthMethod} to the set managed by this AuthCollection.
080     * If an auth method of this type already exists, this method will replace it with
081     * the new provided value.
082     *
083     * @param auth AuthMethod method to be added to this collection.
084     */
085    public void add(AuthMethod auth) {
086        authList.remove(auth);
087        authList.add(auth);
088    }
089
090    /**
091     * Obtain an AuthMethod of type T, if one is contained in this collection.
092     *
093     * @param type The type of AuthMethod to be located.
094     * @param <T>  The type of AuthMethod which will be returned.
095     *
096     * @return An AuthMethod subclass matching type.
097     *
098     * @throws VonageUnacceptableAuthException if no matching AuthMethod is found.
099     */
100    @SuppressWarnings("unchecked")
101    public <T extends AuthMethod> T getAuth(Class<T> type) throws VonageUnacceptableAuthException {
102        for (AuthMethod availableAuthMethod : authList) {
103            if (type.isInstance(availableAuthMethod)) {
104                return (T) availableAuthMethod;
105            }
106        }
107        throw new VonageUnacceptableAuthException(authList, Collections.singletonList(type));
108    }
109
110    /**
111     * Obtain an {@link AuthMethod} instance for a set of acceptable AuthMethod classes.
112     *
113     * @param acceptableAuthMethodClasses A Set of AuthMethod classes which are suitable for the target REST endpoint.
114     *
115     * @return the preferred AuthMethod from the provided set of acceptable AuthMethod classes.
116     *
117     * @throws VonageUnacceptableAuthException if no appropriate AuthMethod is held by this AuthCollection.
118     */
119    public AuthMethod getAcceptableAuthMethod(Set<Class<? extends AuthMethod>> acceptableAuthMethodClasses) throws VonageUnacceptableAuthException {
120        for (AuthMethod availableAuthMethod : authList) {
121            for (Class<? extends AuthMethod> acceptable : acceptableAuthMethodClasses) {
122                if (acceptable.isAssignableFrom(availableAuthMethod.getClass())) {
123                    return availableAuthMethod;
124                }
125            }
126        }
127        throw new VonageUnacceptableAuthException(authList, acceptableAuthMethodClasses);
128    }
129
130    /**
131     * Utility method for determining whether a certain authentication method has been registered.
132     *
133     * @param authMethod The authentication method type.
134     *
135     * @return {@code true} if the specified auth method is available, {@code false} otherwise.
136     *
137     * @since 7.3.0
138     */
139    public boolean hasAuthMethod(Class<? extends AuthMethod> authMethod) {
140        return authList.stream().map(AuthMethod::getClass).anyMatch(authMethod::equals);
141    }
142}