View Javadoc
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 }