001package org.eclipse.aether.repository;
002
003/*
004 * Licensed to the Apache Software Foundation (ASF) under one
005 * or more contributor license agreements.  See the NOTICE file
006 * distributed with this work for additional information
007 * regarding copyright ownership.  The ASF licenses this file
008 * to you under the Apache License, Version 2.0 (the
009 * "License"); you may not use this file except in compliance
010 * with the License.  You may obtain a copy of the License at
011 * 
012 *  http://www.apache.org/licenses/LICENSE-2.0
013 * 
014 * Unless required by applicable law or agreed to in writing,
015 * software distributed under the License is distributed on an
016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017 * KIND, either express or implied.  See the License for the
018 * specific language governing permissions and limitations
019 * under the License.
020 */
021
022import java.io.UnsupportedEncodingException;
023import java.security.MessageDigest;
024import java.security.NoSuchAlgorithmException;
025
026import org.eclipse.aether.RepositorySystemSession;
027
028/**
029 * A helper to calculate a fingerprint/digest for the authentication data of a repository/proxy. Such a fingerprint can
030 * be used to detect changes in the authentication data across JVM restarts without exposing sensitive information.
031 */
032public final class AuthenticationDigest
033{
034
035    private final MessageDigest digest;
036
037    private final RepositorySystemSession session;
038
039    private final RemoteRepository repository;
040
041    private final Proxy proxy;
042
043    /**
044     * Gets the fingerprint for the authentication of the specified repository.
045     * 
046     * @param session The repository system session during which the fingerprint is requested, must not be {@code null}.
047     * @param repository The repository whose authentication is to be fingerprinted, must not be {@code null}.
048     * @return The fingerprint of the repository authentication or an empty string if no authentication is configured,
049     *         never {@code null}.
050     */
051    public static String forRepository( RepositorySystemSession session, RemoteRepository repository )
052    {
053        String digest = "";
054        Authentication auth = repository.getAuthentication();
055        if ( auth != null )
056        {
057            AuthenticationDigest authDigest = new AuthenticationDigest( session, repository, null );
058            auth.digest( authDigest );
059            digest = authDigest.digest();
060        }
061        return digest;
062    }
063
064    /**
065     * Gets the fingerprint for the authentication of the specified repository's proxy.
066     * 
067     * @param session The repository system session during which the fingerprint is requested, must not be {@code null}.
068     * @param repository The repository whose proxy authentication is to be fingerprinted, must not be {@code null}.
069     * @return The fingerprint of the proxy authentication or an empty string if no proxy is present or if no proxy
070     *         authentication is configured, never {@code null}.
071     */
072    public static String forProxy( RepositorySystemSession session, RemoteRepository repository )
073    {
074        String digest = "";
075        Proxy proxy = repository.getProxy();
076        if ( proxy != null )
077        {
078            Authentication auth = proxy.getAuthentication();
079            if ( auth != null )
080            {
081                AuthenticationDigest authDigest = new AuthenticationDigest( session, repository, proxy );
082                auth.digest( authDigest );
083                digest = authDigest.digest();
084            }
085        }
086        return digest;
087    }
088
089    private AuthenticationDigest( RepositorySystemSession session, RemoteRepository repository, Proxy proxy )
090    {
091        this.session = session;
092        this.repository = repository;
093        this.proxy = proxy;
094        digest = newDigest();
095    }
096
097    private static MessageDigest newDigest()
098    {
099        try
100        {
101            return MessageDigest.getInstance( "SHA-1" );
102        }
103        catch ( NoSuchAlgorithmException e )
104        {
105            try
106            {
107                return MessageDigest.getInstance( "MD5" );
108            }
109            catch ( NoSuchAlgorithmException ne )
110            {
111                throw new IllegalStateException( ne );
112            }
113        }
114    }
115
116    /**
117     * Gets the repository system session during which the authentication fingerprint is calculated.
118     * 
119     * @return The repository system session, never {@code null}.
120     */
121    public RepositorySystemSession getSession()
122    {
123        return session;
124    }
125
126    /**
127     * Gets the repository requiring authentication. If {@link #getProxy()} is not {@code null}, the data gathered by
128     * this authentication digest does not apply to the repository's host but rather the proxy.
129     * 
130     * @return The repository to be contacted, never {@code null}.
131     */
132    public RemoteRepository getRepository()
133    {
134        return repository;
135    }
136
137    /**
138     * Gets the proxy (if any) to be authenticated with.
139     * 
140     * @return The proxy or {@code null} if authenticating directly with the repository's host.
141     */
142    public Proxy getProxy()
143    {
144        return proxy;
145    }
146
147    /**
148     * Updates the digest with the specified strings.
149     * 
150     * @param strings The strings to update the digest with, may be {@code null} or contain {@code null} elements.
151     */
152    public void update( String... strings )
153    {
154        if ( strings != null )
155        {
156            for ( String string : strings )
157            {
158                if ( string != null )
159                {
160                    try
161                    {
162                        digest.update( string.getBytes( "UTF-8" ) );
163                    }
164                    catch ( UnsupportedEncodingException e )
165                    {
166                        throw new IllegalStateException( e );
167                    }
168                }
169            }
170        }
171    }
172
173    /**
174     * Updates the digest with the specified characters.
175     * 
176     * @param chars The characters to update the digest with, may be {@code null}.
177     */
178    public void update( char... chars )
179    {
180        if ( chars != null )
181        {
182            for ( char c : chars )
183            {
184                digest.update( (byte) ( c >> 8 ) );
185                digest.update( (byte) ( c & 0xFF ) );
186            }
187        }
188    }
189
190    /**
191     * Updates the digest with the specified bytes.
192     * 
193     * @param bytes The bytes to update the digest with, may be {@code null}.
194     */
195    public void update( byte... bytes )
196    {
197        if ( bytes != null )
198        {
199            digest.update( bytes );
200        }
201    }
202
203    private String digest()
204    {
205        byte[] bytes = digest.digest();
206        StringBuilder buffer = new StringBuilder( bytes.length * 2 );
207        for ( byte aByte : bytes )
208        {
209            int b = aByte & 0xFF;
210            if ( b < 0x10 )
211            {
212                buffer.append( '0' );
213            }
214            buffer.append( Integer.toHexString( b ) );
215        }
216        return buffer.toString();
217    }
218
219}