]> source.dussan.org Git - archiva.git/blob
5b57ca3e879d2aeae00755f82a2e704273226757
[archiva.git] /
1 package org.codehaus.redback.integration.filter.authentication.digest;
2
3 /*
4  * Licensed to the Apache Software Foundation (ASF) under one
5  * or more contributor license agreements.  See the NOTICE file
6  * distributed with this work for additional information
7  * regarding copyright ownership.  The ASF licenses this file
8  * to you under the Apache License, Version 2.0 (the
9  * "License"); you may not use this file except in compliance
10  * with the License.  You may obtain a copy of the License at
11  *
12  * http://www.apache.org/licenses/LICENSE-2.0
13  *
14  * Unless required by applicable law or agreed to in writing,
15  * software distributed under the License is distributed on an
16  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17  * KIND, either express or implied.  See the License for the
18  * specific language governing permissions and limitations
19  * under the License.
20  */
21
22 import org.apache.archiva.redback.authentication.AuthenticationException;
23 import org.apache.archiva.redback.policy.MustChangePasswordException;
24 import org.apache.archiva.redback.users.User;
25 import org.apache.commons.codec.binary.Base64;
26 import org.apache.archiva.redback.authentication.AuthenticationResult;
27 import org.apache.archiva.redback.authentication.TokenBasedAuthenticationDataSource;
28 import org.apache.archiva.redback.policy.AccountLockedException;
29 import org.apache.archiva.redback.users.UserManager;
30 import org.apache.archiva.redback.users.UserNotFoundException;
31 import org.codehaus.plexus.util.StringUtils;
32 import org.codehaus.redback.integration.filter.authentication.HttpAuthenticationException;
33 import org.codehaus.redback.integration.filter.authentication.HttpAuthenticator;
34 import org.springframework.stereotype.Service;
35
36 import javax.inject.Inject;
37 import javax.inject.Named;
38 import javax.servlet.http.HttpServletRequest;
39 import javax.servlet.http.HttpServletResponse;
40 import javax.servlet.http.HttpSession;
41 import java.io.IOException;
42
43 /**
44  * HttpDigestAuthentication methods for working with <a href="http://www.faqs.org/rfcs/rfc2617.html">RFC 2617 HTTP Authentication</a>.
45  *
46  * @author <a href="mailto:joakim@erdfelt.com">Joakim Erdfelt</a>
47  * @version $Id$
48  */
49 @Service("httpAuthenticator#digest")
50 public class HttpDigestAuthentication
51     extends HttpAuthenticator
52 {
53     @Inject
54     @Named(value="userManager#configurable")
55     private UserManager userManager;
56
57     /**
58      *
59      */
60     private int nonceLifetimeSeconds = 300;
61
62     /**
63      * NOTE: Must be alphanumeric.
64      *
65      *
66      */
67     private String digestKey ="OrycteropusAfer";
68
69     private String realm;
70
71     public String getId()
72     {
73         return HttpDigestAuthentication.class.getName();
74     }
75
76     public AuthenticationResult getAuthenticationResult( HttpServletRequest request, HttpServletResponse response )
77         throws AuthenticationException, AccountLockedException, MustChangePasswordException
78     {
79         HttpSession httpSession = request.getSession( true );
80         if ( isAlreadyAuthenticated( httpSession ) )
81         {
82             return getSecuritySession( httpSession ).getAuthenticationResult();
83         }
84
85         TokenBasedAuthenticationDataSource authDataSource = new TokenBasedAuthenticationDataSource();
86         String authHeader = request.getHeader( "Authorization" );
87
88         // in tomcat this is : authorization=Basic YWRtaW46TWFuYWdlMDc=
89         if ( authHeader == null )
90         {
91             authHeader = request.getHeader( "authorization" );
92         }
93
94         if ( ( authHeader != null ) && authHeader.startsWith( "Digest " ) )
95         {
96             String rawDigestHeader = authHeader.substring( 7 );
97
98             HttpDigestHeader digestHeader = new HttpDigestHeader();
99             digestHeader.parseClientHeader( rawDigestHeader, getRealm(), digestKey );
100
101             // Lookup password for presented username
102             User user = findUser( digestHeader.username );
103             authDataSource.setPrincipal( user.getPrincipal().toString() );
104
105             String serverSideHash = generateDigestHash( digestHeader, user.getPassword(), request.getMethod() );
106
107             if ( !StringUtils.equals( serverSideHash, digestHeader.response ) )
108             {
109                 throw new HttpAuthenticationException( "Digest response was invalid." );
110             }
111         }
112
113         return super.authenticate( authDataSource, httpSession );
114     }
115
116     public User findUser( String username )
117         throws HttpAuthenticationException
118     {
119         try
120         {
121             return userManager.findUser( username );
122         }
123         catch ( UserNotFoundException e )
124         {
125             String msg = "Unable to find primary user '" + username + "'.";
126             log.error( msg, e );
127             throw new HttpAuthenticationException( msg, e );
128         }
129     }
130
131     /**
132      * Issue HTTP Digest Authentication Challenge
133      *
134      * @param request   the request to use.
135      * @param response  the response to use.
136      * @param realmName the realm name to state.
137      * @param exception the exception to base the message off of.
138      * @throws IOException if there was a problem with the {@link HttpServletResponse#sendError(int,String)} call.
139      */
140     public void challenge( HttpServletRequest request, HttpServletResponse response, String realmName,
141                            AuthenticationException exception )
142         throws IOException
143     {
144         // The Challenge Header
145         StringBuilder authHeader = new StringBuilder();
146         authHeader.append( "Digest " );
147         // [REQUIRED] The name to appear in the dialog box to the user.
148         authHeader.append( "realm=\"" ).append( realmName ).append( "\"" );
149         // [OPTIONAL] We do not use the optional 'domain' header.
150         // authHeader.append( "domain=\"" ).append( domain ).append( "\"" );
151         // [REQUIRED] Nonce specification.
152         authHeader.append( ", nonce=\"" );
153         long timestamp = System.currentTimeMillis() + ( nonceLifetimeSeconds * 1000 );
154         // Not using ETag from RFC 2617 intentionally.
155         String hraw = String.valueOf( timestamp ) + ":" + digestKey;
156         String rawnonce = String.valueOf( timestamp ) + ":" + Digest.md5Hex( hraw );
157         authHeader.append( Base64.encodeBase64( rawnonce.getBytes() ) );
158         authHeader.append( "\"" );
159         // [REQUIRED] The RFC 2617 Quality of Protection.
160         // MSIE Appears to only support 'auth'
161         // Do not use 'opaque' here. (Your MSIE users will have issues)
162         authHeader.append( ", qop=\"auth\"" );
163         // [BROKEN] since we force the 'auth' qop we cannot use the opaque option.
164         // authHeader.append( ", opaque=\"").append(opaqueString).append("\"");
165
166         // [OPTIONAL] Use of the stale option is reserved for expired nonce strings.
167         if ( exception instanceof NonceExpirationException )
168         {
169             authHeader.append( ", stale=\"true\"" );
170         }
171
172         // [OPTIONAL] We do not use the optional Algorithm header.
173         // authHeader.append( ", algorithm=\"MD5\"");
174
175         response.addHeader( "WWW-Authenticate", authHeader.toString() );
176         response.sendError( HttpServletResponse.SC_UNAUTHORIZED, exception.getMessage() );
177     }
178
179     private String generateDigestHash( HttpDigestHeader digestHeader, String password, String httpMethod )
180     {
181         String a1 = Digest.md5Hex( digestHeader.username + ":" + realm + ":" + password );
182         String a2 = Digest.md5Hex( httpMethod + ":" + digestHeader.uri );
183
184         String digest;
185
186         if ( StringUtils.isEmpty( digestHeader.qop ) )
187         {
188             digest = a1 + ":" + digestHeader.nonce + ":" + a2;
189         }
190         else if ( StringUtils.equals( "auth", digestHeader.qop ) )
191         {
192             digest = a1 + ":" + digestHeader.nonce + ":" + digestHeader.nc + ":" + digestHeader.cnonce + ":"
193                 + digestHeader.qop + ":" + a2;
194         }
195         else
196         {
197             throw new IllegalStateException( "Http Digest Parameter [qop] with value of [" + digestHeader.qop
198                 + "] is unsupported." );
199         }
200
201         return Digest.md5Hex( digest );
202     }
203
204     public String getRealm()
205     {
206         return realm;
207     }
208
209     public void setRealm( String realm )
210     {
211         this.realm = realm;
212     }
213
214 }