Commit 52d7317e authored by Matija Obreza's avatar Matija Obreza

Major updates to OAuth

- Updated models and DB changes
- New service methods
parent 7e7b8d3c
......@@ -20,6 +20,8 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import com.fasterxml.jackson.annotation.JsonIgnore;
import org.genesys2.server.model.UserRole;
public class UserWrapper {
......@@ -28,6 +30,7 @@ public class UserWrapper {
private String email;
@JsonIgnore
private String password;
private String name;
......
/**
* Copyright 2014 Global Crop Diversity Trust
*
* 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 org.genesys2.server.servlet.controller.admin;
import java.util.Collection;
import com.fasterxml.jackson.annotation.JsonView;
import org.genesys.blocks.model.JsonViews;
import org.genesys.blocks.oauth.model.AccessToken;
import org.genesys.blocks.oauth.model.OAuthClient;
import org.genesys.blocks.oauth.model.RefreshToken;
import org.genesys.blocks.oauth.service.OAuthClientDetailsService;
import org.genesys.blocks.oauth.service.OAuthTokenStoreService;
import org.genesys2.server.servlet.controller.BaseController;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.provider.ClientDetails;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
/**
* Allow administrators to manage OAuth API keys.
*/
@Controller
@RequestMapping(OAuthManagementController.CONTROLLER_PATH)
@PreAuthorize("hasRole('ADMINISTRATOR')")
public class OAuthManagementController extends BaseController {
final static String CONTROLLER_PATH = "/admin/oauth-clients";
final static String VIEW_PATH = "/admin/oauth";
@Autowired
private OAuthClientDetailsService clientDetailsService;
@Autowired
private OAuthTokenStoreService tokenStoreService;
@PreAuthorize("hasRole('ADMINISTRATOR')")
@RequestMapping("/")
public String listClients(Model model) {
model.addAttribute("clientDetailsList", clientDetailsService.listClientDetails());
return VIEW_PATH + "/clientslist";
}
@PreAuthorize("hasRole('ADMINISTRATOR')")
@RequestMapping("/{clientId}/removeall")
public String removeAllAccessTokens(@PathVariable("clientId") String clientId) {
final Collection<OAuth2AccessToken> tokens = tokenStoreService.findTokensByClientId(clientId);
for (final OAuth2AccessToken token : tokens) {
tokenStoreService.removeAccessToken(token);
}
return "redirect:" + CONTROLLER_PATH + "/" + clientId + "/";
}
@PreAuthorize("hasRole('ADMINISTRATOR')")
@RequestMapping("/{clientId}/removeall-rt")
public String removeAllRefreshTokens(@PathVariable("clientId") String clientId) {
final Collection<RefreshToken> tokens = tokenStoreService.findRefreshTokensByClientId(clientId);
for (final RefreshToken token : tokens) {
tokenStoreService.removeRefreshToken(token.getTokenId());
}
return "redirect:" + CONTROLLER_PATH + "/" + clientId + "/";
}
@PreAuthorize("hasRole('ADMINISTRATOR')")
@RequestMapping("/{clientId}/{tokenId}/remove-rt")
public String removeRefreshToken(@PathVariable("tokenId") String tokenId, @PathVariable("clientId") String clientId) {
tokenStoreService.removeRefreshToken(tokenId);
return "redirect:" + CONTROLLER_PATH + "/" + clientId + "/";
}
@PreAuthorize("hasRole('ADMINISTRATOR')")
@RequestMapping("/{clientId}/{tokenId}/remove")
public String removeAccessToken(@PathVariable("tokenId") String tokenId, @PathVariable("clientId") String clientId) {
tokenStoreService.removeAccessToken(tokenId);
return "redirect:" + CONTROLLER_PATH + "/" + clientId + "/";
}
@RequestMapping("/user/{uuid}/tokens")
@PreAuthorize("hasRole('ADMINISTRATOR') || principal.uuid == #uuid")
public String getIssuedTokens(@PathVariable("uuid") String uuid, Model model) {
final Collection<AccessToken> tokens = tokenStoreService.findTokensByUserUuid(uuid);
model.addAttribute("tokens", tokens);
return VIEW_PATH + "/tokenslist";
}
@RequestMapping("/user/{uuid}/{tokenId}/remove")
@PreAuthorize("hasRole('ADMINISTRATOR') || principal.uuid == #uuid")
public String removeUsersAccessToken(@PathVariable("tokenId") String tokenId, @PathVariable("uuid") String uuid) {
tokenStoreService.removeAccessToken(tokenId);
return "redirect:" + CONTROLLER_PATH + "/user/" + uuid + "/tokens";
}
@PreAuthorize("hasAnyRole('VETTEDUSER','ADMINISTRATOR')")
@RequestMapping("/add-client")
public String addClientEntry() {
return VIEW_PATH + "/edit";
}
@PreAuthorize("hasAnyRole('VETTEDUSER','ADMINISTRATOR')")
@RequestMapping("/{id}/edit")
public String editClient(Model model, @PathVariable("id") String clientId) {
final ClientDetails clientDetails = clientDetailsService.loadClientByClientId(clientId);
model.addAttribute("clientDetails", clientDetails);
return VIEW_PATH + "/edit";
}
@PreAuthorize("hasAnyRole('VETTEDUSER','ADMINISTRATOR')")
@RequestMapping(value = "/save-client", method = RequestMethod.POST, params = "newClient=new")
public String createClientEntry(Model model, @RequestParam("title") String title, @RequestParam("description") String description,
@RequestParam(value = "redirectUris", required = false) String redirectUris, @RequestParam("accessTokenValiditySeconds") Integer accessTokenValiditySeconds,
@RequestParam("refreshTokenValiditySeconds") Integer refreshTokenValiditySeconds, @RequestParam(value = "clientType", required = false) String clientType) {
OAuthClient oauthClient = clientDetailsService.addClient(title, description, redirectUris, accessTokenValiditySeconds, refreshTokenValiditySeconds);
return "redirect:" + CONTROLLER_PATH + "/" + oauthClient.getId() + "/edit";
}
@PreAuthorize("hasAnyRole('VETTEDUSER','ADMINISTRATOR')")
@RequestMapping(value = "/save-client", method = RequestMethod.POST, params = { "id", "action-delete" })
public String deleteClient(Model model, @RequestParam("id") String clientId) {
ClientDetails clientDetails = clientDetailsService.loadClientByClientId(clientId);
_logger.info("Deleting client " + clientDetails.getClientId());
clientDetailsService.removeClient(clientDetails);
return "redirect:" + CONTROLLER_PATH + "/";
}
@RequestMapping(value = "/save-client", method = RequestMethod.POST, params = { "id", "version", "action-save" })
public String saveExistinClient(Model model, @RequestBody @JsonView(JsonViews.Protected.class) OAuthClient updates, @RequestParam("id") long id, @RequestParam("version") int version) {
final OAuthClient clientDetails = clientDetailsService.updateClient(id, version, updates);
return "redirect:" + CONTROLLER_PATH + "/" + clientDetails.getId() + "/edit";
}
@PreAuthorize("hasRole('ADMINISTRATOR')")
@RequestMapping(value = "/{clientId}", method = RequestMethod.GET)
public String clientDetailsInfo(Model model, @PathVariable("clientId") String clientId) {
final ClientDetails clientDetails = clientDetailsService.loadClientByClientId(clientId);
final Collection<AccessToken> tokensByClientId = tokenStoreService.findAccessTokensByClientId(clientId);
final Collection<RefreshToken> refreshTokensByClientId = tokenStoreService.findRefreshTokensByClientId(clientId);
model.addAttribute("accessTokens", tokensByClientId);
model.addAttribute("refreshTokens", refreshTokensByClientId);
model.addAttribute("clientDetails", clientDetails);
return VIEW_PATH + "/detailsinfo";
}
}
......@@ -23,24 +23,27 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.apache.commons.lang.StringUtils;
import org.genesys2.server.model.elastic.AccessionDetails;
import org.genesys2.server.model.genesys.Accession;
import org.genesys2.server.model.impl.FaoInstitute;
import org.genesys2.server.model.json.AccessionJson;
import org.genesys2.server.model.json.Api1Constants;
import org.genesys2.server.service.AccessionOpResponse;
import org.genesys2.server.service.BatchRESTService;
import org.genesys2.server.service.ElasticService;
import org.genesys2.server.service.FilterConstants;
import org.genesys2.server.service.GenesysFilterService;
import org.genesys2.server.service.GenesysRESTService;
import org.genesys2.server.service.GenesysService;
import org.genesys2.server.service.GeoService;
import org.genesys2.server.service.InstituteService;
import org.genesys2.server.service.TaxonomyService;
import org.genesys2.server.service.AccessionOpResponse;
import org.genesys2.server.service.impl.FilterHandler;
import org.genesys2.server.service.impl.FilterHandler.AppliedFilter;
import org.genesys2.server.service.impl.FilterHandler.AppliedFilters;
import org.genesys2.server.service.impl.NonUniqueAccessionException;
import org.genesys2.server.service.impl.RESTApiException;
......@@ -55,7 +58,6 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.CannotAcquireLockException;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.http.MediaType;
import org.springframework.orm.hibernate4.HibernateOptimisticLockingFailureException;
import org.springframework.security.access.prepost.PreAuthorize;
......@@ -67,12 +69,6 @@ import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
@Controller("restAccessionController")
@PreAuthorize("isAuthenticated()")
@RequestMapping(value = { "/api/v0/acn", "/json/v0/acn" })
......@@ -111,7 +107,8 @@ public class AccessionController extends RestController {
* @throws NonUniqueAccessionException
*/
@RequestMapping(value = "/exists/{instCode}/{genus:.+}", method = RequestMethod.GET, produces = { MediaType.APPLICATION_JSON_VALUE })
public @ResponseBody boolean exists(@PathVariable("instCode") String instCode, @PathVariable("genus") String genus, @RequestParam("acceNumb") String acceNumb) throws NonUniqueAccessionException {
public @ResponseBody boolean exists(@PathVariable("instCode") String instCode, @PathVariable("genus") String genus, @RequestParam("acceNumb") String acceNumb)
throws NonUniqueAccessionException {
if (LOG.isDebugEnabled()) {
LOG.debug("Checking if accn exists " + instCode + "." + acceNumb + " genus=" + genus);
}
......@@ -136,8 +133,8 @@ public class AccessionController extends RestController {
* @throws NonUniqueAccessionException
*/
@RequestMapping(value = "/{instCode}/check", method = { RequestMethod.POST, RequestMethod.PUT }, produces = { MediaType.APPLICATION_JSON_VALUE })
public @ResponseBody JsonNode check(@PathVariable("instCode") String instCode, @RequestBody String content)
throws JsonProcessingException, IOException, RESTApiValueException, NonUniqueAccessionException {
public @ResponseBody JsonNode check(@PathVariable("instCode") String instCode, @RequestBody String content) throws JsonProcessingException, IOException, RESTApiValueException,
NonUniqueAccessionException {
final FaoInstitute institute = instituteService.getInstitute(instCode);
if (institute == null) {
throw new ResourceNotFoundException("No institute " + instCode);
......@@ -197,7 +194,8 @@ public class AccessionController extends RestController {
* @throws RESTApiException
*/
@RequestMapping(value = "/{instCode}/upsert", method = { RequestMethod.POST, RequestMethod.PUT }, produces = { MediaType.APPLICATION_JSON_VALUE })
public @ResponseBody Object upsertInstituteAccession(@PathVariable("instCode") String instCode, @RequestBody String content) throws JsonProcessingException, IOException, RESTApiException {
public @ResponseBody Object upsertInstituteAccession(@PathVariable("instCode") String instCode, @RequestBody String content) throws JsonProcessingException, IOException,
RESTApiException {
// User's permission to WRITE to this WIEWS institute are checked in
// BatchRESTService.
......@@ -255,7 +253,7 @@ public class AccessionController extends RestController {
// transaction)
genesysService.updateAccessionCount(institute);
return response;
} catch (PleaseRetryException | HibernateOptimisticLockingFailureException | org.springframework.orm.hibernate3.HibernateOptimisticLockingFailureException e) {
} catch (PleaseRetryException | HibernateOptimisticLockingFailureException e) {
LOG.info("Will retry upsert. Error: " + e.getMessage());
cause = e;
}
......@@ -304,8 +302,10 @@ public class AccessionController extends RestController {
* @throws IOException
* @throws JsonProcessingException
*/
@RequestMapping(value = "/{instCode}/names", method = { RequestMethod.POST, RequestMethod.PUT }, consumes = { MediaType.APPLICATION_JSON_VALUE }, produces = { MediaType.APPLICATION_JSON_VALUE })
public @ResponseBody List<AccessionOpResponse> upsertAccessionNames(@PathVariable("instCode") String instCode, @RequestBody List<AccessionNamesJson> batch) throws RESTApiException {
@RequestMapping(value = "/{instCode}/names", method = { RequestMethod.POST, RequestMethod.PUT }, consumes = { MediaType.APPLICATION_JSON_VALUE }, produces = {
MediaType.APPLICATION_JSON_VALUE })
public @ResponseBody List<AccessionOpResponse> upsertAccessionNames(@PathVariable("instCode") String instCode, @RequestBody List<AccessionNamesJson> batch)
throws RESTApiException {
// User's permission to WRITE to this WIEWS institute are checked in
// BatchRESTService.
final FaoInstitute institute = instituteService.getInstitute(instCode);
......@@ -450,7 +450,8 @@ public class AccessionController extends RestController {
// FIXME Not using institute...
@RequestMapping(value = "/{instCode}/list", method = { RequestMethod.GET }, produces = { MediaType.APPLICATION_JSON_VALUE })
public @ResponseBody Page<AccessionDetails> list(@PathVariable("instCode") String instCode, @RequestParam("page") int page, @RequestParam("query") String query) throws SearchException {
public @ResponseBody Page<AccessionDetails> list(@PathVariable("instCode") String instCode, @RequestParam("page") int page, @RequestParam("query") String query)
throws SearchException {
FaoInstitute institute = instituteService.getInstitute(instCode);
if (institute == null) {
throw new ResourceNotFoundException();
......@@ -477,18 +478,14 @@ public class AccessionController extends RestController {
@RequestMapping(value = "/filter", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
public Object getAcc(@RequestBody JsonData jsonData) throws IOException {
AppliedFilters appliedFilters = new AppliedFilters();
AppliedFilter cropFilter = new AppliedFilter().setFilterName(FilterConstants.CROPS);
cropFilter.addFilterValue(new FilterHandler.LiteralValueFilter(jsonData.crop));
appliedFilters.add(cropFilter);
public Object getAcc(@RequestBody AppliedFilters appliedFilters, @RequestParam(name = "p", required = false, defaultValue = "1") int page,
@RequestParam(name = "l", required = false, defaultValue = "50") int pageSize) throws IOException {
if (LOG.isDebugEnabled()) {
LOG.debug(appliedFilters.toString());
}
Page<Accession> accessions = filterService.listAccessions(appliedFilters, new PageRequest(jsonData.startAt - 1, Integer.min(jsonData.maxRecords, maxPageSize), new Sort("acceNumb")));
Page<AccessionDetails> accessions = filterService.listAccessionDetails(appliedFilters, new PageRequest(page, Integer.min(pageSize, maxPageSize)));
return accessions;
}
......
......@@ -67,6 +67,7 @@ public class UserController extends RestController {
@RequestMapping(value = ServiceEndpoints.ME, method = RequestMethod.GET, produces = { MediaType.APPLICATION_JSON_VALUE })
public @ResponseBody
Object getProfile() throws AuthorizationException {
// TODO current user may be the OAuth client itself if it has client_credentials grant. Fix this.
final User user = userService.getMe();
return OAuth2Cleanup.clean(user);
}
......
......@@ -32,7 +32,7 @@ import springfox.documentation.swagger2.annotations.EnableSwagger2;
@EnableSwagger2
public class SwaggerConfig {
@Value("${base.hostname}")
@Value("${host.name}")
private String hostName;
@Value("${build.version}")
......
......@@ -31,6 +31,7 @@ build.revision=${buildNumber}
base.host=localhost
base.hostname=${base.host}:8080
host.name=${base.hostname}
base.url=http://${base.hostname}
base.cookie-domain=${base.host}
base.cookie-secure=false
......@@ -39,7 +40,7 @@ base.cookie-http-only=true
# robots.txt
robots.allow=false
db.url=jdbc:mysql://localhost/genesys201709?useUnicode=true&characterEncoding=UTF-8&useFastDateParsing=false
db.url=jdbc:mysql://localhost/genesys?useUnicode=true&characterEncoding=UTF-8&useFastDateParsing=false
db.driverClassName=com.mysql.jdbc.Driver
db.username=root
db.password=
......
......@@ -643,3 +643,40 @@ databaseChangeLog:
columnNames: tokenId
constraintName: PRIMARY
tableName: oauthrefreshtoken
- changeSet:
id: application-blocks-security-1507137884-1
author: matijaobreza
comment: Add refreshToken#clientId
changes:
- addColumn:
tableName: oauthrefreshtoken
columns:
- column:
name: clientId
type: varchar(100)
constraints:
nullable: false
- changeSet:
id: application-blocks-security-1507137884-2
author: matijaobreza
comment: Extend OAuth Tokens
changes:
- addColumn:
tableName: oauthaccesstoken
columns:
- column:
name: expiration
type: datetime
- addColumn:
tableName: oauthrefreshtoken
columns:
- column:
name: expiration
type: datetime
- column:
name: username
type: varchar(100)
......@@ -7,9 +7,6 @@
<title><spring:message code="oauth-client.page.list.title"/></title>
</head>
<body>
<h1>
<spring:message code="oauth-client.list"/>
</h1>
<security:authorize access="hasRole('ADMINISTRATOR') or hasRole('VETTEDUSER')">
<a href="<c:url value="/admin/oauth-clients/add-client" />" class="close"><spring:message code="add" /></a>
......
......@@ -13,7 +13,7 @@
<security:authorize access="hasRole('ADMINISTRATOR') or hasPermission(#clientDetails, 'ADMINISTRATION')">
<a href="<c:url value="/acl/${clientDetails.getClass().name}/${clientDetails.id}/permissions"><c:param name="back">/admin/oauth-clients/${clientDetails.clientId}/</c:param></c:url>" class="close"> <spring:message code="edit-acl" /></a>
<a href="<c:url value="/admin/oauth-clients/${clientDetails.id}/edit" />" class="close">
<a href="<c:url value="/admin/oauth-clients/${clientDetails.clientId}/edit" />" class="close">
<spring:message code="edit" />
</a>
</security:authorize>
......@@ -46,8 +46,7 @@
<c:forEach items="${accessTokens}" var="accessToken">
<tr class="">
<td><fmt:formatDate value="${accessToken.expiration}" type="both" /></td>
<td><c:out value="${jspHelper.userByUuid(accessToken.userUuid).email}"/></td>
<td><c:out value="${accessToken.redirectUri}"/></td>
<td><c:out value="${jspHelper.userByUuid(accessToken.username).email}"/></td>
<td>
<a href="<c:url value="/admin/oauth-clients/${clientDetails.clientId}/${accessToken.id}/remove"/> "><spring:message
code="oauth-client.remove"/></a>
......@@ -56,7 +55,7 @@
</c:forEach>
</tbody>
</table>
<h3><spring:message code="client.details.refresh-token.list"/></h3>
<a href="<c:url value="/admin/oauth-clients/${clientDetails.clientId}/removeall-rt"/> "><spring:message
......@@ -66,8 +65,7 @@
<c:forEach items="${refreshTokens}" var="refreshToken">
<tr class="">
<td><fmt:formatDate value="${refreshToken.expiration}" type="both" /></td>
<td><c:out value="${jspHelper.userByUuid(refreshToken.userUuid).email}"/></td>
<td><c:out value="${refreshToken.redirectUri}"/></td>
<td><c:out value="${jspHelper.userByUuid(refreshToken.username).email}"/></td>
<td>
<a href="<c:url value="/admin/oauth-clients/${clientDetails.clientId}/${refreshToken.id}/remove-rt"/> "><spring:message
code="oauth-client.remove"/></a>
......
......@@ -7,10 +7,6 @@
<title><spring:message code="oauth-client.page.profile.title" arguments="${clientDetails.clientId}" /></title>
</head>
<body>
<h1>
<spring:message code="oauth-client.page.profile.title" arguments="${clientDetails.clientId}" />
</h1>
<form role="form" class="form-horizontal validate" action="<c:url value="/admin/oauth-clients/save-client" />" method="post">
<c:if test="${clientDetails.id gt 0}">
<input type="hidden" name="id" value="${clientDetails.id}" />
......@@ -45,20 +41,20 @@
<div class="form-group">
<label for="redirectUri" class="col-lg-2 control-label"><spring:message code="oauth-client.redirect.uri" /></label>
<div class="col-lg-10">
<textarea id="redirectUri" name="redirectUris" class="form-control"><c:out value="${clientDetails.redirectUris}" /></textarea>
<textarea id="redirectUri" name="redirect" class="form-control"><c:out value="${clientDetails.redirect}" /></textarea>
<p class="help-block">oob,urn:ietf:wg:oauth:2.0:oob</p>
</div>
</div>
<div class="form-group">
<label for="redirectUri" class="col-lg-2 control-label"><spring:message code="oauth-client.access-token.accessTokenValiditySeconds" /></label>
<div class="col-lg-10">
<input type="text" name="accessTokenValiditySeconds" class="form-control" placeholder="<spring:message code="oauth-client.access-token.defaultDuration" />" value="<c:out value="${clientDetails.accessTokenValiditySeconds}" />" />
<input type="text" name="accessTokenValidity" class="form-control" placeholder="<spring:message code="oauth-client.access-token.defaultDuration" />" value="<c:out value="${clientDetails.accessTokenValidity}" />" />
</div>
</div>
<div class="form-group">
<label for="redirectUri" class="col-lg-2 control-label"><spring:message code="oauth-client.access-token.refreshTokenValiditySeconds" /></label>
<div class="col-lg-10">
<input type="text" name="refreshTokenValiditySeconds" class="form-control" placeholder="<spring:message code="oauth-client.access-token.defaultDuration" />" value="<c:out value="${clientDetails.refreshTokenValiditySeconds}" />" />
<input type="text" name="refreshTokenValidity" class="form-control" placeholder="<spring:message code="oauth-client.access-token.defaultDuration" />" value="<c:out value="${clientDetails.refreshTokenValidity}" />" />
</div>
</div>
<%--
......@@ -85,4 +81,4 @@
<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/>
</form>
</body>
</html>
\ No newline at end of file
</html>
......@@ -14,6 +14,8 @@
# limitations under the License.
#-------------------------------------------------------------------------------
host.name=${base.hostname}
base.host=localhost
base.hostname=${base.host}:8080
base.url=http://${base.hostname}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment