View Javadoc
1   /*
2    * MIT License
3    *
4    * Copyright (c) 2010-2024 The Waffle Project Contributors: https://github.com/Waffle/waffle/graphs/contributors
5    *
6    * Permission is hereby granted, free of charge, to any person obtaining a copy
7    * of this software and associated documentation files (the "Software"), to deal
8    * in the Software without restriction, including without limitation the rights
9    * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10   * copies of the Software, and to permit persons to whom the Software is
11   * furnished to do so, subject to the following conditions:
12   *
13   * The above copyright notice and this permission notice shall be included in all
14   * copies or substantial portions of the Software.
15   *
16   * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17   * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18   * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19   * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20   * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21   * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22   * SOFTWARE.
23   */
24  package waffle.util;
25  
26  import java.util.Base64;
27  import java.util.Locale;
28  
29  import javax.servlet.http.HttpServletRequest;
30  
31  import org.slf4j.Logger;
32  import org.slf4j.LoggerFactory;
33  
34  /**
35   * Authorization header.
36   */
37  public class AuthorizationHeader {
38  
39      /** The logger. */
40      private static final Logger LOGGER = LoggerFactory.getLogger(AuthorizationHeader.class);
41  
42      /** The request. */
43      private final HttpServletRequest request;
44  
45      /**
46       * Instantiates a new authorization header.
47       *
48       * @param httpServletRequest
49       *            the http servlet request
50       */
51      public AuthorizationHeader(final HttpServletRequest httpServletRequest) {
52          this.request = httpServletRequest;
53      }
54  
55      /**
56       * Gets the header.
57       *
58       * @return the header
59       */
60      public String getHeader() {
61          return this.request.getHeader("Authorization");
62      }
63  
64      /**
65       * Checks if is null.
66       *
67       * @return true, if is null
68       */
69      public boolean isNull() {
70          return this.getHeader() == null || this.getHeader().length() == 0;
71      }
72  
73      /**
74       * Returns a supported security package string.
75       *
76       * <pre>
77       * Authorization: NTLM the_token
78       * Authorization: Negotiate the_token
79       * Authorization: Bearer the_token
80       * </pre>
81       *
82       * @return AuthenticationScheme as SecurityPackage e.g. Negotiate, NTLM, Bearer.
83       */
84      public String getSecurityPackage() {
85          final String header = this.getHeader();
86  
87          if (header == null) {
88              throw new RuntimeException("Missing Authorization: header");
89          }
90  
91          final int space = header.indexOf(' ');
92          if (space > 0) {
93              return header.substring(0, space);
94          }
95  
96          throw new RuntimeException("Invalid Authorization header: " + header);
97      }
98  
99      @Override
100     public String toString() {
101         return this.isNull() ? "<none>" : this.getHeader();
102     }
103 
104     /**
105      * Gets the token.
106      *
107      * @return the token
108      */
109     public String getToken() {
110         return this.getHeader().substring(this.getSecurityPackage().length() + 1);
111     }
112 
113     /**
114      * Gets the token bytes.
115      *
116      * @return the token bytes
117      */
118     public byte[] getTokenBytes() {
119         try {
120             return Base64.getDecoder().decode(this.getToken());
121         } catch (final IllegalArgumentException e) {
122             AuthorizationHeader.LOGGER.debug("", e);
123             throw new RuntimeException("Invalid authorization header.");
124         }
125     }
126 
127     /**
128      * Checks if is ntlm type1 message.
129      *
130      * @return true, if is ntlm type1 message
131      */
132     public boolean isNtlmType1Message() {
133         if (this.isNull()) {
134             return false;
135         }
136 
137         final byte[] tokenBytes = this.getTokenBytes();
138         if (!NtlmMessage.isNtlmMessage(tokenBytes)) {
139             return false;
140         }
141 
142         return 1 == NtlmMessage.getMessageType(tokenBytes);
143     }
144 
145     /**
146      * Checks if is SP nego message.
147      *
148      * @return true, if is SP nego message that contains NegTokenInit
149      */
150     public boolean isSPNegTokenInitMessage() {
151 
152         if (this.isNull()) {
153             return false;
154         }
155 
156         final byte[] tokenBytes = this.getTokenBytes();
157         return SPNegoMessage.isNegTokenInit(tokenBytes);
158     }
159 
160     /**
161      * When using NTLM authentication and the browser is making a POST request, it preemptively sends a Type 2
162      * authentication message (without the POSTed data). The server responds with a 401, and the browser sends a Type 3
163      * request with the POSTed data. This is to avoid the situation where user's credentials might be potentially
164      * invalid, and all this data is being POSTed across the wire.
165      *
166      * @return True if request is an NTLM POST, PUT, or DELETE with an Authorization header and no data.
167      */
168     public boolean isNtlmType1PostAuthorizationHeader() {
169         if (!"POST".equals(this.request.getMethod()) && !"PUT".equals(this.request.getMethod())
170                 && !"DELETE".equals(this.request.getMethod())) {
171             return false;
172         }
173 
174         if (this.request.getContentLength() != 0) {
175             return false;
176         }
177 
178         return this.isNtlmType1Message() || this.isSPNegTokenInitMessage();
179     }
180 
181     /**
182      * Is Bearer Authorization Header will return true if 'BEARER' exists.
183      *
184      * @return True if header contains 'BEARER' header.
185      */
186     public boolean isBearerAuthorizationHeader() {
187         if (this.isNull()) {
188             return false;
189         }
190 
191         return this.getSecurityPackage().toUpperCase(Locale.ENGLISH).equalsIgnoreCase("BEARER");
192     }
193 }