Commit 622d0999 authored by Matija Obreza's avatar Matija Obreza

ACL implemented on Team, uses pricipal.username as SID

parent e0cdbda2
...@@ -32,7 +32,7 @@ ...@@ -32,7 +32,7 @@
<hibernate.version>4.2.7.SP1</hibernate.version> <hibernate.version>4.2.7.SP1</hibernate.version>
<hibernate.annotations.version>4.0.4.Final</hibernate.annotations.version> <hibernate.annotations.version>4.0.4.Final</hibernate.annotations.version>
<hsqldb.version>2.3.1</hsqldb.version> <hsqldb.version>2.3.1</hsqldb.version>
<ehcache.version>2.7.0</ehcache.version> <ehcache.version>2.7.4</ehcache.version>
<slf4j.version>1.7.5</slf4j.version> <slf4j.version>1.7.5</slf4j.version>
<log4j.version>1.2.17</log4j.version> <log4j.version>1.2.17</log4j.version>
......
...@@ -28,4 +28,9 @@ public interface TeamRepository extends JpaRepository<Team, Long> { ...@@ -28,4 +28,9 @@ public interface TeamRepository extends JpaRepository<Team, Long> {
@Query("select t from Team t where ?1 member of t.members") @Query("select t from Team t where ?1 member of t.members")
List<Team> listForUser(User user); List<Team> listForUser(User user);
Team findOneByUuid(String uuid);
@Query("select t.members from Team t where t = ?1")
List<User> listMembers(Team team);
} }
...@@ -23,6 +23,7 @@ import org.genesys2.server.model.impl.Team; ...@@ -23,6 +23,7 @@ import org.genesys2.server.model.impl.Team;
import org.genesys2.server.model.impl.User; import org.genesys2.server.model.impl.User;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Pageable;
import org.springframework.security.access.prepost.PostAuthorize;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
public interface TeamService { public interface TeamService {
...@@ -43,7 +44,7 @@ public interface TeamService { ...@@ -43,7 +44,7 @@ public interface TeamService {
* @param user * @param user
* @return * @return
*/ */
@PreAuthorize("hasRole('ADMINISTRATOR')") @PreAuthorize("hasRole('ADMINISTRATOR') || hasPermission(#team, 'CREATE')")
Team addTeamMember(Team team, User user); Team addTeamMember(Team team, User user);
/** /**
...@@ -52,7 +53,7 @@ public interface TeamService { ...@@ -52,7 +53,7 @@ public interface TeamService {
* @param team * @param team
* @param user * @param user
*/ */
@PreAuthorize("hasRole('ADMINISTRATOR')") @PreAuthorize("hasRole('ADMINISTRATOR') || hasPermission(#team, 'CREATE')")
Team removeTeamMember(Team team, User user); Team removeTeamMember(Team team, User user);
/** /**
...@@ -63,8 +64,7 @@ public interface TeamService { ...@@ -63,8 +64,7 @@ public interface TeamService {
void removeMe(Team team); void removeMe(Team team);
void removeMe(long teamId); void removeMe(long teamId);
@PreAuthorize("hasRole('ADMINISTRATOR')") @PreAuthorize("hasRole('ADMINISTRATOR')")
Team addTeamInstitute(Team team, FaoInstitute institute); Team addTeamInstitute(Team team, FaoInstitute institute);
...@@ -94,5 +94,10 @@ public interface TeamService { ...@@ -94,5 +94,10 @@ public interface TeamService {
@PreAuthorize("hasRole('ADMINISTRATOR')") @PreAuthorize("hasRole('ADMINISTRATOR')")
Page<Team> listTeams(Pageable pageable); Page<Team> listTeams(Pageable pageable);
@PostAuthorize("hasRole('ADMINISTRATOR') or hasPermission(returnObject, 'READ')")
Team getTeam(String uuid);
@PreAuthorize("hasRole('ADMINISTRATOR') or hasPermission(#team, 'READ')")
List<User> getMembers(Team team);
} }
...@@ -39,11 +39,13 @@ import org.springframework.security.acls.model.Permission; ...@@ -39,11 +39,13 @@ import org.springframework.security.acls.model.Permission;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/** /**
* TODO Add support for cleaning up after objects are removed * TODO Add support for cleaning up after objects are removed
*/ */
@Service @Service
@Transactional
public class AclAssignerServiceImpl implements AclAssignerService { public class AclAssignerServiceImpl implements AclAssignerService {
private static final Logger LOG = LoggerFactory.getLogger(AclAssignerServiceImpl.class); private static final Logger LOG = LoggerFactory.getLogger(AclAssignerServiceImpl.class);
......
...@@ -32,6 +32,7 @@ import org.genesys2.server.service.UserService; ...@@ -32,6 +32,7 @@ import org.genesys2.server.service.UserService;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Pageable;
import org.springframework.security.access.prepost.PostAuthorize;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextHolder;
...@@ -49,8 +50,17 @@ public class TeamServiceImpl implements TeamService { ...@@ -49,8 +50,17 @@ public class TeamServiceImpl implements TeamService {
@Autowired @Autowired
private UserService userService; private UserService userService;
@Override
@PostAuthorize("hasRole('ADMINISTRATOR') or hasPermission(returnObject, 'READ')")
public Team getTeam(String uuid) {
Team team = teamRepository.findOneByUuid(uuid);
System.err.println("Loaded by uuid: " + team);
return team;
}
@Override @Override
@Transactional(readOnly = false) @Transactional(readOnly = false)
@PreAuthorize("isAuthenticated()")
public Team addTeam(String name) { public Team addTeam(String name) {
User user = getCurrentUser(); User user = getCurrentUser();
...@@ -68,7 +78,7 @@ public class TeamServiceImpl implements TeamService { ...@@ -68,7 +78,7 @@ public class TeamServiceImpl implements TeamService {
@Override @Override
@Transactional(readOnly = false) @Transactional(readOnly = false)
@PreAuthorize("hasRole('ADMINISTRATOR')") @PreAuthorize("hasRole('ADMINISTRATOR') || hasPermission(#team, 'CREATE')")
public Team addTeamMember(Team team, User user) { public Team addTeamMember(Team team, User user) {
if (team.getMembers().contains(user)) { if (team.getMembers().contains(user)) {
LOG.info("User already member of this team"); LOG.info("User already member of this team");
...@@ -82,7 +92,7 @@ public class TeamServiceImpl implements TeamService { ...@@ -82,7 +92,7 @@ public class TeamServiceImpl implements TeamService {
} }
@Override @Override
@PreAuthorize("hasRole('ADMINISTRATOR')") @PreAuthorize("hasRole('ADMINISTRATOR') || hasPermission(#team, 'CREATE')")
@Transactional(readOnly = false) @Transactional(readOnly = false)
public Team removeTeamMember(Team team, User user) { public Team removeTeamMember(Team team, User user) {
if (team.getMembers().remove(user)) { if (team.getMembers().remove(user)) {
...@@ -93,15 +103,17 @@ public class TeamServiceImpl implements TeamService { ...@@ -93,15 +103,17 @@ public class TeamServiceImpl implements TeamService {
return team; return team;
} }
@Override @Override
@Transactional(readOnly = false) @Transactional(readOnly = false)
@PreAuthorize("isAuthenticated()")
public void removeMe(long teamId) { public void removeMe(long teamId) {
removeMe(teamRepository.findOne(teamId)); removeMe(teamRepository.findOne(teamId));
} }
@Override @Override
@Transactional(readOnly = false) @Transactional(readOnly = false)
@PreAuthorize("isAuthenticated()")
public void removeMe(Team team) { public void removeMe(Team team) {
User user = getCurrentUser(); User user = getCurrentUser();
boolean removed = team.getMembers().remove(user); boolean removed = team.getMembers().remove(user);
...@@ -154,6 +166,7 @@ public class TeamServiceImpl implements TeamService { ...@@ -154,6 +166,7 @@ public class TeamServiceImpl implements TeamService {
} }
@Override @Override
@PreAuthorize("isAuthenticated()")
public List<Team> listMyTeams() { public List<Team> listMyTeams() {
User user = getCurrentUser(); User user = getCurrentUser();
return listUserTeams(user); return listUserTeams(user);
...@@ -169,4 +182,10 @@ public class TeamServiceImpl implements TeamService { ...@@ -169,4 +182,10 @@ public class TeamServiceImpl implements TeamService {
public Page<Team> listTeams(Pageable pageable) { public Page<Team> listTeams(Pageable pageable) {
return teamRepository.findAll(pageable); return teamRepository.findAll(pageable);
} }
@Override
@PreAuthorize("hasRole('ADMINISTRATOR') or hasPermission(#team, 'READ')")
public List<User> getMembers(Team team) {
return teamRepository.listMembers(team);
}
} }
/**
* Copyright 2013 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;
import org.genesys2.server.model.impl.Team;
import org.genesys2.server.service.ContentService;
import org.genesys2.server.service.InstituteService;
import org.genesys2.server.service.TeamService;
import org.genesys2.spring.ResourceNotFoundException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
@Controller
@Scope("request")
@RequestMapping("/team")
public class TeamController extends BaseController {
@Autowired
private InstituteService instituteService;
@Autowired
private ContentService contentService;
@Autowired
private TeamService teamService;
@RequestMapping("")
@PreAuthorize("hasRole('ADMINISTRATOR')")
public String viewAll(ModelMap model, @RequestParam(value = "page", required = false, defaultValue = "1") int page) {
model.addAttribute("pagedData", teamService.listTeams(new PageRequest(page - 1, 50, new Sort("name"))));
return "/team/index";
}
@RequestMapping("/{teamUuid}")
public String viewTeam(ModelMap model, @PathVariable(value = "teamUuid") String uuid) {
Team team = teamService.getTeam(uuid);
if (team == null) {
throw new ResourceNotFoundException();
}
model.addAttribute("team", team);
model.addAttribute("teammembers", teamService.getMembers(team));
model.addAttribute("blurp", contentService.getArticle(team, "blurp", getLocale()));
return "/team/details";
}
}
...@@ -310,3 +310,7 @@ team.user-teams=User's Teams ...@@ -310,3 +310,7 @@ team.user-teams=User's Teams
team.create-new-team=Create a new team team.create-new-team=Create a new team
team.team-name=Team name team.team-name=Team name
team.leave-team=Leave team team.leave-team=Leave team
team.team-members=Team members
team.page.profile.title=Team: {0}
team.page.list.title=All teams
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<!-- <!--
Copyright 2013 Global Crop Diversity Trust Copyright 2013 Global Crop Diversity Trust
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
You may obtain a copy of the License at You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0 http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
--> -->
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" <ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd">
xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd">
<!--Default cache settings -->
<!--Default cache settings--> <defaultCache maxElementsInMemory="10000" eternal="false" timeToIdleSeconds="1800" timeToLiveSeconds="3600" overflowToDisk="false" />
<defaultCache
maxElementsInMemory="10000" <!--ACL in-memory cache -->
eternal="false" <cache name="sparsedata" maxElementsInMemory="10000" eternal="false" timeToIdleSeconds="1800" timeToLiveSeconds="3600" overflowToDisk="false" />
timeToIdleSeconds="1800"
timeToLiveSeconds="3600" <cache name="sparseentry" maxElementsInMemory="10000" eternal="false" timeToIdleSeconds="1800" timeToLiveSeconds="3600" overflowToDisk="false" />
overflowToDisk="false"/>
<!--CACHEABLE CACHES -->
<!--ACL in-memory cache-->
<cache name="sparsedata" <!--ACL in-memory cache -->
maxElementsInMemory="10000" <cache name="acl" maxElementsInMemory="10000" eternal="false" timeToIdleSeconds="1800" timeToLiveSeconds="3600" overflowToDisk="false" />
eternal="false"
timeToIdleSeconds="1800" <!--HIBERNATE L2 CACHES -->
timeToLiveSeconds="3600" <!--Here are specific caches for all persistence models -->
overflowToDisk="false" />
<cache name="sparseentry"
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="1800"
timeToLiveSeconds="3600"
overflowToDisk="false" />
<!--CACHEABLE CACHES-->
<!--ACL in-memory cache-->
<cache name="acl"
maxElementsInMemory="0"
maxBytesLocalHeap="64M"
eternal="false"
timeToIdleSeconds="1800"
timeToLiveSeconds="3600"
overflowToDisk="false"/>
<!--HIBERNATE L2 CACHES-->
<!--Here are specific caches for all persistence models-->
</ehcache> </ehcache>
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<!-- <!--
Copyright 2013 Global Crop Diversity Trust Copyright 2013 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.
-->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.2.xsd">
<bean name="aclAuthorizationStrategy" class="org.springframework.security.acls.domain.AclAuthorizationStrategyImpl">
<constructor-arg index="0">
<list value-type="org.springframework.security.core.authority.SimpleGrantedAuthority">
<!--TODO review-->
<value>ADMINISTRATOR</value>
</list>
</constructor-arg>
</bean>
<bean name="slf4jAuditLogger" class="org.genesys2.server.security.Slf4jAuditLogger"/>
<bean name="permissionGrantingStrategy" class="org.springframework.security.acls.domain.DefaultPermissionGrantingStrategy"> Licensed under the Apache License, Version 2.0 (the "License");
<constructor-arg index="0" type="org.springframework.security.acls.domain.AuditLogger" you may not use this file except in compliance with the License.
ref="slf4jAuditLogger"/> You may obtain a copy of the License at
</bean>
<bean name="aclCacheBean" factory-bean="ehCacheManager" factory-method="addCacheIfAbsent"> http://www.apache.org/licenses/LICENSE-2.0
<constructor-arg index="0" type="java.lang.String" value="acl"/>
</bean>
<bean name="aclCache" class="org.springframework.security.acls.domain.EhCacheBasedAclCache"> Unless required by applicable law or agreed to in writing, software
<constructor-arg index="0" type="net.sf.ehcache.Ehcache" distributed under the License is distributed on an "AS IS" BASIS,
ref="aclCacheBean"/> WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
<constructor-arg index="1" type="org.springframework.security.acls.model.PermissionGrantingStrategy" See the License for the specific language governing permissions and
ref="permissionGrantingStrategy"/> limitations under the License.
<constructor-arg index="2" type="org.springframework.security.acls.domain.AclAuthorizationStrategy" -->
ref="aclAuthorizationStrategy"/> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans
</bean> http://www.springframework.org/schema/beans/spring-beans-3.2.xsd">
<bean name="lookupStrategy" class="org.springframework.security.acls.jdbc.BasicLookupStrategy"> <bean name="aclAuthorizationStrategy" class="org.springframework.security.acls.domain.AclAuthorizationStrategyImpl">
<constructor-arg index="0" type="javax.sql.DataSource" <constructor-arg index="0">
ref="dataSource"/> <list value-type="org.springframework.security.core.authority.SimpleGrantedAuthority">
<constructor-arg index="1" type="org.springframework.security.acls.model.AclCache" <!--TODO review -->
ref="aclCache"/> <value>ADMINISTRATOR</value>
<constructor-arg index="2" type="org.springframework.security.acls.domain.AclAuthorizationStrategy" </list>
ref="aclAuthorizationStrategy"/> </constructor-arg>
<constructor-arg index="3" type="org.springframework.security.acls.model.PermissionGrantingStrategy" </bean>
ref="permissionGrantingStrategy"/>
</bean> <bean name="slf4jAuditLogger" class="org.genesys2.server.security.Slf4jAuditLogger" />
<bean name="permissionGrantingStrategy" class="org.springframework.security.acls.domain.DefaultPermissionGrantingStrategy">
<constructor-arg type="org.springframework.security.acls.domain.AuditLogger" ref="slf4jAuditLogger" />
</bean>
<bean name="aclCacheBean" factory-bean="ehCacheManager" factory-method="addCacheIfAbsent">
<constructor-arg type="java.lang.String" value="acl" />
</bean>
<bean name="aclCache" class="org.springframework.security.acls.domain.EhCacheBasedAclCache">
<constructor-arg type="net.sf.ehcache.Ehcache" ref="aclCacheBean" />
<constructor-arg type="org.springframework.security.acls.model.PermissionGrantingStrategy" ref="permissionGrantingStrategy" />
<constructor-arg type="org.springframework.security.acls.domain.AclAuthorizationStrategy" ref="aclAuthorizationStrategy" />
</bean>
<bean name="lookupStrategy" class="org.springframework.security.acls.jdbc.BasicLookupStrategy">
<constructor-arg type="javax.sql.DataSource" ref="dataSource" />
<constructor-arg type="org.springframework.security.acls.model.AclCache" ref="aclCache" />
<constructor-arg type="org.springframework.security.acls.domain.AclAuthorizationStrategy" ref="aclAuthorizationStrategy" />
<constructor-arg type="org.springframework.security.acls.model.PermissionGrantingStrategy" ref="permissionGrantingStrategy" />
</bean>
<!-- FIXME Should not be using JdbcMutableAclService, but own service! --> <!-- FIXME Should not be using JdbcMutableAclService, but own service! -->
<bean name="aclService" class="org.springframework.security.acls.jdbc.JdbcMutableAclService"> <bean name="aclService" class="org.springframework.security.acls.jdbc.JdbcMutableAclService">
<constructor-arg index="0" type="javax.sql.DataSource" <constructor-arg type="javax.sql.DataSource" ref="dataSource" />
ref="dataSource"/> <constructor-arg type="org.springframework.security.acls.jdbc.LookupStrategy" ref="lookupStrategy" />
<constructor-arg index="1" type="org.springframework.security.acls.jdbc.LookupStrategy" <constructor-arg type="org.springframework.security.acls.model.AclCache" ref="aclCache" />
ref="lookupStrategy"/> </bean>
<constructor-arg index="2" type="org.springframework.security.acls.model.AclCache"
ref="aclCache"/> <bean name="permissionEvaluator" class="org.springframework.security.acls.AclPermissionEvaluator">
</bean> <constructor-arg index="0" type="org.springframework.security.acls.model.AclService" ref="aclService" />
</bean>
<bean name="permissionEvaluator" class="org.springframework.security.acls.AclPermissionEvaluator">
<constructor-arg index="0" type="org.springframework.security.acls.model.AclService"
ref="aclService"/>
</bean>
</beans> </beans>
<!DOCTYPE html>
<%@include file="/WEB-INF/jsp/init.jsp"%>
<html>
<head>
<title><spring:message code="team.page.profile.title" arguments="${team.name}" argumentSeparator="||" /></title>
</head>
<body>
<h1>
<c:out value="${team.name}" />
</h1>
<h4>
<spring:message code="team.team-members" arguments="${teammembers.size()}" />
</h4>
<ul class="funny-list">
<c:forEach items="${teammembers}" var="user" varStatus="status">
<li class="${status.count % 2 == 0 ? 'even' : 'odd'}">
<c:out value="${user.name}" />
</li>
</c:forEach>
</ul>
</body>
</html>
\ No newline at end of file
<!DOCTYPE html>
<%@include file="/WEB-INF/jsp/init.jsp"%>
<html>
<head>
<title><spring:message code="team.page.list.title" /></title>
</head>
<body>
<h1>
<spring:message code="team.page.list.title" />
</h1>
<div class="nav-header">
<spring:message code="paged.totalElements" arguments="${pagedData.totalElements}" />
<br />
<spring:message code="paged.pageOfPages" arguments="${pagedData.number+1},${pagedData.totalPages}" />
<a class="${pagedData.number eq 0 ? 'disabled' :''}" href="?page=${pagedData.number eq 0 ? 1 : pagedData.number}"><spring:message code="pagination.previous-page" /></a> <a href="?page=${pagedData.number + 2}"><spring:message code="pagination.next-page" /></a>
</div>
<ul class="funny-list">
<c:forEach items="${pagedData.content}" var="team" varStatus="status">
<li class="clearfix ${status.count % 2 == 0 ? 'even' : 'odd'}">
<a href="<c:url value="/team/${team.uuid}" />"><c:out value="${team.name}" /></a>
</li>
</c:forEach>
</ul>
</body>
</html>
\ No newline at end of file
...@@ -49,7 +49,7 @@ ...@@ -49,7 +49,7 @@
<label for="team-name" class="col-lg-2 control-label"><spring:message code="team.team-name" /></label> <label for="team-name" class="col-lg-2 control-label"><spring:message code="team.team-name" /></label>
<div class="col-lg-3"><input type="text" name="name" id="team-name" class="span3 form-control" /></div> <div class="col-lg-3"><input type="text" name="name" id="team-name" class="span3 form-control" /></div>
<div class="col-lg-1"> <div class="col-lg-1">
<input type="submit" value="<spring:message code="create"/>" class="btn btn-primary" /> <input type="submit" value="<spring:message code="create" />" class="btn btn-primary" />
</div> </div>
</div> </div>
</form> </form>
......
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