Skip to content

Commit 0aef900

Browse files
authored
RANGER-5331: Test Cases for unixauthclient & unixauthservice Module (#675)
1 parent d6c2516 commit 0aef900

File tree

11 files changed

+1587
-0
lines changed

11 files changed

+1587
-0
lines changed

unixauthclient/pom.xml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,18 @@
102102
<artifactId>libpam4j</artifactId>
103103
<version>${libpam4j.version}</version>
104104
</dependency>
105+
<dependency>
106+
<groupId>org.mockito</groupId>
107+
<artifactId>mockito-inline</artifactId>
108+
<version>${mockito.version}</version>
109+
<scope>test</scope>
110+
</dependency>
111+
<dependency>
112+
<groupId>org.mockito</groupId>
113+
<artifactId>mockito-junit-jupiter</artifactId>
114+
<version>${mockito.version}</version>
115+
<scope>test</scope>
116+
</dependency>
105117
<dependency>
106118
<groupId>org.slf4j</groupId>
107119
<artifactId>log4j-over-slf4j</artifactId>
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.apache.ranger.authentication.unix.jaas;
20+
21+
import org.junit.jupiter.api.MethodOrderer;
22+
import org.junit.jupiter.api.Test;
23+
import org.junit.jupiter.api.TestMethodOrder;
24+
import org.junit.jupiter.api.extension.ExtendWith;
25+
import org.mockito.junit.jupiter.MockitoExtension;
26+
27+
import javax.security.auth.callback.Callback;
28+
import javax.security.auth.callback.NameCallback;
29+
import javax.security.auth.callback.PasswordCallback;
30+
import javax.security.auth.callback.UnsupportedCallbackException;
31+
32+
import java.io.ByteArrayInputStream;
33+
import java.io.IOException;
34+
import java.nio.charset.StandardCharsets;
35+
36+
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
37+
import static org.junit.jupiter.api.Assertions.assertEquals;
38+
39+
/**
40+
* @generated by Cursor
41+
* @description : Unit Test cases for ConsolePromptCallbackHandler
42+
*/
43+
44+
@ExtendWith(MockitoExtension.class)
45+
@TestMethodOrder(MethodOrderer.MethodName.class)
46+
public class TestConsolePromptCallbackHandler {
47+
@Test
48+
public void test01_handle_setsNameAndPassword() throws IOException, UnsupportedCallbackException {
49+
String input = "alice\nsecret\n";
50+
System.setIn(new ByteArrayInputStream(input.getBytes(StandardCharsets.UTF_8)));
51+
52+
ConsolePromptCallbackHandler handler = new ConsolePromptCallbackHandler();
53+
NameCallback nameCallback = new NameCallback("User: ");
54+
PasswordCallback passwordCallback = new PasswordCallback("Pass: ", false);
55+
56+
handler.handle(new Callback[] {nameCallback, passwordCallback});
57+
58+
assertEquals("alice", nameCallback.getName());
59+
assertArrayEquals("secret".toCharArray(), passwordCallback.getPassword());
60+
}
61+
62+
@Test
63+
public void test02_handle_unknownCallback_doesNotThrow() throws IOException, UnsupportedCallbackException {
64+
System.setIn(new ByteArrayInputStream("\n".getBytes(StandardCharsets.UTF_8)));
65+
ConsolePromptCallbackHandler handler = new ConsolePromptCallbackHandler();
66+
Callback unknown = new Callback() {};
67+
handler.handle(new Callback[] {unknown});
68+
}
69+
}
Lines changed: 281 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,281 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.apache.ranger.authentication.unix.jaas;
20+
21+
import org.junit.jupiter.api.MethodOrderer;
22+
import org.junit.jupiter.api.Test;
23+
import org.junit.jupiter.api.TestMethodOrder;
24+
import org.junit.jupiter.api.extension.ExtendWith;
25+
import org.jvnet.libpam.PAM;
26+
import org.jvnet.libpam.UnixUser;
27+
import org.mockito.Mockito;
28+
import org.mockito.junit.jupiter.MockitoExtension;
29+
30+
import javax.security.auth.Subject;
31+
import javax.security.auth.callback.Callback;
32+
import javax.security.auth.callback.CallbackHandler;
33+
import javax.security.auth.callback.NameCallback;
34+
import javax.security.auth.callback.PasswordCallback;
35+
import javax.security.auth.login.FailedLoginException;
36+
import javax.security.auth.login.LoginException;
37+
38+
import java.lang.reflect.Field;
39+
import java.lang.reflect.InvocationTargetException;
40+
import java.lang.reflect.Method;
41+
import java.security.Principal;
42+
import java.util.Collections;
43+
import java.util.HashMap;
44+
import java.util.Map;
45+
import java.util.Set;
46+
47+
import static org.junit.jupiter.api.Assertions.assertEquals;
48+
import static org.junit.jupiter.api.Assertions.assertFalse;
49+
import static org.junit.jupiter.api.Assertions.assertNotNull;
50+
import static org.junit.jupiter.api.Assertions.assertThrows;
51+
import static org.junit.jupiter.api.Assertions.assertTrue;
52+
53+
/**
54+
* @generated by Cursor
55+
* @description : Unit Test cases for PamLoginModule
56+
*/
57+
58+
@ExtendWith(MockitoExtension.class)
59+
@TestMethodOrder(MethodOrderer.MethodName.class)
60+
public class TestPamLoginModule {
61+
@Test
62+
public void test01_initialize_withoutService_throwsOnLogin() throws Exception {
63+
PamLoginModule m = new PamLoginModule();
64+
Subject subject = new Subject();
65+
Map<String, Object> opts = new HashMap<>();
66+
m.initialize(subject, creds("alice", "secret"), new HashMap<>(), opts);
67+
setField(m, "options", new HashMap<>());
68+
assertThrows(LoginException.class, m::login);
69+
}
70+
71+
@Test
72+
public void test02_commit_addsPrincipal_and_logout_clears_without_realPam() throws Exception {
73+
PamLoginModule m = new PamLoginModule();
74+
Subject subject = new Subject();
75+
UnixUser user = Mockito.mock(UnixUser.class);
76+
Mockito.when(user.getUserName()).thenReturn("alice");
77+
Mockito.when(user.getGecos()).thenReturn("");
78+
Mockito.when(user.getDir()).thenReturn("");
79+
Mockito.when(user.getShell()).thenReturn("");
80+
Mockito.when(user.getUID()).thenReturn(0);
81+
Mockito.when(user.getGID()).thenReturn(0);
82+
Mockito.when(user.getGroups()).thenReturn(Collections.emptySet());
83+
84+
setField(m, "subject", subject);
85+
setField(m, "authSucceeded", true);
86+
setField(m, "principal", new PamPrincipal(user));
87+
setField(m, "pam", Mockito.mock(PAM.class));
88+
setField(m, "passwordChar", new char[] {'x'});
89+
90+
assertTrue(m.commit());
91+
Set<Principal> principals = subject.getPrincipals();
92+
assertTrue(principals.stream().anyMatch(p -> p instanceof PamPrincipal && p.getName().equals("alice")));
93+
94+
assertTrue(m.logout());
95+
assertTrue(subject.getPrincipals().isEmpty());
96+
}
97+
98+
@Test
99+
public void test03_login_throwsLoginException_withRealPam() throws Exception {
100+
PamLoginModule m = new PamLoginModule();
101+
Subject subject = new Subject();
102+
Map<String, Object> opts = new HashMap<>();
103+
opts.put(PamLoginModule.SERVICE_KEY, "sshd");
104+
CallbackHandler cb = creds("alice", "bad");
105+
m.initialize(subject, cb, new HashMap<>(), opts);
106+
setField(m, "options", opts);
107+
setField(m, "callbackHandler", cb);
108+
assertThrows(LoginException.class, m::login);
109+
}
110+
111+
@Test
112+
public void test04_abort_withoutAuth_returnsFalse() throws Exception {
113+
PamLoginModule m = new PamLoginModule();
114+
Subject subject = new Subject();
115+
Map<String, Object> opts = new HashMap<>();
116+
opts.put(PamLoginModule.SERVICE_KEY, "sshd");
117+
CallbackHandler cb = creds("u", "p");
118+
m.initialize(subject, cb, new HashMap<>(), opts);
119+
setField(m, "options", opts);
120+
setField(m, "subject", subject);
121+
setField(m, "callbackHandler", cb);
122+
assertFalse(m.abort());
123+
}
124+
125+
@Test
126+
public void test05_abort_afterSuccess_cleansUpAndReturnsTrue() throws Exception {
127+
PamLoginModule m = new PamLoginModule();
128+
setField(m, "authSucceeded", true);
129+
PAM pam = Mockito.mock(PAM.class);
130+
setField(m, "pam", pam);
131+
setField(m, "passwordChar", new char[] {'x'});
132+
assertTrue(m.abort());
133+
}
134+
135+
@Test
136+
public void test06_commit_returnsFalse_whenAuthNotSucceeded() throws Exception {
137+
PamLoginModule m = new PamLoginModule();
138+
setField(m, "authSucceeded", false);
139+
setField(m, "subject", new Subject());
140+
assertFalse(m.commit());
141+
}
142+
143+
@Test
144+
public void test07_commit_readOnlySubject_throws_and_cleansUp() throws Exception {
145+
PamLoginModule m = new PamLoginModule();
146+
Subject subject = new Subject();
147+
subject.setReadOnly();
148+
setField(m, "subject", subject);
149+
setField(m, "authSucceeded", true);
150+
setField(m, "principal", new PamPrincipal(Mockito.mock(UnixUser.class)));
151+
PAM pam = Mockito.mock(PAM.class);
152+
setField(m, "pam", pam);
153+
setField(m, "passwordChar", new char[] {'p'});
154+
assertThrows(LoginException.class, m::commit);
155+
Mockito.verify(pam).dispose();
156+
}
157+
158+
@Test
159+
public void test08_obtainUserAndPassword_noCallbackHandler_throws() throws Exception {
160+
PamLoginModule m = new PamLoginModule();
161+
setField(m, "callbackHandler", null);
162+
Method method = PamLoginModule.class.getDeclaredMethod("obtainUserAndPassword");
163+
method.setAccessible(true);
164+
assertThrows(LoginException.class, () -> invokeAndRethrowLoginException(m, method));
165+
}
166+
167+
@Test
168+
public void test09_obtainUserAndPassword_errorInCallbacks_throws() throws Exception {
169+
PamLoginModule m = new PamLoginModule();
170+
CallbackHandler cb = new CallbackHandler() {
171+
@Override
172+
public void handle(Callback[] callbacks) throws java.io.IOException {
173+
throw new java.io.IOException("boom");
174+
}
175+
};
176+
setField(m, "callbackHandler", cb);
177+
Method method = PamLoginModule.class.getDeclaredMethod("obtainUserAndPassword");
178+
method.setAccessible(true);
179+
assertThrows(LoginException.class, () -> invokeAndRethrowLoginException(m, method));
180+
}
181+
182+
@Test
183+
public void test10_performLogin_withNullPassword_throwsFailedLogin() throws Exception {
184+
PamLoginModule m = new PamLoginModule();
185+
setField(m, "username", "alice");
186+
setField(m, "pam", Mockito.mock(PAM.class));
187+
Method method = PamLoginModule.class.getDeclaredMethod("performLogin");
188+
method.setAccessible(true);
189+
assertThrows(FailedLoginException.class, () -> invokeAndRethrowLoginException(m, method));
190+
}
191+
192+
@Test
193+
public void test11_performLogin_success_setsPrincipalAndAuthSucceeded() throws Exception {
194+
PamLoginModule m = new PamLoginModule();
195+
setField(m, "username", "alice");
196+
setField(m, "passwordChar", new char[] {'s'});
197+
PAM pam = Mockito.mock(PAM.class);
198+
UnixUser user = Mockito.mock(UnixUser.class);
199+
Mockito.when(user.getUserName()).thenReturn("alice");
200+
Mockito.when(pam.authenticate("alice", "s")).thenReturn(user);
201+
setField(m, "pam", pam);
202+
203+
Method method = PamLoginModule.class.getDeclaredMethod("performLogin");
204+
method.setAccessible(true);
205+
assertTrue(invokeAndReturnBoolean(m, method));
206+
Object principal = getField(m, "principal");
207+
assertNotNull(principal);
208+
assertTrue(principal instanceof PamPrincipal);
209+
assertEquals("alice", ((PamPrincipal) principal).getName());
210+
assertTrue((Boolean) getField(m, "authSucceeded"));
211+
}
212+
213+
@Test
214+
public void test12_logout_readOnlySubject_throws_and_cleansUp() throws Exception {
215+
PamLoginModule m = new PamLoginModule();
216+
Subject subject = new Subject();
217+
subject.setReadOnly();
218+
setField(m, "subject", subject);
219+
setField(m, "principal", new PamPrincipal(Mockito.mock(UnixUser.class)));
220+
PAM pam = Mockito.mock(PAM.class);
221+
setField(m, "pam", pam);
222+
setField(m, "passwordChar", new char[] {'p'});
223+
assertThrows(LoginException.class, m::logout);
224+
Mockito.verify(pam).dispose();
225+
}
226+
227+
private static void setField(Object target, String name, Object value) throws Exception {
228+
Field f = target.getClass().getDeclaredField(name);
229+
f.setAccessible(true);
230+
f.set(target, value);
231+
}
232+
233+
private static Object getField(Object target, String name) throws Exception {
234+
Field f = target.getClass().getDeclaredField(name);
235+
f.setAccessible(true);
236+
return f.get(target);
237+
}
238+
239+
private static void invokeAndRethrowLoginException(Object target, Method method) throws LoginException {
240+
try {
241+
method.invoke(target);
242+
} catch (InvocationTargetException e) {
243+
Throwable cause = e.getCause();
244+
if (cause instanceof LoginException) {
245+
throw (LoginException) cause;
246+
}
247+
throw new RuntimeException(cause);
248+
} catch (IllegalAccessException e) {
249+
throw new RuntimeException(e);
250+
}
251+
}
252+
253+
private static boolean invokeAndReturnBoolean(Object target, Method method) throws Exception {
254+
try {
255+
Object ret = method.invoke(target);
256+
return (Boolean) ret;
257+
} catch (InvocationTargetException e) {
258+
Throwable cause = e.getCause();
259+
if (cause instanceof Exception) {
260+
throw (Exception) cause;
261+
}
262+
throw new RuntimeException(cause);
263+
}
264+
}
265+
266+
private static CallbackHandler creds(String user, String pass) {
267+
return new CallbackHandler() {
268+
@Override
269+
public void handle(Callback[] callbacks) {
270+
for (Callback c : callbacks) {
271+
if (c instanceof NameCallback) {
272+
((NameCallback) c).setName(user);
273+
}
274+
if (c instanceof PasswordCallback) {
275+
((PasswordCallback) c).setPassword(pass != null ? pass.toCharArray() : null);
276+
}
277+
}
278+
}
279+
};
280+
}
281+
}

0 commit comments

Comments
 (0)