diff --git a/src/main/java/org/olat/restapi/security/RestApiLoginFilter.java b/src/main/java/org/olat/restapi/security/RestApiLoginFilter.java index 5af2f6c8a7fe191a6d94a358a5b07fad41959399..ec211151669cb00a4854a537715a1af57b58119b 100644 --- a/src/main/java/org/olat/restapi/security/RestApiLoginFilter.java +++ b/src/main/java/org/olat/restapi/security/RestApiLoginFilter.java @@ -377,10 +377,13 @@ public class RestApiLoginFilter implements Filter { Identity identity = securityBean.getIdentity(token); int loginStatus = AuthHelper.doHeadlessLogin(identity, BaseSecurityModule.getDefaultAuthProviderIdentifier(), ureq, true); if(loginStatus == AuthHelper.LOGIN_OK) { - response.setHeader(RestSecurityHelper.SEC_TOKEN, securityBean.renewToken(token)); - synchronized(uress) { - chain.doFilter(request, response); - } + String renewedToken = securityBean.renewToken(token); + if(renewedToken != null) { + response.setHeader(RestSecurityHelper.SEC_TOKEN, renewedToken); + synchronized(uress) { + chain.doFilter(request, response); + } + } else response.sendError(401); } else response.sendError(401); } else response.sendError(401); } diff --git a/src/main/java/org/olat/restapi/security/RestSecurityBeanImpl.java b/src/main/java/org/olat/restapi/security/RestSecurityBeanImpl.java index 9222f9d4e02cc38def6ac235d1a9dbd855dcf2d0..1968a04649025f09258131673aecd8b1096927b1 100644 --- a/src/main/java/org/olat/restapi/security/RestSecurityBeanImpl.java +++ b/src/main/java/org/olat/restapi/security/RestSecurityBeanImpl.java @@ -53,9 +53,9 @@ public class RestSecurityBeanImpl implements RestSecurityBean { public static final String REST_AUTH_PROVIDER = "REST"; - private Map<String,Long> tokenToIdentity = new ConcurrentHashMap<String,Long>(); - private Map<String,List<String>> tokenToSessionIds = new ConcurrentHashMap<String,List<String>>(); - private Map<String,String> sessionIdToTokens = new ConcurrentHashMap<String,String>(); + private Map<String,Long> tokenToIdentity = new ConcurrentHashMap<>(); + private Map<String,List<String>> tokenToSessionIds = new ConcurrentHashMap<>(); + private Map<String,String> sessionIdToTokens = new ConcurrentHashMap<>(); @Autowired private BaseSecurity securityManager; @@ -70,7 +70,7 @@ public class RestSecurityBeanImpl implements RestSecurityBean { Authentication auth = securityManager.findAuthentication(identity, REST_AUTH_PROVIDER); if(auth == null) { - auth = securityManager.createAndPersistAuthentication(identity, REST_AUTH_PROVIDER, identity.getName(), token, null); + securityManager.createAndPersistAuthentication(identity, REST_AUTH_PROVIDER, identity.getName(), token, null); } else { authenticationDao.updateCredential(auth, token); } @@ -79,6 +79,19 @@ public class RestSecurityBeanImpl implements RestSecurityBean { @Override public String renewToken(String token) { + if(token == null || token.length() > 40) { + return null; + } + // don't regex, never + for(char c:token.toCharArray()) { + if(c == '-' + || (c >= 48 && c <= 57) + || (c >= 65 && c <= 90) + || (c >= 97 && c <= 122)) { + continue; + } + return null; + } return token; } @@ -119,7 +132,7 @@ public class RestSecurityBeanImpl implements RestSecurityBean { String sessionId = session.getId(); synchronized(tokenToSessionIds) {//cluster notOK -> need probably a mapping on the DB if(!tokenToSessionIds.containsKey(token)) { - List<String> sessionIds = new ArrayList<String>(); + List<String> sessionIds = new ArrayList<>(); sessionIds.add(session.getId()); tokenToSessionIds.put(token, sessionIds); } else { diff --git a/src/test/java/org/olat/restapi/RestConnection.java b/src/test/java/org/olat/restapi/RestConnection.java index 0d12e054d20e3267bc546ca3abb2d85288ce1f73..493cca66255b702acda10bdf5459a1f1404084bc 100644 --- a/src/test/java/org/olat/restapi/RestConnection.java +++ b/src/test/java/org/olat/restapi/RestConnection.java @@ -33,7 +33,6 @@ import java.util.List; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.UriBuilder; -import org.apache.commons.io.IOUtils; import org.apache.http.Header; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; @@ -44,6 +43,7 @@ import org.apache.http.client.CookieStore; import org.apache.http.client.config.CookieSpecs; import org.apache.http.client.config.RequestConfig; import org.apache.http.client.entity.UrlEncodedFormEntity; +import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpDelete; import org.apache.http.client.methods.HttpEntityEnclosingRequestBase; import org.apache.http.client.methods.HttpGet; @@ -102,6 +102,17 @@ public class RestConnection { .build(); } + public RestConnection(boolean enableCookieStore, boolean enableCredentialProvider) { + HttpClientBuilder builder = HttpClientBuilder.create(); + if(enableCookieStore) { + builder = builder.setDefaultCookieStore(cookieStore); + } + if(enableCredentialProvider) { + builder = builder.setDefaultCredentialsProvider(provider); + } + httpclient = builder.build(); + } + public RestConnection(URL url) { PORT = url.getPort(); HOST = url.getHost(); @@ -151,7 +162,11 @@ public class RestConnection { } public void shutdown() { - IOUtils.closeQuietly(httpclient); + try { + httpclient.close(); + } catch (IOException e) { + log.error("", e); + } } public boolean login(String username, String password) throws IOException, URISyntaxException { @@ -161,17 +176,20 @@ public class RestConnection { provider.setCredentials(new AuthScope(HOST, PORT), new UsernamePasswordCredentials(username, password)); provider.setCredentials(new AuthScope(uri.getHost(), uri.getPort()), new UsernamePasswordCredentials(username, password)); + int code = -1; HttpGet httpget = new HttpGet(uri); - HttpResponse response = httpclient.execute(httpget); - - Header header = response.getFirstHeader(RestSecurityHelper.SEC_TOKEN); - if(header != null) { - securityToken = header.getValue(); + try(CloseableHttpResponse response = httpclient.execute(httpget)) { + Header header = response.getFirstHeader(RestSecurityHelper.SEC_TOKEN); + if(header != null) { + securityToken = header.getValue(); + } + + HttpEntity entity = response.getEntity(); + code = response.getStatusLine().getStatusCode(); + EntityUtils.consume(entity); + } catch(IOException e) { + log.error("", e); } - - HttpEntity entity = response.getEntity(); - int code = response.getStatusLine().getStatusCode(); - EntityUtils.consume(entity); return code == 200; } @@ -310,8 +328,7 @@ public class RestConnection { } public <U> U parse(HttpResponse response, Class<U> cl) { - try { - InputStream body = response.getEntity().getContent(); + try(InputStream body = response.getEntity().getContent()) { ObjectMapper mapper = new ObjectMapper(jsonFactory); U obj = mapper.readValue(body, cl); return obj; diff --git a/src/test/java/org/olat/restapi/security/RestSecurityBeanTest.java b/src/test/java/org/olat/restapi/security/RestSecurityBeanTest.java new file mode 100644 index 0000000000000000000000000000000000000000..a7c7664e833c5e35cd80b11a7c29048b5cbb5d8a --- /dev/null +++ b/src/test/java/org/olat/restapi/security/RestSecurityBeanTest.java @@ -0,0 +1,92 @@ +/** + * <a href="http://www.openolat.org"> + * OpenOLAT - Online Learning and Training</a><br> + * <p> + * Licensed under the Apache License, Version 2.0 (the "License"); <br> + * you may not use this file except in compliance with the License.<br> + * You may obtain a copy of the License at the + * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a> + * <p> + * Unless required by applicable law or agreed to in writing,<br> + * software distributed under the License is distributed on an "AS IS" BASIS, <br> + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br> + * See the License for the specific language governing permissions and <br> + * limitations under the License. + * <p> + * Initial code contributed and copyrighted by<br> + * frentix GmbH, http://www.frentix.com + * <p> + */ +package org.olat.restapi.security; + +import java.util.UUID; + +import org.junit.Assert; +import org.junit.Test; +import org.olat.core.id.Identity; +import org.olat.test.JunitTestHelper; +import org.olat.test.OlatTestCase; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.mock.web.MockHttpSession; + +/** + * + * Initial date: 5 mars 2018<br> + * @author srosse, stephane.rosse@frentix.com, http://www.frentix.com + * + */ +public class RestSecurityBeanTest extends OlatTestCase { + + @Autowired + private RestSecurityBean restSecurityBean; + + @Test + public void generatedToken() { + Identity id = JunitTestHelper.createAndPersistIdentityAsRndUser("rest-api"); + String token = restSecurityBean.generateToken(id, new MockHttpSession()); + String renewedToken = restSecurityBean.renewToken(token); + Assert.assertEquals(token, renewedToken); + } + + @Test + public void renewToken() { + String token = UUID.randomUUID().toString(); + String renewedToken = restSecurityBean.renewToken(token); + Assert.assertEquals(token, renewedToken); + } + + @Test + public void renewToken_09() { + String uuid = "0123456789"; + String renewedToken = restSecurityBean.renewToken(uuid); + Assert.assertEquals(uuid, renewedToken); + } + + @Test + public void renewToken_az() { + String uuid = "abcdefghijklmnopqrstuvwxyz"; + String renewedToken = restSecurityBean.renewToken(uuid); + Assert.assertEquals(uuid, renewedToken); + } + + @Test + public void renewToken_AZ() { + String uuid = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + String renewedToken = restSecurityBean.renewToken(uuid); + Assert.assertEquals(uuid, renewedToken); + } + + @Test + public void renewToken_tooLong() { + String uuid = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; + String renewedToken = restSecurityBean.renewToken(uuid); + Assert.assertNull(renewedToken); + } + + @Test + public void renewToken_notAllowed() { + String uuid = "abc:test"; + String renewedToken = restSecurityBean.renewToken(uuid); + Assert.assertNull(renewedToken); + } +} diff --git a/src/test/java/org/olat/test/AllTestsJunit4.java b/src/test/java/org/olat/test/AllTestsJunit4.java index 0095f54148d9e266ce7d37c2186e4f3c3bf06aa5..4eaad3e8c391e74b37e14fab07ab2d383461a50f 100644 --- a/src/test/java/org/olat/test/AllTestsJunit4.java +++ b/src/test/java/org/olat/test/AllTestsJunit4.java @@ -331,6 +331,7 @@ import org.junit.runners.Suite; org.olat.restapi.RegistrationTest.class, org.olat.restapi.DocumentPoolModuleWebServiceTest.class, org.olat.restapi.TaxonomyWebServiceTest.class, + org.olat.restapi.security.RestSecurityBeanTest.class, de.bps.onyx.plugin.OnyxModuleTest.class, de.bps.onyx.plugin.OnyxResultManagerTest.class, de.bps.olat.portal.institution.InstitutionPortletTest.class,