Skip to content

Commit 4249cd6

Browse files
author
damoeb
committed
fix capabilities
1 parent 57cb65a commit 4249cd6

File tree

5 files changed

+53
-200
lines changed

5 files changed

+53
-200
lines changed

packages/server-core/src/main/kotlin/org/migor/feedless/repository/RepositoryResolver.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ class RepositoryResolver(
104104

105105
@Throttled
106106
@DgsMutation(field = DgsConstants.MUTATION.CreateRepositories)
107-
@PreAuthorize("hasToken()")
107+
@PreAuthorize("@capabilityService.hasToken()")
108108
suspend fun createRepositories(
109109
dfe: DataFetchingEnvironment,
110110
@InputArgument(DgsConstants.MUTATION.CREATEREPOSITORIES_INPUT_ARGUMENT.Data) data: List<RepositoryCreateInput>,

packages/server-core/src/main/kotlin/org/migor/feedless/scrape/ScrapeQueryResolver.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ class ScrapeQueryResolver {
4545

4646
@Throttled
4747
@DgsQuery(field = DgsConstants.QUERY.Scrape)
48-
@PreAuthorize("hasToken()")
48+
@PreAuthorize("@capabilityService.hasToken()")
4949
suspend fun scrape(
5050
dfe: DataFetchingEnvironment,
5151
@InputArgument(DgsConstants.QUERY.SCRAPE_INPUT_ARGUMENT.Data) data: SourceInput,

packages/server-core/src/main/kotlin/org/migor/feedless/session/JwtRequestFilter.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ class JwtRequestFilter : Filter {
4343
runCatching {
4444
SecurityContextHolder.getContext().authentication =
4545
toOAuth2AuthenticationToken(authService.interceptToken(request))
46-
}.onFailure { log.error(it.message, it) }
46+
}.onFailure { log.debug(it.message) }
4747
}
4848
val attributes = ServletRequestAttributes(request)
4949
val corrId = StringUtils.trimToNull(request.getHeader(ApiParams.corrId)) ?: newCorrId()

packages/server-core/src/main/kotlin/org/migor/feedless/session/StatefulAuthService.kt

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -133,16 +133,14 @@ class StatefulAuthService : AuthService() {
133133
private fun resolveWhitelistedHosts() {
134134
this.whitelistedIps = whitelistedHostsParam
135135
.trim()
136-
.split(" ", ",")
137-
.map {
136+
.split(" ", ",").mapNotNull {
138137
try {
139138
InetAddress.getByName(it.trim()).hostAddress
140139
} catch (e: Exception) {
141140
log.warn("Cannot resolve DNS $it: ${e.message}")
142141
null
143142
}
144143
}
145-
.filterNotNull()
146144
.plus(
147145
listOf(
148146
InetAddress.getLocalHost().hostAddress,

packages/server-core/src/test/kotlin/org/migor/feedless/config/CustomSecurityExpressionRootTest.kt

Lines changed: 49 additions & 194 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,12 @@ import org.assertj.core.api.Assertions.assertThat
55
import org.junit.jupiter.api.Test
66
import org.migor.feedless.capability.SecurityContextCapabilityService
77
import org.migor.feedless.session.LazyGrantedAuthority
8+
import org.mockito.Mockito
89
import org.mockito.Mockito.mock
910
import org.mockito.Mockito.`when`
1011
import org.springframework.security.core.Authentication
11-
import org.springframework.security.core.GrantedAuthority
12-
import org.springframework.security.core.authority.SimpleGrantedAuthority
12+
import org.springframework.security.core.context.SecurityContext
13+
import org.springframework.security.core.context.SecurityContextHolder
1314

1415
class CustomSecurityExpressionRootTest {
1516

@@ -23,11 +24,13 @@ class CustomSecurityExpressionRootTest {
2324
)
2425
`when`(authentication.authorities).thenReturn(authorities)
2526

26-
val expressionRoot = SecurityContextCapabilityService(authentication)
27+
mockAuthentication(authentication) {
28+
val expressionRoot = SecurityContextCapabilityService()
2729

28-
// when & then
29-
assertThat(expressionRoot.hasCapability("user")).isTrue()
30-
assertThat(expressionRoot.hasCapability("groups")).isTrue()
30+
// when & then
31+
assertThat(expressionRoot.hasCapability("user")).isTrue()
32+
assertThat(expressionRoot.hasCapability("groups")).isTrue()
33+
}
3134
}
3235

3336
@Test
@@ -39,12 +42,14 @@ class CustomSecurityExpressionRootTest {
3942
)
4043
`when`(authentication.authorities).thenReturn(authorities)
4144

42-
val expressionRoot = SecurityContextCapabilityService(authentication)
45+
mockAuthentication(authentication) {
46+
val expressionRoot = SecurityContextCapabilityService()
4347

44-
// when & then
45-
assertThat(expressionRoot.hasCapability("agent")).isFalse()
46-
assertThat(expressionRoot.hasCapability("admin")).isFalse()
47-
assertThat(expressionRoot.hasCapability("nonexistent")).isFalse()
48+
// when & then
49+
assertThat(expressionRoot.hasCapability("agent")).isFalse()
50+
assertThat(expressionRoot.hasCapability("admin")).isFalse()
51+
assertThat(expressionRoot.hasCapability("nonexistent")).isFalse()
52+
}
4853
}
4954

5055
@Test
@@ -53,47 +58,13 @@ class CustomSecurityExpressionRootTest {
5358
val authentication = mock(Authentication::class.java)
5459
`when`(authentication.authorities).thenReturn(emptyList())
5560

56-
val expressionRoot = SecurityContextCapabilityService(authentication)
61+
mockAuthentication(authentication) {
62+
val expressionRoot = SecurityContextCapabilityService()
5763

58-
// when & then
59-
assertThat(expressionRoot.hasCapability("user")).isFalse()
60-
assertThat(expressionRoot.hasCapability("agent")).isFalse()
61-
}
62-
63-
@Test
64-
fun `hasCapability is case sensitive`() = runTest {
65-
// given
66-
val authentication = mock(Authentication::class.java)
67-
val authorities = listOf(
68-
LazyGrantedAuthority("user", """{"id":"test-user-id"}""")
69-
)
70-
`when`(authentication.authorities).thenReturn(authorities)
71-
72-
val expressionRoot = SecurityContextCapabilityService(authentication)
73-
74-
// when & then
75-
assertThat(expressionRoot.hasCapability("user")).isTrue()
76-
assertThat(expressionRoot.hasCapability("USER")).isFalse()
77-
assertThat(expressionRoot.hasCapability("User")).isFalse()
78-
}
79-
80-
@Test
81-
fun `hasCapability ignores non-LazyGrantedAuthority authorities`() = runTest {
82-
// given
83-
val authentication = mock(Authentication::class.java)
84-
val authorities: List<GrantedAuthority> = listOf(
85-
SimpleGrantedAuthority("ROLE_USER"),
86-
LazyGrantedAuthority("user", """{"id":"test-user-id"}"""),
87-
SimpleGrantedAuthority("ROLE_ADMIN")
88-
)
89-
`when`(authentication.authorities).thenReturn(authorities)
90-
91-
val expressionRoot = SecurityContextCapabilityService(authentication)
92-
93-
// when & then
94-
assertThat(expressionRoot.hasCapability("user")).isTrue()
95-
assertThat(expressionRoot.hasCapability("ROLE_USER")).isFalse()
96-
assertThat(expressionRoot.hasCapability("ROLE_ADMIN")).isFalse()
64+
// when & then
65+
assertThat(expressionRoot.hasCapability("user")).isFalse()
66+
assertThat(expressionRoot.hasCapability("agent")).isFalse()
67+
}
9768
}
9869

9970
@Test
@@ -106,14 +77,15 @@ class CustomSecurityExpressionRootTest {
10677
LazyGrantedAuthority("groups", """[]""")
10778
)
10879
`when`(authentication.authorities).thenReturn(authorities)
80+
mockAuthentication(authentication) {
81+
val expressionRoot = SecurityContextCapabilityService()
10982

110-
val expressionRoot = SecurityContextCapabilityService(authentication)
111-
112-
// when & then
113-
assertThat(expressionRoot.hasCapability("user")).isTrue()
114-
assertThat(expressionRoot.hasCapability("agent")).isTrue()
115-
assertThat(expressionRoot.hasCapability("groups")).isTrue()
116-
assertThat(expressionRoot.hasCapability("admin")).isFalse()
83+
// when & then
84+
assertThat(expressionRoot.hasCapability("user")).isTrue()
85+
assertThat(expressionRoot.hasCapability("agent")).isTrue()
86+
assertThat(expressionRoot.hasCapability("groups")).isTrue()
87+
assertThat(expressionRoot.hasCapability("admin")).isFalse()
88+
}
11789
}
11890

11991
@Test
@@ -122,154 +94,37 @@ class CustomSecurityExpressionRootTest {
12294
val authentication = mock(Authentication::class.java)
12395
`when`(authentication.isAuthenticated).thenReturn(true)
12496

125-
val expressionRoot = SecurityContextCapabilityService(authentication)
97+
mockAuthentication(authentication) {
98+
val expressionRoot = SecurityContextCapabilityService()
12699

127-
// when & then
128-
assertThat(expressionRoot.hasToken()).isTrue()
100+
// when & then
101+
assertThat(expressionRoot.hasToken()).isTrue()
102+
}
129103
}
130104

131105
@Test
132106
fun `hasToken returns false when authentication is not authenticated`() = runTest {
133107
// given
134108
val authentication = mock(Authentication::class.java)
135109
`when`(authentication.isAuthenticated).thenReturn(false)
110+
mockAuthentication(authentication) {
111+
val expressionRoot = SecurityContextCapabilityService()
136112

137-
val expressionRoot = SecurityContextCapabilityService(authentication)
138-
139-
// when & then
140-
assertThat(expressionRoot.hasToken()).isFalse()
141-
}
142-
143-
@Test
144-
fun `hasToken returns true for authenticated user with capabilities`() = runTest {
145-
// given
146-
val authentication = mock(Authentication::class.java)
147-
`when`(authentication.isAuthenticated).thenReturn(true)
148-
val authorities = listOf(
149-
LazyGrantedAuthority("user", """{"id":"test-user-id"}""")
150-
)
151-
`when`(authentication.authorities).thenReturn(authorities)
152-
153-
val expressionRoot = SecurityContextCapabilityService(authentication)
154-
155-
// when & then
156-
assertThat(expressionRoot.hasToken()).isTrue()
157-
assertThat(expressionRoot.hasCapability("user")).isTrue()
158-
}
159-
160-
@Test
161-
fun `hasToken returns true even without capabilities`() = runTest {
162-
// given
163-
val authentication = mock(Authentication::class.java)
164-
`when`(authentication.isAuthenticated).thenReturn(true)
165-
`when`(authentication.authorities).thenReturn(emptyList())
166-
167-
val expressionRoot = SecurityContextCapabilityService(authentication)
168-
169-
// when & then
170-
assertThat(expressionRoot.hasToken()).isTrue()
171-
assertThat(expressionRoot.hasCapability("user")).isFalse()
113+
// when & then
114+
assertThat(expressionRoot.hasToken()).isFalse()
115+
}
172116
}
173117

174-
@Test
175-
fun `combined test - hasToken true but specific capability false`() = runTest {
176-
// given
177-
val authentication = mock(Authentication::class.java)
178-
`when`(authentication.isAuthenticated).thenReturn(true)
179-
val authorities = listOf(
180-
LazyGrantedAuthority("user", """{"id":"test-user-id"}""")
181-
)
182-
`when`(authentication.authorities).thenReturn(authorities)
183-
184-
val expressionRoot = SecurityContextCapabilityService(authentication)
185-
186-
// when & then
187-
assertThat(expressionRoot.hasToken()).isTrue()
188-
assertThat(expressionRoot.hasCapability("user")).isTrue()
189-
assertThat(expressionRoot.hasCapability("agent")).isFalse()
190-
}
191-
192-
@Test
193-
fun `hasCapability with empty string returns false`() = runTest {
194-
// given
195-
val authentication = mock(Authentication::class.java)
196-
val authorities = listOf(
197-
LazyGrantedAuthority("user", """{"id":"test-user-id"}""")
198-
)
199-
`when`(authentication.authorities).thenReturn(authorities)
200-
201-
val expressionRoot = SecurityContextCapabilityService(authentication)
202-
203-
// when & then
204-
assertThat(expressionRoot.hasCapability("")).isFalse()
205-
}
206-
207-
@Test
208-
fun `hasCapability with special characters in capability ID`() = runTest {
209-
// given
210-
val authentication = mock(Authentication::class.java)
211-
val authorities = listOf(
212-
LazyGrantedAuthority("special-capability-123", """{"id":"test"}""")
213-
)
214-
`when`(authentication.authorities).thenReturn(authorities)
215-
216-
val expressionRoot = SecurityContextCapabilityService(authentication)
217-
218-
// when & then
219-
assertThat(expressionRoot.hasCapability("special-capability-123")).isTrue()
220-
assertThat(expressionRoot.hasCapability("special-capability-456")).isFalse()
221-
}
222-
223-
@Test
224-
fun `real world scenario - user capability check`() = runTest {
225-
// given - simulating a JWT token with user capability
226-
val authentication = mock(Authentication::class.java)
227-
`when`(authentication.isAuthenticated).thenReturn(true)
228-
val authorities = listOf(
229-
LazyGrantedAuthority("user", """{"id":"550e8400-e29b-41d4-a716-446655440000"}"""),
230-
LazyGrantedAuthority("groups", """[]""")
231-
)
232-
`when`(authentication.authorities).thenReturn(authorities)
233-
234-
val expressionRoot = SecurityContextCapabilityService(authentication)
235-
236-
// when & then - user can access user-protected endpoints
237-
assertThat(expressionRoot.hasToken()).isTrue()
238-
assertThat(expressionRoot.hasCapability("user")).isTrue()
239-
assertThat(expressionRoot.hasCapability("agent")).isFalse()
240-
}
241-
242-
@Test
243-
fun `real world scenario - agent capability check`() = runTest {
244-
// given - simulating an agent/service token
245-
val authentication = mock(Authentication::class.java)
246-
`when`(authentication.isAuthenticated).thenReturn(true)
247-
val authorities = listOf(
248-
LazyGrantedAuthority("agent", """{"dummy":"service-123"}""")
249-
)
250-
`when`(authentication.authorities).thenReturn(authorities)
251-
252-
val expressionRoot = SecurityContextCapabilityService(authentication)
253-
254-
// when & then - agent can only access agent-protected endpoints
255-
assertThat(expressionRoot.hasToken()).isTrue()
256-
assertThat(expressionRoot.hasCapability("agent")).isTrue()
257-
assertThat(expressionRoot.hasCapability("user")).isFalse()
258-
}
259-
260-
@Test
261-
fun `real world scenario - anonymous token-only access`() = runTest {
262-
// given - simulating a token without specific capabilities (anonymous)
263-
val authentication = mock(Authentication::class.java)
264-
`when`(authentication.isAuthenticated).thenReturn(true)
265-
`when`(authentication.authorities).thenReturn(emptyList())
266-
267-
val expressionRoot = SecurityContextCapabilityService(authentication)
118+
private fun mockAuthentication(authentication: Authentication, block: () -> Any) {
119+
Mockito.mockStatic(SecurityContextHolder::class.java).use { staticMock ->
120+
{
121+
val securityContext = mock(SecurityContext::class.java)
122+
`when`(securityContext.authentication).thenReturn(authentication)
123+
`when`(SecurityContextHolder.getContext()).thenReturn(securityContext)
268124

269-
// when & then - has token but no specific capabilities
270-
assertThat(expressionRoot.hasToken()).isTrue()
271-
assertThat(expressionRoot.hasCapability("user")).isFalse()
272-
assertThat(expressionRoot.hasCapability("agent")).isFalse()
125+
block.invoke()
126+
}
127+
}
273128
}
274129
}
275130

0 commit comments

Comments
 (0)