package io.kaif.service.impl;
import static java.util.Arrays.asList;
import static org.junit.Assert.*;
import java.time.Duration;
import java.util.EnumSet;
import java.util.List;
import java.util.stream.IntStream;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import io.kaif.model.account.Account;
import io.kaif.model.account.AccountOnceToken;
import io.kaif.model.clientapp.ClientApp;
import io.kaif.model.clientapp.ClientAppScope;
import io.kaif.model.clientapp.ClientAppUser;
import io.kaif.model.clientapp.ClientAppUserAccessToken;
import io.kaif.model.exception.CallbackUriReservedException;
import io.kaif.model.exception.ClientAppMaxException;
import io.kaif.model.exception.ClientAppNameReservedException;
import io.kaif.oauth.OauthAccessTokenDto;
import io.kaif.service.AccountService;
import io.kaif.test.DbIntegrationTests;
import io.kaif.web.support.AccessDeniedException;
public class ClientAppServiceImplTest extends DbIntegrationTests {
@Autowired
private ClientAppServiceImpl service;
@Autowired
private AccountService accountService;
private Account dev;
@Before
public void setUp() throws Exception {
dev = savedAccountCitizen("dev1");
}
@Test
public void create() throws Exception {
ClientApp clientApp = service.create(dev, "myapp", "ya ~ good", "http://myapp.com/callback");
ClientApp loaded = service.loadClientAppWithoutCache(clientApp.getClientId());
assertEquals("myapp", loaded.getAppName());
assertEquals("ya ~ good", loaded.getDescription());
assertEquals("http://myapp.com/callback", loaded.getCallbackUri());
assertEquals(32, loaded.getClientSecret().length());
assertEquals(16, loaded.getClientId().length());
assertTrue(loaded.isOwner(dev));
assertNotNull(loaded.getCreateTime());
}
@Test
public void verifyRedirectUri() throws Exception {
assertFalse(service.verifyRedirectUri("notExist", "foo://com").isPresent());
ClientApp clientApp = service.create(dev, "myapp", "ya ~ good", "http://myapp.com/callback");
String id = clientApp.getClientId();
assertEquals(clientApp, service.verifyRedirectUri(id, "http://myapp.com/callback").get());
assertTrue(service.verifyRedirectUri(id, "http://myapp.com/callback/").isPresent());
assertTrue(service.verifyRedirectUri(id, "http://myapp.com/callback/bar").isPresent());
assertFalse(service.verifyRedirectUri(id, "http://myapp.com/wrong").isPresent());
assertFalse(service.verifyRedirectUri(id, "https://myapp.com/callback/foo").isPresent());
assertFalse(service.verifyRedirectUri(id, "myapp://callback/foo").isPresent());
assertFalse(service.verifyRedirectUri(id, null).isPresent());
}
@Test
public void create_maxApps() throws Exception {
IntStream.rangeClosed(1, 5).forEach(i -> {
service.create(dev, "myapp", "ya ~ good", "http://myapp.com/callback");
});
try {
service.create(dev, "myapp", "ya ~ good", "http://myapp.com/callback");
fail("ClientAppMaxException expected");
} catch (ClientAppMaxException expected) {
}
}
@Test
public void update() throws Exception {
ClientApp clientApp = service.create(dev, "myapp", "ya ~ good", "http://myapp.com/callback");
service.update(dev, clientApp.getClientId(), "appU2", "poor baby", "myapp://callback");
ClientApp loaded = service.loadClientAppWithoutCache(clientApp.getClientId());
assertEquals("appU2", loaded.getAppName());
assertEquals("poor baby", loaded.getDescription());
assertEquals("myapp://callback", loaded.getCallbackUri());
}
@Test
public void update_not_owner() throws Exception {
ClientApp clientApp = service.create(dev, "myapp", "ya ~ good", "http://myapp.com/callback");
try {
service.update(savedAccountCitizen("otherDev"),
clientApp.getClientId(),
"appU2",
"poor baby",
"myapp://callback");
fail("AccessDeniedException expected");
} catch (AccessDeniedException expected) {
}
}
@Test
public void create_not_citizen() throws Exception {
Account tourist = savedAccountTourist("tourist1");
try {
service.create(tourist, "myapp", "ya ~ good", "http://myapp.com/callback");
fail("AccessDeniedException expected");
} catch (AccessDeniedException expected) {
}
}
@Test
public void client_app_not_allow_reserved_word() throws Exception {
try {
service.create(dev, "myapp", "ya ~ good", "http://myapp.com/kaif");
fail("CallbackUriReservedException expected");
} catch (CallbackUriReservedException expected) {
}
try {
service.create(dev, "myapp", "ya ~ good", "Kaif-demo://myapp.com/");
fail("CallbackUriReservedException expected");
} catch (CallbackUriReservedException expected) {
}
try {
service.create(dev, "Kaif", "ya ~ good", "demo://myapp.com/");
fail("ClientAppNameReservedException expected");
} catch (ClientAppNameReservedException expected) {
}
ClientApp clientApp = service.create(dev, "myapp", "ya ~ good", "http://myapp.com/callback");
try {
service.update(dev, clientApp.getClientId(), "kaif", "foobar", "demo://foo");
fail("ClientAppNameReservedException expected");
} catch (ClientAppNameReservedException expected) {
}
}
@Test
public void listClientApps() throws Exception {
ClientApp clientApp1 = service.create(dev, "myapp1", "ya ~ good", "http://myapp1.com/callback");
ClientApp clientApp2 = service.create(dev, "myapp2", "ya ~ good", "http://myapp2.com/callback");
assertEquals(asList(clientApp1, clientApp2), service.listClientApps(dev));
}
@Test
public void directGrantCode() throws Exception {
Account user = savedAccountCitizen("user1");
AccountOnceToken oauthDirectAuthorizeToken = accountService.createOauthDirectAuthorizeToken(user);
ClientApp clientApp = service.create(dev, "myapp", "ya ~ good", "http://myapp.com/callback");
String code = service.directGrantCode(oauthDirectAuthorizeToken.getToken(),
clientApp.getClientId(),
"feed public",
"http://myapp.com/callback/foo");
assertTrue(code.length() > 100);
OauthAccessTokenDto token = service.createOauthAccessTokenByGrantCode(code,
clientApp.getClientId(),
"http://myapp.com/callback/foo");
assertNotNull(token.getAccessToken());
assertEquals("feed public", token.getScope());
}
@Test
public void createOauthAccessTokenByGrantCode() throws Exception {
Account user = savedAccountCitizen("user1");
AccountOnceToken oauthDirectAuthorizeToken = accountService.createOauthDirectAuthorizeToken(user);
ClientApp clientApp = service.create(dev, "myapp", "ya ~ good", "http://myapp.com/callback");
String grantCode = service.directGrantCode(oauthDirectAuthorizeToken.getToken(),
clientApp.getClientId(),
"feed public",
"http://myapp.com/callback/foo");
OauthAccessTokenDto tokenDto = service.createOauthAccessTokenByGrantCode(grantCode,
clientApp.getClientId(),
"http://myapp.com/callback/foo");
ClientAppUser appUser = service.listGrantedAppUsers(user).get(0);
assertEquals(user.getAccountId(), appUser.getAccountId());
assertEquals(clientApp.getClientId(), appUser.getClientId());
assertEquals(clientApp.getClientSecret(), appUser.getCurrentClientSecret());
assertEquals(EnumSet.of(ClientAppScope.FEED, ClientAppScope.PUBLIC),
appUser.getLastGrantedScopes());
ClientAppUserAccessToken clientAppUserAccessToken = service.verifyAccessToken(tokenDto.getAccessToken())
.get();
assertTrue(clientAppUserAccessToken.validate(appUser));
assertTrue(clientAppUserAccessToken.containsScope(ClientAppScope.FEED));
assertTrue(clientAppUserAccessToken.containsScope(ClientAppScope.PUBLIC));
}
@Test
public void listGrantedApps() throws Exception {
Account user = savedAccountCitizen("user1");
assertEquals(0, service.listGrantedApps(user).size());
ClientApp app1 = service.create(dev, "myapp1", "ya ~ good", "http://myapp.com/callback");
ClientApp app2 = service.create(dev, "myapp2", "ya ~ good", "http://myapp.com/callback");
service.createOauthAccessToken(app1,
user.getAccountId(),
EnumSet.of(ClientAppScope.FEED),
Duration.ofDays(1)).getAccessToken();
service.createOauthAccessToken(app2,
user.getAccountId(),
EnumSet.of(ClientAppScope.FEED),
Duration.ofDays(1)).getAccessToken();
assertEquals(asList(app2, app1), service.listGrantedApps(user));
}
@Test
public void generateDebugAccessToken() throws Exception {
ClientApp app1 = service.create(dev, "myapp1", "ya ~ good", "http://myapp.com/callback");
String token = service.generateDebugAccessToken(dev, app1.getClientId());
assertTrue(service.verifyAccessToken(token).isPresent());
}
@Test
public void verifyAccessToken_failed_if_app_reset_secret() throws Exception {
Account user = savedAccountCitizen("user1");
ClientApp clientApp = service.create(dev, "myapp", "ya ~ good", "http://myapp.com/callback");
String accessToken = service.createOauthAccessToken(clientApp,
user.getAccountId(),
EnumSet.of(ClientAppScope.FEED),
Duration.ofDays(1)).getAccessToken();
assertTrue(service.verifyAccessToken(accessToken).isPresent());
service.resetClientAppSecret(dev, clientApp.getClientId());
assertFalse(service.verifyAccessToken(accessToken).isPresent());
}
@Test
public void validateApp() throws Exception {
assertFalse(service.validateApp("foo", "bar"));
ClientApp clientApp = service.create(dev, "myapp", "ya ~ good", "http://myapp.com/callback");
assertTrue(service.validateApp(clientApp.getClientId(), clientApp.getClientSecret()));
service.resetClientAppSecret(dev, clientApp.getClientId());
assertFalse(service.validateApp(clientApp.getClientId(), clientApp.getClientSecret()));
ClientApp reset = service.loadClientAppWithoutCache(clientApp.getClientId());
assertTrue(service.validateApp(reset.getClientId(), reset.getClientSecret()));
}
@Test
public void verifyAccessToken_failed_if_user_revoked() throws Exception {
Account user = savedAccountCitizen("user1");
ClientApp clientApp = service.create(dev, "myapp", "ya ~ good", "http://myapp.com/callback");
String accessToken = service.createOauthAccessToken(clientApp,
user.getAccountId(),
EnumSet.of(ClientAppScope.FEED),
Duration.ofDays(1)).getAccessToken();
assertTrue(service.verifyAccessToken(accessToken).isPresent());
service.revokeApp(user, clientApp.getClientId());
assertFalse(service.verifyAccessToken(accessToken).isPresent());
}
@Test
public void createOauthAccessToken_preserve_last_creation_only() throws Exception {
Account user = savedAccountCitizen("user1");
ClientApp clientApp = service.create(dev, "myapp", "ya ~ good", "http://myapp.com/callback");
service.createOauthAccessToken(clientApp,
user.getAccountId(),
EnumSet.of(ClientAppScope.FEED),
Duration.ofDays(1));
List<ClientAppUser> clientAppUsers = service.listGrantedAppUsers(user);
assertEquals(1, clientAppUsers.size());
ClientAppUser appUser = clientAppUsers.get(0);
assertEquals(user.getAccountId(), appUser.getAccountId());
assertEquals(clientApp.getClientId(), appUser.getClientId());
assertEquals(clientApp.getClientSecret(), appUser.getCurrentClientSecret());
assertEquals(EnumSet.of(ClientAppScope.FEED), appUser.getLastGrantedScopes());
//both clientSecret and scopes are updated
service.resetClientAppSecret(dev, clientApp.getClientId());
ClientApp resetApp = service.loadClientAppWithoutCache(clientApp.getClientId());
service.createOauthAccessToken(clientApp,
user.getAccountId(),
EnumSet.of(ClientAppScope.ARTICLE),
Duration.ofDays(1));
clientAppUsers = service.listGrantedAppUsers(user);
assertEquals("should update exist client app user if issue new access token",
1,
clientAppUsers.size());
ClientAppUser updated = clientAppUsers.get(0);
assertEquals(user.getAccountId(), updated.getAccountId());
assertEquals(clientApp.getClientId(), updated.getClientId());
assertEquals(resetApp.getClientSecret(), updated.getCurrentClientSecret());
assertEquals(EnumSet.of(ClientAppScope.ARTICLE), updated.getLastGrantedScopes());
}
}