Skip to content

Commit

Permalink
#246: Initial implementation complete
Browse files Browse the repository at this point in the history
  • Loading branch information
manusa committed Apr 7, 2019
1 parent b635cf4 commit a2c96a3
Show file tree
Hide file tree
Showing 4 changed files with 201 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,11 @@
*/
package com.marcnuri.isotope.api.application;

import com.marcnuri.isotope.api.configuration.IsotopeApiConfiguration;
import com.marcnuri.isotope.api.credentials.Credentials;
import com.marcnuri.isotope.api.folder.FolderResource;
import com.marcnuri.isotope.api.imap.ImapService;
import com.marcnuri.isotope.api.smtp.SmtpResource;
import com.marcnuri.isotope.api.smtp.SmtpService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand All @@ -30,11 +33,15 @@
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo;
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.methodOn;

/**
* Created by Marc Nuri <[email protected]> on 2018-08-15.
*/
Expand All @@ -44,15 +51,27 @@ public class ApplicationResource {

private static final Logger log = LoggerFactory.getLogger(ApplicationResource.class);

private static final String REL_APPLICATION_LOGIN = "application.login";
private static final String REL_FOLDERS = "folders";
private static final String REL_SMTP = "smtp";

private final IsotopeApiConfiguration configuration;
private final ImapService imapService;
private final SmtpService smtpService;

@Autowired
public ApplicationResource(ImapService imapService, SmtpService smtpService) {
public ApplicationResource(IsotopeApiConfiguration configuration, ImapService imapService, SmtpService smtpService) {
this.configuration = configuration;
this.imapService = imapService;
this.smtpService = smtpService;
}

@GetMapping(path = "/configuration", produces = MediaTypes.HAL_JSON_VALUE)
public ResponseEntity<ConfigurationDto> getConfiguration() {
log.info("User retrieving application configuration");
return ResponseEntity.ok(toDto(configuration));
}

@PostMapping(path = "/login", produces = MediaTypes.HAL_JSON_VALUE)
public ResponseEntity<Credentials> login(
@Validated(Credentials.Login.class) @RequestBody Credentials credentials) {
Expand All @@ -64,4 +83,12 @@ public ResponseEntity<Credentials> login(
return ResponseEntity.ok(encryptedCredentials);
}

private ConfigurationDto toDto(IsotopeApiConfiguration configuration) {
final ConfigurationDto ret = new ConfigurationDto();
ret.add(linkTo(methodOn(ApplicationResource.class).login(null)).withRel(REL_APPLICATION_LOGIN));
ret.add(linkTo(FolderResource.class).withRel(REL_FOLDERS));
ret.add(linkTo(methodOn(SmtpResource.class).sendMessage(null, null)).withRel(REL_SMTP));
return ret;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* ConfigurationDto.java
*
* Created on 2019-04-06, 19:09
*
* Copyright 2019 Marc Nuri
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package com.marcnuri.isotope.api.application;

import com.marcnuri.isotope.api.resource.IsotopeResource;

import java.io.Serializable;

/**
* Created by Marc Nuri <[email protected]> on 2019-04-06.
*/
class ConfigurationDto extends IsotopeResource implements Serializable {

private static final long serialVersionUID = -1279556906780840837L;
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,9 @@
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import org.springframework.security.web.util.matcher.NegatedRequestMatcher;
import org.springframework.security.web.util.matcher.OrRequestMatcher;
import org.springframework.security.web.util.matcher.RegexRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;

/**
* Created by Marc Nuri <[email protected]> on 2019-02-23.
Expand All @@ -40,6 +42,7 @@
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

private static final String CONFIGURATION_REGEX = ".*v1/application/configuration";
private static final String LOGIN_REGEX = ".*v1/application/login";
private final CredentialsService credentialsService;

Expand All @@ -50,23 +53,25 @@ public SecurityConfiguration(CredentialsService credentialsService) {

@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
final RequestMatcher negatedPublicMatchers = new NegatedRequestMatcher(new OrRequestMatcher(
new RegexRequestMatcher(CONFIGURATION_REGEX, "GET"),
new RegexRequestMatcher(LOGIN_REGEX, "POST")
));
httpSecurity
.csrf().disable()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.regexMatchers(LOGIN_REGEX).permitAll()
.anyRequest().authenticated()
.requestMatchers(negatedPublicMatchers).authenticated()
.and()
.cors()
.and()
.logout()
.permitAll()
.and()
.addFilterAfter(new CredentialsAuthenticationFilter(
new NegatedRequestMatcher(new RegexRequestMatcher(LOGIN_REGEX, "POST")),
credentialsService), BasicAuthenticationFilter.class)
negatedPublicMatchers, credentialsService), BasicAuthenticationFilter.class)
.addFilterAfter(new CredentialsRefreshFilter(credentialsService), CredentialsAuthenticationFilter.class);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
/*
* ApplicationResourceTest.java
*
* Created on 2019-04-07, 8:46
*
* Copyright 2019 Marc Nuri
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package com.marcnuri.isotope.api.application;

import com.marcnuri.isotope.api.configuration.IsotopeApiConfiguration;
import com.marcnuri.isotope.api.imap.ImapService;
import com.marcnuri.isotope.api.smtp.SmtpService;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.hateoas.MediaTypes;
import org.springframework.http.MediaType;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.ResultActions;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;

import static com.marcnuri.isotope.api.configuration.WebConfiguration.IMAP_SERVICE_PROTOTYPE;
import static org.hamcrest.Matchers.aMapWithSize;
import static org.hamcrest.Matchers.endsWith;
import static org.hamcrest.Matchers.is;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doNothing;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

/**
* Created by Marc Nuri <[email protected]> on 2019-04-07.
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = ApplicationResource.class)
public class ApplicationResourceTest {

@Autowired
private ApplicationResource applicationResource;
@MockBean
private IsotopeApiConfiguration isotopeApiConfiguration;
@MockBean(name = IMAP_SERVICE_PROTOTYPE)
private ImapService imapService;
@MockBean
private SmtpService smtpService;

private MockMvc mockMvc;

@Before
public void setUp() {
mockMvc = MockMvcBuilders.standaloneSetup(applicationResource).build();
}

@After
public void tearDown() {
mockMvc = null;
}

@Test
public void getConfiguration_na_shouldReturnOk() throws Exception {
// Given
// Untouched IsotopeApiConfiguration
// When
final ResultActions result = mockMvc.perform(get("/v1/application/configuration")
.accept(MediaTypes.HAL_JSON_VALUE));
// Then
result.andExpect(status().isOk());
result.andExpect(jsonPath("$._links").exists());
result.andExpect(jsonPath("$._links", aMapWithSize(3)));
result.andExpect(jsonPath("$._links['application.login'].href", endsWith("/v1/application/login")));
result.andExpect(jsonPath("$._links['folders'].href", endsWith("/v1/folders")));
result.andExpect(jsonPath("$._links['smtp'].href", endsWith("/v1/smtp")));
}

@Test
public void login_validCredentials_shouldReturnOk() throws Exception {
// Given
doAnswer(inv -> inv.getArgument(0)).when(imapService).checkCredentials(Mockito.any());
doNothing().when(smtpService).checkCredentials(Mockito.any());
// When
final ResultActions result = mockMvc.perform(post("/v1/application/login")
.contentType(MediaType.APPLICATION_JSON)
.content("{" +
"\"serverHost\":\"host\"," +
"\"serverPort\":1337," +
"\"user\":\"user\"," +
"\"password\":\"password\"," +
"\"imapSsl\":true," +
"\"smtpPort\":31337," +
"\"smtpSsl\":true" +
"}")
.accept(MediaTypes.HAL_JSON_VALUE));
// Then
result.andExpect(status().isOk());
result.andExpect(jsonPath("$.serverHost", is("host")));
result.andExpect(jsonPath("$.serverPort", is(1337)));
}

@Test
public void login_invalidCredentials_shouldReturnBadRequest() throws Exception {
// Given
// When
final ResultActions result = mockMvc.perform(post("/v1/application/login")
.contentType(MediaType.APPLICATION_JSON)
.content("{}")
.accept(MediaTypes.HAL_JSON_VALUE));
// Then
result.andExpect(status().isBadRequest());
}
}

0 comments on commit a2c96a3

Please sign in to comment.