1 package de.dlr.shepard.auth.security;
2
3 import static org.junit.jupiter.api.Assertions.assertEquals;
4 import static org.junit.jupiter.api.Assertions.assertThrows;
5 import static org.junit.jupiter.api.Assertions.assertTrue;
6 import static org.mockito.ArgumentMatchers.eq;
7 import static org.mockito.Mockito.doAnswer;
8 import static org.mockito.Mockito.doReturn;
9 import static org.mockito.Mockito.mock;
10 import static org.mockito.Mockito.verify;
11 import static org.mockito.Mockito.when;
12
13 import de.dlr.shepard.BaseTestCase;
14 import de.dlr.shepard.common.exceptions.ShepardProcessingException;
15 import jakarta.ws.rs.ProcessingException;
16 import jakarta.ws.rs.client.Client;
17 import jakarta.ws.rs.client.Invocation;
18 import jakarta.ws.rs.client.Invocation.Builder;
19 import jakarta.ws.rs.client.WebTarget;
20 import jakarta.ws.rs.core.HttpHeaders;
21 import jakarta.ws.rs.core.MediaType;
22 import java.net.URI;
23 import org.apache.commons.lang3.reflect.FieldUtils;
24 import org.eclipse.microprofile.config.Config;
25 import org.eclipse.microprofile.config.ConfigProvider;
26 import org.junit.jupiter.api.AfterAll;
27 import org.junit.jupiter.api.BeforeAll;
28 import org.junit.jupiter.api.BeforeEach;
29 import org.junit.jupiter.api.Test;
30 import org.mockito.ArgumentCaptor;
31 import org.mockito.Captor;
32 import org.mockito.Mock;
33 import org.mockito.MockedStatic;
34 import org.mockito.Mockito;
35 import org.mockito.Spy;
36 import org.mockito.invocation.InvocationOnMock;
37 import org.mockito.stubbing.Answer;
38
39 public class UserinfoServiceTest extends BaseTestCase {
40
41 @Mock
42 private Client client;
43
44 @Mock
45 private WebTarget target;
46
47 @Mock
48 private Builder builder;
49
50 @Mock
51 private Invocation invocation;
52
53 @Spy
54 private UserinfoService service;
55
56 @Captor
57 private ArgumentCaptor<URI> uriCaptor;
58
59 @Captor
60 private ArgumentCaptor<String> urlCaptor;
61
62 @Captor
63 private ArgumentCaptor<String> headerCaptor;
64
65 private static MockedStatic<ConfigProvider> mockConfigProvider;
66
67 @BeforeAll
68 public static void mockConfigProvider() {
69 Config config = mock(Config.class);
70 mockConfigProvider = Mockito.mockStatic(ConfigProvider.class);
71 mockConfigProvider.when(ConfigProvider::getConfig).thenReturn(config);
72 when(config.getValue("oidc.authority", String.class)).thenReturn("https://my.oidc.provider.com/realm/");
73 }
74
75 @AfterAll
76 public static void removeMockConfigProvider() {
77 mockConfigProvider.close();
78 }
79
80 @BeforeEach
81 public void prepareSpy() throws IllegalAccessException {
82 FieldUtils.writeField(service, "client", client, true);
83 }
84
85 @BeforeEach
86 public void setUpClient() throws IllegalAccessException {
87 doReturn(target).when(client).target(uriCaptor.capture());
88 doReturn(target).when(client).target(urlCaptor.capture());
89 when(target.request(MediaType.APPLICATION_JSON)).thenReturn(builder);
90 doReturn(builder).when(builder).header(eq(HttpHeaders.AUTHORIZATION), headerCaptor.capture());
91 when(builder.buildGet()).thenReturn(invocation);
92 }
93
94 @Test
95 public void testFetchUser_Successful() throws IllegalAccessException {
96 var userinfo = new Userinfo("f:sub:name_fi", "first name", "first.name@example.com", "first", "name", "name_fi");
97
98 FieldUtils.writeField(service, "userinfoEndpoint", "https://userinfo.endpoint/userinfo", true);
99 when(invocation.invoke(Userinfo.class)).thenReturn(userinfo);
100 var actual = service.fetchUserinfo("Bearer myToken");
101 assertEquals(userinfo, actual);
102 assertEquals("https://userinfo.endpoint/userinfo", urlCaptor.getValue());
103 assertEquals("Bearer myToken", headerCaptor.getValue());
104 }
105
106 @Test
107 public void testFetchUser_InvokeInit() throws IllegalAccessException {
108 var userinfo = new Userinfo("f:sub:name_fi", "first name", "first.name@example.com", "first", "name", "name_fi");
109
110 doAnswer(
111 new Answer<>() {
112 @Override
113 public Object answer(InvocationOnMock invocation) throws Throwable {
114 FieldUtils.writeField(service, "userinfoEndpoint", "https://userinfo.endpoint/userinfo", true);
115 return null;
116 }
117 }
118 )
119 .when(service)
120 .init();
121 when(invocation.invoke(Userinfo.class)).thenReturn(userinfo);
122
123 var actual = service.fetchUserinfo("Bearer myToken");
124 assertEquals(userinfo, actual);
125 verify(service).init();
126 }
127
128 @Test
129 public void testInit_Successful() {
130 var conf = new OpenIdConfiguration(
131 "iss",
132 "auth",
133 "userinfo.endpoint",
134 "jwks",
135 new String[0],
136 new String[0],
137 new String[0]
138 );
139
140 when(invocation.invoke(OpenIdConfiguration.class)).thenReturn(conf);
141
142 service.init();
143 assertEquals(
144 URI.create("https://my.oidc.provider.com/realm/.well-known/openid-configuration"),
145 uriCaptor.getValue()
146 );
147 assertTrue(headerCaptor.getAllValues().isEmpty());
148 }
149
150 @Test
151 public void testInit_ReturnNull() {
152 when(invocation.invoke(OpenIdConfiguration.class)).thenReturn(null);
153
154 assertThrows(ShepardProcessingException.class, service::init);
155 }
156
157 @Test
158 public void testInit_ProcessingException() {
159 when(invocation.invoke(OpenIdConfiguration.class)).thenThrow(new ProcessingException("Message"));
160
161 assertThrows(ShepardProcessingException.class, service::init);
162 }
163
164 @Test
165 public void testFetchUser_ReturnNull() throws IllegalAccessException {
166 FieldUtils.writeField(service, "userinfoEndpoint", "https://userinfo.endpoint/userinfo", true);
167 when(invocation.invoke(Userinfo.class)).thenReturn(null);
168
169 assertThrows(ShepardProcessingException.class, () -> service.fetchUserinfo("Bearer myToken"));
170 }
171
172 @Test
173 public void testFetchUser_ProcessingException() throws IllegalAccessException {
174 FieldUtils.writeField(service, "userinfoEndpoint", "https://userinfo.endpoint/userinfo", true);
175 when(invocation.invoke(Userinfo.class)).thenThrow(new ProcessingException("Message"));
176
177 assertThrows(ShepardProcessingException.class, () -> service.fetchUserinfo("Bearer myToken"));
178 }
179 }