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.spring;
25  
26  import static org.assertj.core.api.Assertions.assertThat;
27  
28  import com.sun.jna.WString;
29  import com.sun.jna.platform.win32.Advapi32Util;
30  import com.sun.jna.platform.win32.LMAccess;
31  import com.sun.jna.platform.win32.LMErr;
32  import com.sun.jna.platform.win32.Netapi32;
33  
34  import java.io.IOException;
35  import java.nio.charset.StandardCharsets;
36  import java.security.Principal;
37  import java.util.Base64;
38  
39  import javax.servlet.ServletException;
40  
41  import org.junit.jupiter.api.AfterEach;
42  import org.junit.jupiter.api.Assertions;
43  import org.junit.jupiter.api.Assumptions;
44  import org.junit.jupiter.api.BeforeEach;
45  import org.junit.jupiter.api.Test;
46  import org.springframework.security.core.Authentication;
47  import org.springframework.security.core.context.SecurityContextHolder;
48  
49  import waffle.mock.MockWindowsAccount;
50  import waffle.mock.http.RecordUserNameFilterChain;
51  import waffle.mock.http.SimpleHttpRequest;
52  import waffle.mock.http.SimpleHttpResponse;
53  import waffle.servlet.AutoDisposableWindowsPrincipal;
54  import waffle.servlet.WindowsPrincipal;
55  import waffle.servlet.spi.SecurityFilterProviderCollection;
56  import waffle.windows.auth.impl.WindowsAuthProviderImpl;
57  
58  /**
59   * The Class ImpersonateTest.
60   */
61  class ImpersonateTest {
62  
63      /** The filter. */
64      private waffle.spring.NegotiateSecurityFilter filter;
65  
66      /** The user info. */
67      private LMAccess.USER_INFO_1 userInfo;
68  
69      /** The result of net add user. */
70      private int resultOfNetAddUser;
71  
72      /**
73       * Sets the up.
74       */
75      @BeforeEach
76      void setUp() {
77          this.filter = new NegotiateSecurityFilter();
78          this.filter.setProvider(new SecurityFilterProviderCollection(new WindowsAuthProviderImpl()));
79  
80          this.userInfo = new LMAccess.USER_INFO_1();
81          this.userInfo.usri1_name = new WString(MockWindowsAccount.TEST_USER_NAME).toString();
82          this.userInfo.usri1_password = new WString(MockWindowsAccount.TEST_PASSWORD).toString();
83          this.userInfo.usri1_priv = LMAccess.USER_PRIV_USER;
84  
85          this.resultOfNetAddUser = Netapi32.INSTANCE.NetUserAdd(null, 1, this.userInfo, null);
86          Assumptions.assumeTrue(this.resultOfNetAddUser == LMErr.NERR_Success,
87                  "Unable to add user (need to be administrator to do this).");
88      }
89  
90      /**
91       * Tear down.
92       */
93      @AfterEach
94      void tearDown() {
95          this.filter.destroy();
96  
97          if (LMErr.NERR_Success == this.resultOfNetAddUser) {
98              Assertions.assertEquals(LMErr.NERR_Success, Netapi32.INSTANCE.NetUserDel(null, this.userInfo.usri1_name));
99          }
100     }
101 
102     /**
103      * Test impersonate enabled.
104      *
105      * @throws IOException
106      *             Signals that an I/O exception has occurred.
107      * @throws ServletException
108      *             the servlet exception
109      */
110     @Test
111     void testImpersonateEnabled() throws IOException, ServletException {
112 
113         Assertions.assertNotEquals("Current user shouldn't be the test user prior to the test",
114                 MockWindowsAccount.TEST_USER_NAME, Advapi32Util.getUserName());
115 
116         final SimpleHttpRequest request = new SimpleHttpRequest();
117         request.setMethod("GET");
118         final String userHeaderValue = MockWindowsAccount.TEST_USER_NAME + ":" + MockWindowsAccount.TEST_PASSWORD;
119         final String basicAuthHeader = "Basic "
120                 + Base64.getEncoder().encodeToString(userHeaderValue.getBytes(StandardCharsets.UTF_8));
121         request.addHeader("Authorization", basicAuthHeader);
122 
123         final SimpleHttpResponse response = new SimpleHttpResponse();
124         final RecordUserNameFilterChain filterChain = new RecordUserNameFilterChain();
125 
126         this.filter.setImpersonate(true);
127         this.filter.doFilter(request, response, filterChain);
128 
129         final Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
130         Assertions.assertTrue(authentication.isAuthenticated(), "Test user should be authenticated");
131 
132         final Principal principal = (Principal) authentication.getPrincipal();
133         assertThat(principal).isInstanceOf(AutoDisposableWindowsPrincipal.class);
134         final AutoDisposableWindowsPrincipal windowsPrincipal = (AutoDisposableWindowsPrincipal) principal;
135         try {
136             Assertions.assertEquals(MockWindowsAccount.TEST_USER_NAME, filterChain.getUserName(),
137                     "Test user should be impersonated");
138             Assertions.assertNotEquals(MockWindowsAccount.TEST_USER_NAME, Advapi32Util.getUserName(),
139                     "Impersonation context should have been reverted");
140         } finally {
141             windowsPrincipal.getIdentity().dispose();
142         }
143     }
144 
145     /**
146      * Test impersonate disabled.
147      *
148      * @throws IOException
149      *             Signals that an I/O exception has occurred.
150      * @throws ServletException
151      *             the servlet exception
152      */
153     @Test
154     void testImpersonateDisabled() throws IOException, ServletException {
155 
156         Assertions.assertNotEquals("Current user shouldn't be the test user prior to the test",
157                 MockWindowsAccount.TEST_USER_NAME, Advapi32Util.getUserName());
158         final SimpleHttpRequest request = new SimpleHttpRequest();
159         request.setMethod("GET");
160         final String userHeaderValue = MockWindowsAccount.TEST_USER_NAME + ":" + MockWindowsAccount.TEST_PASSWORD;
161         final String basicAuthHeader = "Basic "
162                 + Base64.getEncoder().encodeToString(userHeaderValue.getBytes(StandardCharsets.UTF_8));
163         request.addHeader("Authorization", basicAuthHeader);
164         final SimpleHttpResponse response = new SimpleHttpResponse();
165         final RecordUserNameFilterChain filterChain = new RecordUserNameFilterChain();
166 
167         this.filter.setImpersonate(false);
168         this.filter.doFilter(request, response, filterChain);
169 
170         final Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
171         Assertions.assertTrue(authentication.isAuthenticated(), "Test user should be authenticated");
172 
173         final Principal principal = (Principal) authentication.getPrincipal();
174         assertThat(principal).isInstanceOf(WindowsPrincipal.class);
175         final WindowsPrincipal windowsPrincipal = (WindowsPrincipal) principal;
176         try {
177             Assertions.assertNotEquals(MockWindowsAccount.TEST_USER_NAME, filterChain.getUserName(),
178                     "Test user should not be impersonated");
179             Assertions.assertNotEquals(MockWindowsAccount.TEST_USER_NAME, Advapi32Util.getUserName(),
180                     "Impersonation context should have been reverted");
181         } finally {
182             windowsPrincipal.getIdentity().dispose();
183         }
184     }
185 }