PageRenderTime 47ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 1ms

/tests/e2e-client/src/test/java/org/glassfish/jersey/tests/e2e/client/HttpAuthorizationTest.java

http://github.com/jersey/jersey
Java | 567 lines | 422 code | 86 blank | 59 comment | 15 complexity | 20353f01dbf5e12485bcdc11bce8af17 MD5 | raw file
  1. /*
  2. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
  3. *
  4. * Copyright (c) 2013-2017 Oracle and/or its affiliates. All rights reserved.
  5. *
  6. * The contents of this file are subject to the terms of either the GNU
  7. * General Public License Version 2 only ("GPL") or the Common Development
  8. * and Distribution License("CDDL") (collectively, the "License"). You
  9. * may not use this file except in compliance with the License. You can
  10. * obtain a copy of the License at
  11. * https://oss.oracle.com/licenses/CDDL+GPL-1.1
  12. * or LICENSE.txt. See the License for the specific
  13. * language governing permissions and limitations under the License.
  14. *
  15. * When distributing the software, include this License Header Notice in each
  16. * file and include the License file at LICENSE.txt.
  17. *
  18. * GPL Classpath Exception:
  19. * Oracle designates this particular file as subject to the "Classpath"
  20. * exception as provided by Oracle in the GPL Version 2 section of the License
  21. * file that accompanied this code.
  22. *
  23. * Modifications:
  24. * If applicable, add the following below the License Header, with the fields
  25. * enclosed by brackets [] replaced by your own identifying information:
  26. * "Portions Copyright [year] [name of copyright owner]"
  27. *
  28. * Contributor(s):
  29. * If you wish your version of this file to be governed by only the CDDL or
  30. * only the GPL Version 2, indicate your decision by adding "[Contributor]
  31. * elects to include this software in this distribution under the [CDDL or GPL
  32. * Version 2] license." If you don't indicate a single choice of license, a
  33. * recipient has the option to distribute your version of this file under
  34. * either the CDDL, the GPL Version 2 or to extend the choice of license to
  35. * its licensees as provided above. However, if you add GPL Version 2 code
  36. * and therefore, elected the GPL Version 2 license, then the option applies
  37. * only if the new code is made subject to such option by the copyright
  38. * holder.
  39. */
  40. package org.glassfish.jersey.tests.e2e.client;
  41. import java.io.IOException;
  42. import java.lang.annotation.ElementType;
  43. import java.lang.annotation.Retention;
  44. import java.lang.annotation.RetentionPolicy;
  45. import java.lang.annotation.Target;
  46. import java.nio.charset.Charset;
  47. import java.security.Principal;
  48. import java.util.regex.Matcher;
  49. import java.util.regex.Pattern;
  50. import javax.ws.rs.GET;
  51. import javax.ws.rs.NameBinding;
  52. import javax.ws.rs.Path;
  53. import javax.ws.rs.client.WebTarget;
  54. import javax.ws.rs.container.ContainerRequestContext;
  55. import javax.ws.rs.container.ContainerRequestFilter;
  56. import javax.ws.rs.core.Application;
  57. import javax.ws.rs.core.Context;
  58. import javax.ws.rs.core.HttpHeaders;
  59. import javax.ws.rs.core.Response;
  60. import javax.ws.rs.core.SecurityContext;
  61. import org.glassfish.jersey.client.authentication.HttpAuthenticationFeature;
  62. import org.glassfish.jersey.internal.util.Base64;
  63. import org.glassfish.jersey.logging.LoggingFeature;
  64. import org.glassfish.jersey.server.ResourceConfig;
  65. import org.glassfish.jersey.test.JerseyTest;
  66. import org.glassfish.jersey.test.util.runner.ConcurrentRunner;
  67. import org.junit.Assert;
  68. import org.junit.Test;
  69. import org.junit.runner.RunWith;
  70. /**
  71. * Tests {@link org.glassfish.jersey.client.authentication.HttpAuthenticationFeature}.
  72. *
  73. * @author Miroslav Fuksa
  74. */
  75. @RunWith(ConcurrentRunner.class)
  76. public class HttpAuthorizationTest extends JerseyTest {
  77. @NameBinding
  78. @Target({ElementType.TYPE, ElementType.METHOD})
  79. @Retention(value = RetentionPolicy.RUNTIME)
  80. public static @interface Digest {
  81. }
  82. @NameBinding
  83. @Target({ElementType.TYPE, ElementType.METHOD})
  84. @Retention(value = RetentionPolicy.RUNTIME)
  85. public static @interface Basic {
  86. }
  87. @NameBinding
  88. @Target({ElementType.TYPE, ElementType.METHOD})
  89. @Retention(value = RetentionPolicy.RUNTIME)
  90. public static @interface Alternating {
  91. }
  92. /**
  93. * Alternates between BASIC and DIGEST (each is used for 2 requests).
  94. */
  95. @Alternating
  96. public static class AlternatingDigestBasicFilter implements ContainerRequestFilter {
  97. int counter = 0;
  98. @Override
  99. public void filter(ContainerRequestContext requestContext) throws IOException {
  100. if ((counter++ / 2) % 2 == 0) {
  101. new BasicFilter().filter(requestContext);
  102. } else {
  103. new DigestFilter().filter(requestContext);
  104. }
  105. }
  106. }
  107. @Digest
  108. public static class DigestFilter implements ContainerRequestFilter {
  109. @Override
  110. public void filter(ContainerRequestContext requestContext) throws IOException {
  111. final String authorization = requestContext.getHeaderString(HttpHeaders.AUTHORIZATION);
  112. if (authorization != null && authorization.trim().toUpperCase().startsWith("DIGEST")) {
  113. final Matcher match = Pattern.compile("username=\"([^\"]+)\"").matcher(authorization);
  114. if (!match.find()) {
  115. return;
  116. }
  117. final String username = match.group(1);
  118. requestContext.setSecurityContext(new SecurityContext() {
  119. @Override
  120. public Principal getUserPrincipal() {
  121. return new Principal() {
  122. @Override
  123. public String getName() {
  124. return username;
  125. }
  126. };
  127. }
  128. @Override
  129. public boolean isUserInRole(String role) {
  130. return false;
  131. }
  132. @Override
  133. public boolean isSecure() {
  134. return false;
  135. }
  136. @Override
  137. public String getAuthenticationScheme() {
  138. return "DIGEST";
  139. }
  140. });
  141. return;
  142. }
  143. requestContext.abortWith(Response.status(401).header(HttpHeaders.WWW_AUTHENTICATE,
  144. "Digest realm=\"my-realm\", domain=\"\", nonce=\"n9iv3MeSNkEfM3uJt2gnBUaWUbKAljxp\", algorithm=MD5, "
  145. + "qop=\"auth\", stale=false")
  146. .build());
  147. }
  148. }
  149. /**
  150. * Basic Auth: password must be the same as user name except first letter is capitalized.
  151. * Example: username "homer" -> password "Homer"
  152. */
  153. @Basic
  154. public static class BasicFilter implements ContainerRequestFilter {
  155. static final Charset CHARACTER_SET = Charset.forName("iso-8859-1");
  156. public static final String AUTH_SCHEME_CASE = "Auth-Scheme-Case";
  157. @Override
  158. public void filter(ContainerRequestContext request) throws IOException {
  159. String authHeader = request.getHeaders().getFirst(HttpHeaders.AUTHORIZATION);
  160. if (authHeader != null && authHeader.trim().toUpperCase().startsWith("BASIC")) {
  161. String decoded = new String(Base64.decode(authHeader.substring(6).getBytes()), CHARACTER_SET);
  162. // String decoded = Base64.decodeAsString(authHeader.substring(6));
  163. final String[] split = decoded.split(":");
  164. final String username = split[0];
  165. final String pwd = split[1];
  166. String capitalizedUserName = username.substring(0, 1).toUpperCase() + username.substring(1);
  167. if (capitalizedUserName.equals(pwd)) {
  168. request.setSecurityContext(new SecurityContext() {
  169. @Override
  170. public Principal getUserPrincipal() {
  171. return new Principal() {
  172. @Override
  173. public String getName() {
  174. return username;
  175. }
  176. };
  177. }
  178. @Override
  179. public boolean isUserInRole(String role) {
  180. return true;
  181. }
  182. @Override
  183. public boolean isSecure() {
  184. return false;
  185. }
  186. @Override
  187. public String getAuthenticationScheme() {
  188. return "BASIC";
  189. }
  190. });
  191. return;
  192. }
  193. }
  194. final String authSchemeCase = request.getHeaderString(AUTH_SCHEME_CASE);
  195. final String authScheme;
  196. if ("uppercase".equals(authSchemeCase)) {
  197. authScheme = "BASIC";
  198. } else if ("lowercase".equals(authSchemeCase)) {
  199. authScheme = "basic";
  200. } else {
  201. authScheme = "Basic";
  202. }
  203. request.abortWith(Response.status(401).header(HttpHeaders.WWW_AUTHENTICATE, authScheme).build());
  204. }
  205. }
  206. @Override
  207. protected Application configure() {
  208. ResourceConfig resourceConfig = new ResourceConfig(MyResource.class);
  209. resourceConfig.register(LoggingFeature.class);
  210. resourceConfig.register(new BasicFilter());
  211. resourceConfig.register(new DigestFilter());
  212. resourceConfig.register(new AlternatingDigestBasicFilter());
  213. return resourceConfig;
  214. }
  215. @Path("resource")
  216. public static class MyResource {
  217. @Context
  218. SecurityContext securityContext;
  219. @GET
  220. public String unsecure() {
  221. return "unsecure";
  222. }
  223. @GET
  224. @Path("basic")
  225. @Basic
  226. public String basic() {
  227. return securityContext.getAuthenticationScheme() + ":" + securityContext.getUserPrincipal().getName();
  228. }
  229. @GET
  230. @Path("digest")
  231. @Digest
  232. public String digest() {
  233. return securityContext.getAuthenticationScheme() + ":" + securityContext.getUserPrincipal().getName();
  234. }
  235. @GET
  236. @Path("alternating")
  237. @Alternating
  238. public String alternating() {
  239. return securityContext.getAuthenticationScheme() + ":" + securityContext.getUserPrincipal().getName();
  240. }
  241. }
  242. @Test
  243. public void testBasicPreemptive() {
  244. Response response = target().path("resource").path("basic")
  245. .register(HttpAuthenticationFeature.basicBuilder().credentials("homer", "Homer").build())
  246. .request().get();
  247. check(response, 200, "BASIC:homer");
  248. }
  249. @Test
  250. public void testBasicNonPreemptive() {
  251. Response response = target().path("resource").path("basic")
  252. .register(HttpAuthenticationFeature.basicBuilder().nonPreemptive().credentials("homer", "Homer").build())
  253. .request().get();
  254. check(response, 200, "BASIC:homer");
  255. }
  256. @Test
  257. public void testBasicNonPreemptiveWithEmptyPassword() {
  258. final WebTarget target = target().path("resource")
  259. .register(HttpAuthenticationFeature.basicBuilder().nonPreemptive().build());
  260. Response response = target.request().get();
  261. check(response, 200, "unsecure");
  262. try {
  263. response = target().path("resource").path("basic")
  264. .register(HttpAuthenticationFeature.basicBuilder().nonPreemptive().build())
  265. .request().get();
  266. Assert.fail("should throw an exception as credentials are missing");
  267. } catch (Exception e) {
  268. // ok
  269. }
  270. response = target.path("basic").request().property(HttpAuthenticationFeature.HTTP_AUTHENTICATION_BASIC_USERNAME, "bart")
  271. .property(HttpAuthenticationFeature.HTTP_AUTHENTICATION_BASIC_PASSWORD, "Bart")
  272. .get();
  273. check(response, 200, "BASIC:bart");
  274. }
  275. @Test
  276. public void testUniversalBasic() {
  277. Response response = target().path("resource").path("basic")
  278. .register(HttpAuthenticationFeature.universalBuilder().credentials("homer", "Homer").build())
  279. .request().get();
  280. check(response, 200, "BASIC:homer");
  281. }
  282. /**
  283. * Reproducer for JERSEY-2941: BasicAuthenticator#filterResponseAndAuthenticate: auth-scheme checks should be case
  284. * insensitve.
  285. */
  286. @Test
  287. public void testUniversalBasicCaseSensitivity() {
  288. Response response;
  289. response = target().path("resource").path("basic")
  290. .register(HttpAuthenticationFeature.universalBuilder().credentials("homer", "Homer").build())
  291. .request()
  292. // no AUTH_SCHEME_CASE header = mixed case
  293. .get();
  294. check(response, 200, "BASIC:homer");
  295. response = target().path("resource").path("basic")
  296. .register(HttpAuthenticationFeature.universalBuilder().credentials("homer", "Homer").build())
  297. .request()
  298. .header(BasicFilter.AUTH_SCHEME_CASE, "lowercase")
  299. .get();
  300. check(response, 200, "BASIC:homer");
  301. response = target().path("resource").path("basic")
  302. .register(HttpAuthenticationFeature.universalBuilder().credentials("homer", "Homer").build())
  303. .request()
  304. .header(BasicFilter.AUTH_SCHEME_CASE, "uppercase")
  305. .get();
  306. check(response, 200, "BASIC:homer");
  307. }
  308. @Test
  309. public void testUniversalBasicWrongPassword() {
  310. Response response = target().path("resource").path("basic")
  311. .register(HttpAuthenticationFeature.universalBuilder().credentials("homer", "FOO").build())
  312. .request().get();
  313. check(response, 401);
  314. }
  315. @Test
  316. public void testBasicWithDifferentCredentials() {
  317. final WebTarget target = target().path("resource").path("basic")
  318. .register(HttpAuthenticationFeature.basicBuilder().credentials("marge", "Marge").build());
  319. _testBasicWithDifferentCredentials(target);
  320. }
  321. @Test
  322. public void testBasicUniversalWithDifferentCredentials() {
  323. final WebTarget target = target().path("resource").path("basic")
  324. .register(HttpAuthenticationFeature.universalBuilder().credentials("marge", "Marge").build());
  325. _testBasicWithDifferentCredentials(target);
  326. }
  327. public void _testBasicWithDifferentCredentials(WebTarget target) {
  328. Response response = target
  329. .request().get();
  330. check(response, 200, "BASIC:marge");
  331. response = target.request().property(HttpAuthenticationFeature.HTTP_AUTHENTICATION_USERNAME, "bart")
  332. .property(HttpAuthenticationFeature.HTTP_AUTHENTICATION_PASSWORD, "Bart")
  333. .get();
  334. check(response, 200, "BASIC:bart");
  335. response = target.request().property(HttpAuthenticationFeature.HTTP_AUTHENTICATION_BASIC_USERNAME, "bart")
  336. .property(HttpAuthenticationFeature.HTTP_AUTHENTICATION_BASIC_PASSWORD, "Bart")
  337. .get();
  338. check(response, 200, "BASIC:bart");
  339. response = target.request().property(HttpAuthenticationFeature.HTTP_AUTHENTICATION_DIGEST_USERNAME, "bart")
  340. .property(HttpAuthenticationFeature.HTTP_AUTHENTICATION_DIGEST_PASSWORD, "Bart")
  341. .get();
  342. check(response, 200, "BASIC:marge");
  343. }
  344. @Test
  345. public void testDigest() {
  346. Response response = target().path("resource").path("digest")
  347. .register(HttpAuthenticationFeature.digest("homer", "Homer"))
  348. .request().get();
  349. check(response, 200, "DIGEST:homer");
  350. }
  351. @Test
  352. public void testDigestWithPasswords() {
  353. final WebTarget target = target().path("resource").path("digest")
  354. .register(HttpAuthenticationFeature.digest("homer", "Homer"));
  355. _testDigestWithPasswords(target);
  356. }
  357. @Test
  358. public void testUniversalDigestWithPasswords() {
  359. final WebTarget target = target().path("resource").path("digest")
  360. .register(HttpAuthenticationFeature.universalBuilder().credentials("homer", "Homer").build());
  361. _testDigestWithPasswords(target);
  362. }
  363. public void _testDigestWithPasswords(WebTarget target) {
  364. Response response = target.request().get();
  365. check(response, 200, "DIGEST:homer");
  366. response = target.request().property(HttpAuthenticationFeature.HTTP_AUTHENTICATION_DIGEST_USERNAME, "bart")
  367. .property(HttpAuthenticationFeature.HTTP_AUTHENTICATION_DIGEST_PASSWORD, "Bart")
  368. .get();
  369. check(response, 200, "DIGEST:bart");
  370. response = target.request().property(HttpAuthenticationFeature.HTTP_AUTHENTICATION_BASIC_USERNAME, "bart")
  371. .property(HttpAuthenticationFeature.HTTP_AUTHENTICATION_BASIC_PASSWORD, "Bart")
  372. .get();
  373. check(response, 200, "DIGEST:homer");
  374. response = target.request().property(HttpAuthenticationFeature.HTTP_AUTHENTICATION_USERNAME, "bart")
  375. .property(HttpAuthenticationFeature.HTTP_AUTHENTICATION_PASSWORD, "Bart")
  376. .get();
  377. check(response, 200, "DIGEST:bart");
  378. }
  379. @Test
  380. public void testDigestWithEmptyDefaultPassword() {
  381. final WebTarget target = target().path("resource")
  382. .register(HttpAuthenticationFeature.digest());
  383. _testDigestWithEmptyDefaultPassword(target);
  384. }
  385. @Test
  386. public void testDigestUniversalWithEmptyDefaultPassword() {
  387. final WebTarget target = target().path("resource")
  388. .register(HttpAuthenticationFeature.universalBuilder().build());
  389. _testDigestWithEmptyDefaultPassword(target);
  390. }
  391. public void _testDigestWithEmptyDefaultPassword(WebTarget target) {
  392. Response response = target.request().get();
  393. check(response, 200, "unsecure");
  394. response = target.request().property(HttpAuthenticationFeature.HTTP_AUTHENTICATION_DIGEST_USERNAME, "bart")
  395. .property(HttpAuthenticationFeature.HTTP_AUTHENTICATION_DIGEST_PASSWORD, "Bart")
  396. .get();
  397. check(response, 200, "unsecure");
  398. response = target.path("digest").request().property(HttpAuthenticationFeature.HTTP_AUTHENTICATION_DIGEST_USERNAME, "bart")
  399. .property(HttpAuthenticationFeature.HTTP_AUTHENTICATION_DIGEST_PASSWORD, "Bart")
  400. .get();
  401. check(response, 200, "DIGEST:bart");
  402. try {
  403. target.path("digest").request()
  404. .property(HttpAuthenticationFeature.HTTP_AUTHENTICATION_BASIC_USERNAME, "bart")
  405. .property(HttpAuthenticationFeature.HTTP_AUTHENTICATION_BASIC_PASSWORD, "Bart")
  406. .get();
  407. Assert.fail("should throw an exception as no credentials were supplied for digest auth");
  408. } catch (Exception e) {
  409. // ok
  410. }
  411. response = target.path("digest").request().property(HttpAuthenticationFeature.HTTP_AUTHENTICATION_USERNAME, "bart")
  412. .property(HttpAuthenticationFeature.HTTP_AUTHENTICATION_PASSWORD, "Bart")
  413. .get();
  414. check(response, 200, "DIGEST:bart");
  415. }
  416. private void check(Response response, int status, String entity) {
  417. Assert.assertEquals(status, response.getStatus());
  418. Assert.assertEquals(entity, response.readEntity(String.class));
  419. }
  420. private void check(Response response, int status) {
  421. Assert.assertEquals(status, response.getStatus());
  422. }
  423. @Test
  424. public void testDigestUniversalSimple() {
  425. Response response = target().path("resource").path("digest")
  426. .register(HttpAuthenticationFeature.universalBuilder().credentials("homer", "Homer").build())
  427. .request().get();
  428. check(response, 200, "DIGEST:homer");
  429. }
  430. @Test
  431. public void testDigestUniversalSimple2() {
  432. Response response = target().path("resource").path("digest")
  433. .register(HttpAuthenticationFeature.universalBuilder().credentialsForDigest("homer", "Homer").build())
  434. .request().get();
  435. check(response, 200, "DIGEST:homer");
  436. }
  437. @Test
  438. public void testDigestUniversalSimple3() {
  439. Response response = target().path("resource").path("digest")
  440. .register(HttpAuthenticationFeature.universalBuilder()
  441. .credentialsForDigest("homer", "Homer")
  442. .credentialsForBasic("foo", "bar")
  443. .build())
  444. .request().get();
  445. check(response, 200, "DIGEST:homer");
  446. }
  447. @Test
  448. public void testDigestUniversalSimple4() {
  449. Response response = target().path("resource").path("digest")
  450. .register(HttpAuthenticationFeature.universal("homer", "Homer"))
  451. .request().get();
  452. check(response, 200, "DIGEST:homer");
  453. }
  454. @Test
  455. public void testUniversal() {
  456. final WebTarget target = target().path("resource")
  457. .register(HttpAuthenticationFeature.universal("homer", "Homer"));
  458. check(target.request().get(), 200, "unsecure");
  459. check(target.path("digest").request().get(), 200, "DIGEST:homer");
  460. check(target.path("basic").request().get(), 200, "BASIC:homer");
  461. check(target.path("basic").request().get(), 200, "BASIC:homer");
  462. check(target.path("digest").request().get(), 200, "DIGEST:homer");
  463. check(target.path("digest").request().get(), 200, "DIGEST:homer");
  464. check(target.path("digest").request().get(), 200, "DIGEST:homer");
  465. check(target.path("basic").request().get(), 200, "BASIC:homer");
  466. check(target.path("basic").request().property(HttpAuthenticationFeature.HTTP_AUTHENTICATION_DIGEST_USERNAME, "bart")
  467. .property(HttpAuthenticationFeature.HTTP_AUTHENTICATION_DIGEST_PASSWORD, "Bart").get(), 200, "BASIC:homer");
  468. check(target.path("digest").request().property(HttpAuthenticationFeature.HTTP_AUTHENTICATION_DIGEST_USERNAME, "bart")
  469. .property(HttpAuthenticationFeature.HTTP_AUTHENTICATION_DIGEST_PASSWORD, "Bart").get(), 200, "DIGEST:bart");
  470. check(target.path("digest").request().property(HttpAuthenticationFeature.HTTP_AUTHENTICATION_USERNAME, "bart")
  471. .property(HttpAuthenticationFeature.HTTP_AUTHENTICATION_PASSWORD, "Bart").get(), 200, "DIGEST:bart");
  472. check(target.path("alternating").request().get(), 200, "BASIC:homer");
  473. check(target.path("alternating").request().get(), 200, "DIGEST:homer");
  474. check(target.path("alternating").request().get(), 200, "BASIC:homer");
  475. check(target.path("basic").request().get(), 200, "BASIC:homer");
  476. check(target.path("alternating").request().property(HttpAuthenticationFeature.HTTP_AUTHENTICATION_USERNAME, "bart")
  477. .property(HttpAuthenticationFeature.HTTP_AUTHENTICATION_PASSWORD, "Bart").get(), 200, "DIGEST:bart");
  478. check(target.path("alternating").request().property(HttpAuthenticationFeature.HTTP_AUTHENTICATION_USERNAME, "bart")
  479. .property(HttpAuthenticationFeature.HTTP_AUTHENTICATION_PASSWORD, "Bart").get(), 200, "BASIC:bart");
  480. }
  481. }