Skip to content

Commit 833b1fd

Browse files
authored
#397: support JWT microprofile (#399)
1 parent 177d68d commit 833b1fd

File tree

13 files changed

+153
-36
lines changed

13 files changed

+153
-36
lines changed

CHANGELOG.asciidoc

+14
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,23 @@
22

33
This file documents all notable changes to https://https://github.com/devonfw/devon4j[devon4j].
44

5+
== 2021.04.003
6+
7+
ATTENTION: This release is still work in progress.
8+
9+
Bugfix release of with the following stories:
10+
11+
* https://github.com/devonfw/devon4j/issues/336[#336]: archetype contains batch artefacts even when no batch was generated
12+
* https://github.com/devonfw/devon4j/issues/385[#385]: Access-control should honor roles by default
13+
* https://github.com/devonfw/devon4j/issues/397[#397]: security-jwt should support the claim "groups" from the microprofile jwt
14+
15+
Documentation is available at https://repo.maven.apache.org/maven2/com/devonfw/java/doc/devon4j-doc/2021.04.003/devon4j-doc-2021.04.003.pdf[devon4j guide 2021.04.003].
16+
The full list of changes for this release can be found in https://github.com/devonfw/devon4j/milestone/19?closed=1[milestone devon4j 2020.04.003].
17+
518
== 2021.04.002
619

720
Bugfix release of with the following stories:
21+
822
* https://github.com/devonfw/devon4j/issues/389[#389]: archetype build broken with ci-friendly-maven
923
* https://github.com/devonfw/devon4j/pull/391[#391]: jasypt documentation improvements
1024
* https://github.com/devonfw/devon4j/pull/387[#387]: rebuild and updated diagram with drawio

documentation/guide-jwt.asciidoc

+6
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,14 @@ security.authentication.jwt.validation.not-before-required=false
6363
security.authentication.jwt.creation.add-issued-at=true
6464
security.authentication.jwt.creation.validity=4h
6565
security.authentication.jwt.creation.not-before-delay=1m
66+
# the following properties enable backward compatiblity for devon4j <= 2021.04.002
67+
# after microprofile JWT is used by default since 2021.04.003
68+
#security.authentication.jwt.claims.access-controls-name=roles
69+
#security.authentication.jwt.claims.access-controls-array=false
6670
----
6771

72+
See also https://github.com/devonfw/devon4j/blob/master/modules/security-jwt/src/main/java/com/devonfw/module/security/jwt/common/impl/JwtConfigProperties.java[JwtConfigProperties] for details about configuration.
73+
6874
== Authentication with JWT via OAuth
6975

7076
The authentication with JWT via OAuth (HTTP header), will happen via `JwtAuthenticationFilter` that is automatically added by `devon4j-starter-security-jwt` via `JwtAutoConfiguration`.

modules/security-jwt/src/main/java/com/devonfw/module/security/jwt/common/api/JwtManager.java

+4
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,11 @@ public interface JwtManager {
1414
/**
1515
* Custom {@link Claims#get(String, Class) claim} for the
1616
* {@link com.devonfw.module.security.common.api.accesscontrol.AccessControl roles/permissions} assigned to the user.
17+
*
18+
* @deprecated configurable via {@link com.devonfw.module.security.jwt.common.impl.JwtConfigProperties#getClaims()} in
19+
* {@link com.devonfw.module.security.jwt.common.impl.JwtConfigProperties.ClaimsConfigProperties#getAccessControlsName()}.
1720
*/
21+
@Deprecated
1822
String CLAIM_ROLES = "roles";
1923

2024
/** Custom {@link Claims#get(String, Class) claim} for the email address of the user. */

modules/security-jwt/src/main/java/com/devonfw/module/security/jwt/common/base/JwtConstants.java

+4
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,11 @@ public class JwtConstants {
99

1010
/**
1111
* The roles/permissions assigned to the user
12+
*
13+
* @deprecated configurable via {@link com.devonfw.module.security.jwt.common.impl.JwtConfigProperties#getClaims()} in
14+
* {@link com.devonfw.module.security.jwt.common.impl.JwtConfigProperties.ClaimsConfigProperties#getAccessControlsName()}.
1215
*/
16+
@Deprecated
1317
public static final String CLAIM_ROLES = "roles";
1418

1519
/**

modules/security-jwt/src/main/java/com/devonfw/module/security/jwt/common/impl/JwtAuthenticatorImpl.java

+21-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package com.devonfw.module.security.jwt.common.impl;
22

33
import java.util.Arrays;
4+
import java.util.Collection;
5+
import java.util.Collections;
46
import java.util.Set;
57

68
import javax.inject.Inject;
@@ -30,13 +32,30 @@ public class JwtAuthenticatorImpl implements JwtAuthenticator {
3032
@Inject
3133
private AccessControlProvider accessControlProvider;
3234

35+
@Inject
36+
private JwtConfigProperties jwtConfig;
37+
38+
@SuppressWarnings("unchecked")
3339
@Override
3440
public Authentication authenticate(String jwt) {
3541

3642
Claims claims = this.jwtManager.decodeAndVerify(jwt);
3743
String principal = claims.getSubject();
38-
String[] roleIds = claims.get(JwtManager.CLAIM_ROLES, String.class).split(",");
39-
Set<AccessControl> permissions = this.accessControlProvider.expandPermissions(Arrays.asList(roleIds));
44+
String accessControlsName = this.jwtConfig.getClaims().getAccessControlsName();
45+
Collection<String> accessControlIds;
46+
Object accessControls = claims.get(accessControlsName);
47+
if (accessControls instanceof String) {
48+
accessControlIds = Arrays.asList(accessControls.toString().split(","));
49+
} else if (accessControls instanceof String[]) {
50+
accessControlIds = Arrays.asList((String[]) accessControls);
51+
} else if (accessControls instanceof Collection) {
52+
accessControlIds = (Collection<String>) accessControls;
53+
} else if (accessControls == null) {
54+
accessControlIds = Collections.emptyList();
55+
} else {
56+
throw new IllegalStateException("Invalid or malformed JWT claim " + accessControlsName + ": " + accessControls);
57+
}
58+
Set<AccessControl> permissions = this.accessControlProvider.expandPermissions(accessControlIds);
4059
return DefaultAuthentication.ofAccessControls(principal, jwt, permissions, claims);
4160
}
4261

modules/security-jwt/src/main/java/com/devonfw/module/security/jwt/common/impl/JwtConfigProperties.java

+62
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ public class JwtConfigProperties {
1818

1919
private String alias = "jwt";
2020

21+
private ClaimsConfigProperties claims = new ClaimsConfigProperties();
22+
2123
private ValidationConfigProperties validation = new ValidationConfigProperties();
2224

2325
private CreationConfigProperties creation = new CreationConfigProperties();
@@ -39,6 +41,14 @@ public CreationConfigProperties getCreation() {
3941
return this.creation;
4042
}
4143

44+
/**
45+
* @return the {@link ClaimsConfigProperties configuration of the JWT claims}.
46+
*/
47+
public ClaimsConfigProperties getClaims() {
48+
49+
return this.claims;
50+
}
51+
4252
/**
4353
* @return algorithm, which can be configured
4454
*/
@@ -221,4 +231,56 @@ public void setAddIssuedAt(boolean addIssuedAt) {
221231

222232
}
223233

234+
/**
235+
* {@link ConfigurationProperties} for claims of the JWT.
236+
*
237+
* @see JwtConfigProperties#getClaims
238+
*/
239+
public static class ClaimsConfigProperties {
240+
241+
private String accessControlsName = "groups";
242+
243+
private boolean accessControlsArray = true;
244+
245+
/**
246+
* @return name of the claim containing the access-controls (groups, roles, permissions). Defaults to "groups"
247+
* according to
248+
* <a href="https://www.eclipse.org/community/eclipse_newsletter/2017/september/article2.php">MP-JWT</a>
249+
* standard.
250+
*/
251+
public String getAccessControlsName() {
252+
253+
return this.accessControlsName;
254+
}
255+
256+
/**
257+
* @param accessControlsName new value of {@link #getAccessControlsName()}.
258+
*/
259+
public void setAccessControlsName(String accessControlsName) {
260+
261+
this.accessControlsName = accessControlsName;
262+
}
263+
264+
/**
265+
* @return {@code true} if a JSON array should be used to encode the actual items in the
266+
* {@link #getAccessControlsName() access controls claim}, {@code false} to use a comma separated JSON
267+
* string. Defaults to {@code true} according to
268+
* <a href="https://www.eclipse.org/community/eclipse_newsletter/2017/september/article2.php">MP-JWT</a>
269+
* standard.
270+
*/
271+
public boolean isAccessControlsArray() {
272+
273+
return this.accessControlsArray;
274+
}
275+
276+
/**
277+
* @param accessControlsArray new value of {@link #isAccessControlsArray()}.
278+
*/
279+
public void setAccessControlsArray(boolean accessControlsArray) {
280+
281+
this.accessControlsArray = accessControlsArray;
282+
}
283+
284+
}
285+
224286
}

modules/security-jwt/src/main/java/com/devonfw/module/security/jwt/common/impl/JwtCreatorImpl.java

+12-2
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ public class JwtCreatorImpl implements JwtCreator {
2727
@Inject
2828
private JwtManager jwtManager;
2929

30+
@Inject
31+
private JwtConfigProperties jwtConfig;
32+
3033
@Override
3134
public String create(Authentication authentication) {
3235

@@ -37,6 +40,7 @@ public String create(Authentication authentication) {
3740
if (attributes instanceof Claims) {
3841
Object credentials = authentication.getCredentials();
3942
if (credentials instanceof String) {
43+
// in this case we already have the existing encoded JWT
4044
return (String) credentials;
4145
}
4246
claims = (Claims) attributes;
@@ -46,8 +50,14 @@ public String create(Authentication authentication) {
4650
claims = new DefaultClaims();
4751
claims.setSubject(authentication.getName());
4852
Set<String> permissions = AdvancedAuthentication.getPermissions(authentication);
49-
String roles = String.join(",", permissions);
50-
claims.put(JwtManager.CLAIM_ROLES, roles);
53+
String accessControlsName = this.jwtConfig.getClaims().getAccessControlsName();
54+
Object accessControls;
55+
if (this.jwtConfig.getClaims().isAccessControlsArray()) {
56+
accessControls = permissions;
57+
} else {
58+
accessControls = String.join(",", permissions);
59+
}
60+
claims.put(accessControlsName, accessControls);
5161
}
5262
return this.jwtManager.encodeAndSign(claims);
5363
}

modules/security/src/main/java/com/devonfw/module/security/common/api/accesscontrol/AccessControlProvider.java

+5-5
Original file line numberDiff line numberDiff line change
@@ -54,16 +54,16 @@ public interface AccessControlProvider {
5454
boolean collectAccessControls(String id, Set<AccessControl> permissions);
5555

5656
/**
57-
* This is a convenvience method to expand the permissions for all given roleIds. So for each provided roleId the
57+
* This is a convenience method to expand the permissions for all given roleIds. So for each provided roleId the
5858
* corresponding {@link AccessControl} are collected via {@link #collectAccessControls(String, Set)}.
59-
*
60-
* @param roleIds The IDs of the roles.
59+
*
60+
* @param accessControlIds the {@link Collection} of {@link AccessControl#getId()} access control IDs.
6161
* @return A collection of {@link AccessControl} belonging to the given roleIds.
6262
*/
63-
default Set<AccessControl> expandPermissions(Collection<String> roleIds) {
63+
default Set<AccessControl> expandPermissions(Collection<String> accessControlIds) {
6464

6565
Set<AccessControl> accessControlSet = new HashSet<>();
66-
for (String id : roleIds) {
66+
for (String id : accessControlIds) {
6767
collectAccessControls(id, accessControlSet);
6868
}
6969
return accessControlSet;

starters/starter-security-jwt/src/test/java/com/devonfw/module/security/jwt/common/api/JwtAuthenticatorTest.java

+2-5
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ public class JwtAuthenticatorTest extends JwtComponentTest {
2222
* Test of {@link JwtAuthenticator#authenticate(String)}.
2323
*/
2424
@Test
25-
public void testDo() {
25+
public void testAuthenticateJwt() {
2626

2727
// prepare
2828
adjustClock();
@@ -41,10 +41,7 @@ public void testDo() {
4141
assertThat(authentication).isInstanceOf(AdvancedAuthentication.class);
4242
assertThat(authentication.getCredentials()).isEqualTo(token);
4343
AdvancedAuthentication advancedAuthentication = (AdvancedAuthentication) authentication;
44-
assertThat((Number) advancedAuthentication.getAttribute(Claims.EXPIRATION)).isEqualTo(1587742156);
45-
46-
// reset/cleanup
47-
resetClock();
44+
assertThat((Number) advancedAuthentication.getAttribute(Claims.EXPIRATION)).isEqualTo(1587730516);
4845
}
4946

5047
}

starters/starter-security-jwt/src/test/java/com/devonfw/module/security/jwt/common/api/JwtComponentTest.java

+16-7
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
import java.time.Clock;
44
import java.time.Instant;
55
import java.time.ZoneOffset;
6+
import java.util.Arrays;
7+
import java.util.Collection;
68
import java.util.Date;
79

810
import javax.inject.Inject;
@@ -12,8 +14,8 @@
1214

1315
import com.devonfw.module.security.jwt.common.impl.JwtManagerImpl;
1416
import com.devonfw.module.test.common.base.ComponentTest;
15-
import com.devonfw.test.app.TestJwtAccessControlConfig;
1617
import com.devonfw.test.app.TestApplication;
18+
import com.devonfw.test.app.TestJwtAccessControlConfig;
1719

1820
/**
1921
* Abstract test class for JWT {@link ComponentTest}s.
@@ -28,8 +30,8 @@ public abstract class JwtComponentTest extends ComponentTest {
2830
protected static final String TEST_ISSUER = "devon4j";
2931

3032
/** Roles for testing. */
31-
protected static final String TEST_ROLES = TestJwtAccessControlConfig.GROUP_READ_MASTER_DATA + ","
32-
+ TestJwtAccessControlConfig.GROUP_SAVE_USER;
33+
protected static final Collection<String> TEST_ACCESS_CONTROLS = Arrays
34+
.asList(TestJwtAccessControlConfig.GROUP_READ_MASTER_DATA, TestJwtAccessControlConfig.GROUP_SAVE_USER);
3335

3436
/** Expiration in millis as configured in application.properties */
3537
protected long EXPIRATION_MS = 4 * 60 * 60 * 1000;
@@ -38,7 +40,7 @@ public abstract class JwtComponentTest extends ComponentTest {
3840
protected long NOT_BEFORE_DELAY_MS = 1 * 60 * 1000;
3941

4042
/** Test JSON Web Token. */
41-
protected static final String TEST_JWT = "eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJqb2huLmRvZSIsInJvbGVzIjoidGVzdC5SZWFkTWFzdGVyRGF0YSx0ZXN0LlNhdmVVc2VyIiwiaXNzIjoiZGV2b240aiIsIm5iZiI6MTU4NzcxMzA1NiwiZXhwIjoxNTg3NzQyMTU2fQ.Onx1p7ZrscBcGRcfVlRncVKLL-j1QMC_n9SgPLq1PjjEJMS6VRz52u0o2lI2LwdPCjve0ujdHfuYHje4jyGgFJwMbL45CpYijl9gFsHzu4WLvw8qH2sZgpe0LSl0ZqdpvpsGtBYqQeL0jdIkz0ppai9U5Ctyr6fxl5LCXeLrQ991dK3n9vJ6i5eEPVL_6TyK5nzTMuJfgxNNLxqxo_drKQGywitQfv0IPpwFdEe5537_buibF1QAOdDCCi-qN8hyZiut0wXmBu1k4yDgw3IwdkCBngi7KAGaHlSI0smRH-wNpsf0pTlYwk1U6AGCCyrxks0WMJG528rtA3HQL5lae6KtyCGW6xfHav9HvmAMU0y7TQlzqadVddxrxdGTP342Y2yjc0HKeKDLLi84Rzp8Z5AkJrG-f0Gcah_ExO9rU9jE3OUpSTmbZAuks56hLC6bik8cZHh9aJ7J-7CG7_5c224fRtlayp0GHPAIYSLZU1SIvN68mgGLpJKGQYNA7l3RFQbfe-h_nfBMAM0Izk7TiHyEM_E7rkh2aBxgkByXdMzm8GQmvDV_is7It2r1DIyHUydYkGAJuJB0sbuX8bkIR3t9ylW-MoGZw5KVUWkNNDlBEzw1bgX6j17JU8al6CWsau9LIt9tSLnrY7YDCpQG1lEXHk95OQCp83wminUnbU8";
43+
protected static final String TEST_JWT = "eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJqb2huLmRvZSIsImdyb3VwcyI6WyJ0ZXN0LlJlYWRNYXN0ZXJEYXRhIiwidGVzdC5TYXZlVXNlciJdLCJpc3MiOiJkZXZvbjRqIiwibmJmIjoxNTg3NzE2MDU2LCJleHAiOjE1ODc3MzA1MTZ9.MXNd7Z8hlqzoeR_HcN39f-vzscoS8aTQf9PXO5ATsV1Rre9vihq-IIxKIEY8-JXd7NJ_9TMlMpY70l--0A0EsyBU3ZTbiV8yNMe_k6AteBiZhZ7WR31JhtuEMVViCTHlTXnRlWpYMtTCCNFSI9sNv_s7YU38pgHmojmAI-so09F8c9uOb1PyWlX-lBfkr39AIFq2RgcAiAVApiXNqlgvrDSGQgV72Gjqt7JOjc38wi0DZBVYscRCkkrxMuIH1j1BsCciuwZCpKURhKJ2dl5KaGkCt0xVuEAKuOc31zOPiBjf8M-5lqHkEKD9ei9lPIPKI0gMbwal2YGexJE36qnB_K0aLktsjNfUf6dtxhqUVIL45OeWAXjWwnQNsLr7Lc92scRYU2Eh9KzRp18R8W1lRgRrXlETErpDDbKf1Or8NDQtbUmoS7tNWZsSWVmXhC5-ScFSFxZjA88Eo7vtS0SM2S1O8dz6iEaiXB8xDqRWkqKaBLPFznzs3iwj-LW2WZPLDM7Md8f9_1dV9JMZVXPBU9wL-R9udjXzDoI1-1yOxEyNgXWp83AmDSlsHGgBDRVkxVaaKrkTzLzVadrv8KRHsSrIPTxk4R6EBL7exbC-JB5_CKfH9pMzzIrkAws3uAGQyekAlqnWtwy-vE2stzyBF9I6EU9Tj4xE60zTBWbXPX8";
4244

4345
/** Test JSON Invalid Web Token. */
4446
protected static final String INVALID_TEST_JWT = "AeyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJqb2huLmRvZSIsInJvbGVzIjoidGVzdC5SZWFkTWFzdGVyRGF0YSx0ZXN0LlNhdmVVc2VyIiwiaXNzIjoiZGV2b240aiIsIm5iZiI6MTU4NzcxMzA1NiwiZXhwIjoxNTg3NzQyMTU2fQ.Onx1p7ZrscBcGRcfVlRncVKLL-j1QMC_n9SgPLq1PjjEJMS6VRz52u0o2lI2LwdPCjve0ujdHfuYHje4jyGgFJwMbL45CpYijl9gFsHzu4WLvw8qH2sZgpe0LSl0ZqdpvpsGtBYqQeL0jdIkz0ppai9U5Ctyr6fxl5LCXeLrQ991dK3n9vJ6i5eEPVL_6TyK5nzTMuJfgxNNLxqxo_drKQGywitQfv0IPpwFdEe5537_buibF1QAOdDCCi-qN8hyZiut0wXmBu1k4yDgw3IwdkCBngi7KAGaHlSI0smRH-wNpsf0pTlYwk1U6AGCCyrxks0WMJG528rtA3HQL5lae6KtyCGW6xfHav9HvmAMU0y7TQlzqadVddxrxdGTP342Y2yjc0HKeKDLLi84Rzp8Z5AkJrG-f0Gcah_ExO9rU9jE3OUpSTmbZAuks56hLC6bik8cZHh9aJ7J-7CG7_5c224fRtlayp0GHPAIYSLZU1SIvN68mgGLpJKGQYNA7l3RFQbfe-h_nfBMAM0Izk7TiHyEM_E7rkh2aBxgkByXdMzm8GQmvDV_is7It2r1DIyHUydYkGAJuJB0sbuX8bkIR3t9ylW-MoGZw5KVUWkNNDlBEzw1bgX6j17JU8al6CWsau9LIt9tSLnrY7YDCpQG1lEXHk95OQCp83wminUnbU8";
@@ -71,9 +73,10 @@ protected void adjustClock() {
7173
*/
7274
protected void resetClock() {
7375

74-
assertThat(this.clock).as("Original clock stored on adjustClock()").isNotNull();
75-
this.jwtManager.setClock(this.clock);
76-
this.clock = null;
76+
if (this.clock != null) {
77+
this.jwtManager.setClock(this.clock);
78+
this.clock = null;
79+
}
7780
}
7881

7982
/**
@@ -86,4 +89,10 @@ protected Date addToDate(Date date, long millis) {
8689
return new Date(date.getTime() + millis);
8790
}
8891

92+
@Override
93+
protected void doTearDown() {
94+
95+
resetClock();
96+
}
97+
8998
}

starters/starter-security-jwt/src/test/java/com/devonfw/module/security/jwt/common/api/JwtCreatorTest.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ public class JwtCreatorTest extends JwtComponentTest {
2828
* Test of {@link JwtCreator#create(Authentication)}.
2929
*/
3030
@Test
31-
public void testDo() {
31+
public void testCreateJwt() {
3232

3333
// given
3434
List<GrantedAuthority> authorities = new ArrayList<>();

starters/starter-security-jwt/src/test/java/com/devonfw/module/security/jwt/common/api/JwtManagerTest.java

+6-9
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@
44

55
import org.junit.jupiter.api.Test;
66

7-
import com.devonfw.module.security.jwt.common.base.JwtConstants;
8-
97
import io.jsonwebtoken.Claims;
108
import io.jsonwebtoken.impl.DefaultClaims;
119

@@ -14,6 +12,8 @@
1412
*/
1513
public class JwtManagerTest extends JwtComponentTest {
1614

15+
private static final String CLAIM_GROUPS = "groups";
16+
1717
/**
1818
* Test of encoding claims to JWT and decoding to assert claims afterwards.
1919
*/
@@ -24,7 +24,7 @@ public void testEncodeAndDecode() {
2424
String login = TEST_LOGIN;
2525
DefaultClaims claims = new DefaultClaims();
2626
claims.setSubject(login);
27-
claims.put(JwtConstants.CLAIM_ROLES, TEST_ROLES);
27+
claims.put(CLAIM_GROUPS, TEST_ACCESS_CONTROLS);
2828
Date now = new Date();
2929
JwtManager jwtManager = getJwtManager();
3030

@@ -59,12 +59,9 @@ public void testDecodeAndVerify() {
5959
// then
6060
assertThat(claims.getIssuer()).isEqualTo(TEST_ISSUER);
6161
assertThat(claims.getSubject()).isEqualTo(TEST_LOGIN);
62-
assertThat(claims.get(JwtConstants.CLAIM_ROLES)).isEqualTo(TEST_ROLES);
63-
assertThat(claims.getNotBefore()).isEqualTo(new Date(1587713056000L));
64-
assertThat(claims.getExpiration()).isEqualTo(new Date(1587742156000L));
65-
66-
// cleanup/reset
67-
resetClock();
62+
assertThat(claims.get(CLAIM_GROUPS)).isEqualTo(TEST_ACCESS_CONTROLS);
63+
assertThat(claims.getNotBefore()).isEqualTo(new Date(1587716056000L));
64+
assertThat(claims.getExpiration()).isEqualTo(new Date(1587730516000L));
6865
}
6966

7067
}

0 commit comments

Comments
 (0)