Problem with DB container in TestContainers using SpringBootTest - spring

I have an abstract class BaseIntegrationTest that use TestContainers. The problem is when I'm trying to run a simple DB test like UserRepositoryIntSpec I have an exception, which means that count starts from 114, but not from 1 as expected. Why index not starts from 1? Why every time setup is executed my local db user table is clear, since I expect test to be runned in container with container db usage, so only container table will be cleared.
It's definetly should be something easy I just missed or didn't understand. I will be appreciate for help.
For migrations I'm using Flyway, for testing Spock.
Condition not satisfied:
user1.getId() == 1 && user1.getRiskcustomerid() == 1 && user1.getDateCreated() != null
| | | | |
| 114 | false false
| false
BaseIntegrationTest
#ContextConfiguration
#SpringBootTest(webEnvironment = DEFINED_PORT)
#Testcontainers
#Slf4j
abstract class BaseIntegrationTest extends Specification {
protected static PostgreSQLContainer postgres = new PostgreSQLContainer()
.withDatabaseName("db")
.withUsername("root")
.withPassword("root")
def setupSpec() {
startPostgresIfNeeded()
['spring.datasource.url' : postgres.getJdbcUrl(),
'spring.datasource.username': postgres.getUsername(),
'spring.datasource.password': postgres.getPassword()
].each { k, v ->
System.setProperty(k, v)
}
}
private static void startPostgresIfNeeded() {
if (!postgres.isRunning()) {
log.info("[BASE-INTEGRATION-TEST] - Postgres is not started. Running...")
postgres.start()
}
}
def cleanupSpec() {
if (postgres.isRunning()) {
log.info("[BASE-INTEGRATION-TEST] - Stopping Postgres...")
postgres.stop()
}
}
}
UserRepositoryIntSpec
class UserRepositoryIntSpec extends BaseIntegrationTest {
#Autowired
private UserRepository UserRepository
def setup() {
UserRepository.deleteAll()
}
def "FindAll returns all users correctly"() {
given:
List<Integer> friends = [1,2]
User User1 = User.builder()
.riskcustomerid(1)
.possibleids([1000, 1001])
.preferableid(1000)
.totalfriendscount(2)
.friends(friends)
.build()
User User2 = User.builder()
.riskcustomerid(2)
.possibleids([8000, 8001])
.preferableid(8000)
.totalfriendscount(3)
.friends(friends)
.build()
when:
UserRepository.saveAll([User1, User2])
then:
List<User> Users = UserRepository.findAll()
assert Users.size() == 2
User user1 = Users.get(0)
User user2 = Users.get(1)
assert user1.getId() == 1 && user1.getRiskcustomerid() == 1 && user1.getDateCreated() != null
assert user2.getId() == 2 && user2.getRiskcustomerid() == 2 && user2.getDateCreated() != null
}
Application.yml
spring:
datasource:
url: jdbc:postgresql://localhost:5432/db
username: root
password: root
hikari:
connection-timeout: 10000
leak-detection-threshold: 60000
validation-timeout: 30000
connection-test-query: SELECT 1;
jdbc-url: jdbc:postgresql://localhost:5432/db
username: root
password: root
data-source-properties: stringtype=unspecified
maximum-pool-size: 16
max-lifetime: 1800000
transaction-isolation: TRANSACTION_READ_COMMITTED
pool-name: hikari.local
driver-class-name: org.postgresql.Driver
jpa:
hibernate:
ddl-auto: update
flyway:
schemas: schema1
baseline-on-migrate: false
server:
port: 8080

I see that you're using url: jdbc:postgresql://localhost:5432/db. You're literally saying "please run against my local DB" :)
For your use case, I suggest using JDBC-based containers in Testcontainers. It will start the container automatically and destroy it when you close your last connection to it.

Related

How to define a default filter for all routes but disable it for a specific route?

When using Spring Cloud Gateway (v3.1.3), how would one go about defining a default filter to perform retries for all routes, but then disable it for individual routes? I would like something as intuitive as this:
spring:
cloud:
gateway:
default-filters:
- name: Retry
args:
retries: 3
statuses: BAD_GATEWAY
methods: GET,POST,PUT,DELETE
backoff:
firstBackoff: 10ms
maxBackoff: 50ms
factor: 2
basedOnPreviousValue: false
routes:
- id: retry_disabled
uri: http://localhost:8080/retry_disabled
filters:
- name: Retry
args:
retries: 0
- id: retry_enabled
uri: http://localhost:8080/retry_enabled
I see in the RetryGatewayFilterFactory class that the RetryConfig.validate() method will fail when the number of retries is less than 1 or the other config options are not defined properly:
public void validate() {
Assert.isTrue(this.retries > 0, "retries must be greater than 0");
Assert.isTrue(!this.series.isEmpty() || !this.statuses.isEmpty() || !this.exceptions.isEmpty(),
"series, status and exceptions may not all be empty");
Assert.notEmpty(this.methods, "methods may not be empty");
if (this.backoff != null) {
this.backoff.validate();
}
}
Edit: I'm considering to implement it like this in code:
#Bean
public Function<GatewayFilterSpec, UriSpec> defaultRetryGatewayFilter() {
return gatewayFilterSpec -> gatewayFilterSpec
.retry(retryConfig -> {
RetryGatewayFilterFactory.BackoffConfig backoffConfig = new RetryGatewayFilterFactory.BackoffConfig();
backoffConfig.setFirstBackoff(Duration.ofMillis(10));
backoffConfig.setMaxBackoff(Duration.ofMillis(50));
backoffConfig.setFactor(2);
backoffConfig.setBasedOnPreviousValue(false);
retryConfig
.setRetries(3)
.allMethods()
.setSeries(HttpStatus.Series.SERVER_ERROR)
.setStatuses(HttpStatus.BAD_GATEWAY)
.setBackoff(backoffConfig);
});
}
#Bean
public RouteLocator routes(RouteLocatorBuilder builder, Function<GatewayFilterSpec, UriSpec> defaultRetryGatewayFilter) {
return builder.routes()
.route("retry_enabled", r -> r
.path("/retry_enabled")
.filters(defaultRetryGatewayFilter)
.uri("lb://foo"))
.route("retry_disabled", r -> r
.path("/retry_disabled")
// not retryable
.uri("lb://foo"))
.build();
}
Will the singleton defaultRetryGatewayFilter be thread safe?

Secured users created in grails integration test are unauthorized but bootstrapped ones are

I'm using Grails Spring Security Core and the Grails Spring Security REST plugin and I'm just starting to get things set up. I initialized the plugins with a User class and an Authority class (defaults) and went to write an integration test, following a guide I found on the Grails website.
It said to put the following in an integration test:
def "test a user with the role ROLE_BOSS is able to access /api/announcements url"() {
when: 'login with the sherlock'
RestBuilder rest = new RestBuilder()
def resp = rest.post("http://localhost:${serverPort}/api/login") {
accept('application/json')
contentType('application/json')
json {
username = 'sherlock'
password = 'elementary'
}
}
then:
resp.status == 200
resp.json.roles.find { it == 'ROLE_BOSS' }
}
I went ahead and did something similar and it worked with a bootstrapped User, but when I tried to do the exact same test with a User created in the test method itself, it would fail with a 401 HTTP response code.
The code I'm trying to run:
void "check get access token"() {
given:
RestBuilder rest = new RestBuilder()
new User(username: "securitySpecTestUserName", password: "securitySpecTestPassword").save(flush: true)
assert User.count == 2
when:
def resp = rest.post("http://localhost:${serverPort}/api/login") {
accept('application/json')
contentType('application/json')
json {
username = "securitySpecTestUserName"
password = "securitySpecTestPassword"
}
}
then:
resp.status == 200
}
Note that the User.count == 2 assertion passes because there is one User in Bootstrap.groovy and the one create in the test method.
Why does this work and pass with the bootstrapped User without any issues at all but not the one created in the method? Is there a way I can write this integration test so that I can test the /api/login endpoint included in the grails-spring-security-rest plugin in this way?
The User you create in the given section is in a transaction that has not been committed. When you make the REST call, the api/login controller will be run in a new transaction that cannot see your un-committed User.
A few options (there are others)...
Create User in BootStrap.groovy
def init = { servletContext ->
environments {
test {
new User(username: "securitySpecTestUserName", password: "securitySpecTestPassword").save(flush: true)
}
}
}
Make REST calls to create the User - assuming you have such functionality
Create User in setup
#Integration
#Rollback
class UserIntSpec extends Specification {
def setup() {
new User(username: "securitySpecTestUserName", password: "securitySpecTestPassword").save(flush: true)
}
void "check get access token"() {
given:
RestBuilder rest = new RestBuilder()
when:
def response = rest.post("http://localhost:${serverPort}/api/login") {
accept('application/json')
contentType('application/json')
json {
username = "securitySpecTestUserName"
password = "securitySpecTestPassword"
}
}
then:
response.status == HttpServletResponse.SC_OK
when:
def token = response.json.access_token
then:
token
}
}
Note: In Grails >= 3.0, setup() is run in a separate transaction and persisted (why it solves your problem) which is not rolled back. Any data will need to be cleaned up manually.
I suggest you read the grails documentation on testing: Integration Testing

grails spring security CAS: cannot get login to work with non-local user

My grails 2.5.0 app uses Spring Security with the ldap and cas plugins. I trust this CAS server, so I need to allow users with no local (User, Role, UserRole tables) records to authenticate.
If I have a local user, everything works great. If I don't have a local user, then I get the CAS re-direct error followed by "Sorry, we were not able to find a user with that username and password." I read that the GormUserDetailsService supplied with CAS doesn't work for non-local users, so I've written my own MyUserDetailsService that implements GrailsUserDetailsService, and I've registered it in ./conf/spring/resources.groovy:
beans = {
userDetailsService(edu.uga.reg.MyUserDetailsService)
}
Unfortunately, this doesn't solve the problem: it works with local users, but gives the same re-direct+user not found error if the user isn't in my local database. Here's MyUserDetailService code:
import grails.plugin.springsecurity.SpringSecurityUtils
import grails.plugin.springsecurity.userdetails.GrailsUser
import grails.plugin.springsecurity.userdetails.GrailsUserDetailsService
import grails.transaction.Transactional
import org.springframework.security.core.authority.GrantedAuthorityImpl
import edu.uga.reg.User
import org.springframework.security.core.userdetails.UserDetails
import org.springframework.security.core.userdetails.UsernameNotFoundException
class MyUserDetailsService implements GrailsUserDetailsService {
/**
* Some Spring Security classes (e.g. RoleHierarchyVoter) expect at least
* one role, so we give a user with no granted roles this one which gets
* past that restriction but doesn't grant anything.
*/
static final List NO_ROLES =
[new GrantedAuthorityImpl(SpringSecurityUtils.NO_ROLE)]
UserDetails loadUserByUsername(String username, boolean loadRoles)
throws UsernameNotFoundException {
return loadUserByUsername(username)
}
UserDetails loadUserByUsername(String username) {
def user = User.findByUsername(username)
// No local user in my db: create one from this username
if (!user)
return new GrailsUser(username, '', true, true, true, NO_ROLES, 999)
def authorities = user.authorities.collect {
new GrantedAuthorityImpl(it.authority)
}
return new GrailsUser(user.username, user.password,
user.enabled, !user.accountExpired, !user.passwordExpired,
!user.accountLocked, authorities ?: NO_ROLES, user.id)
}
}
Here are my cas settings in Config.groovy:
grails.plugin.springsecurity.providerNames = ['casAuthenticationProvider']
grails.plugin.springsecurity.useCAS = true
grails.plugin.springsecurity.cas.active = true
grails.plugin.springsecurity.cas.loginUri = '/login'
grails.plugin.springsecurity.cas.serverUrlPrefix = 'https://cas.dev.server/cas'
grails.plugin.springsecurity.cas.serviceUrl = 'https://apps-dev.server:8743/CASIS/j_spring_cas_security_check'
grails.plugin.springsecurity.logout.afterLogoutUrl = 'https://cas.dev.server/cas/logout?url=http://apps-dev.server:8743/CASIS/'
//default cas settings
grails.plugin.springsecurity.cas.sendRenew = false
grails.plugin.springsecurity.cas.key = 'grails-spring-security-cas'
grails.plugin.springsecurity.cas.artifactParameter = 'ticket'
grails.plugin.springsecurity.cas.serviceParameter = 'service'
grails.plugin.springsecurity.cas.filterProcessesUrl = '/j_spring_cas_security_check'
grails.plugin.springsecurity.cas.useSingleSignout = true
grails.plugin.springsecurity.cas.serverUrlEncoding = 'UTF-8'
grails.plugin.springsecurity.cas.proxyCallbackUrl = null
grails.plugin.springsecurity.cas.proxyReceptorUrl = null
Again, CAS works fine if the user is in my local db, but fails otherwise. What am I missing? Thanks.
The answer to my problem is trivial. It's always a good idea to pass the correct number of arguments to the constructor. Instead of:
return new GrailsUser(username, '', true, true, true, NO_ROLES, 999)
I simply needed:
return new GrailsUser(username, '', true, true, true, true, NO_ROLES, 999)
And my user is setup as the principal!

spring security core assertion error in grails [duplicate]

I am using grails spring seurity core and very new to this. i am getting following error
Configuring Spring Security Core 1.2.7.3...
... finished configuring Spring Security Core
**| Error 2014-01-10 09:40:36,688 [localhost-startStop-1] ERROR context.GrailsContextLoader - Error initializing the application: Assertion failed:
assert SecUserSecRole.count() == 1
| |
0 false
Message: Assertion failed:
assert SecUserSecRole.count() == 1
| |
0 false**
Line | Method
The BootStrap class is as follows
class **BootStrap** {
def springSecurityService
def init = { servletContext ->
//def userRole= SecRole.findByAuthority("ROLE_USER") ?: new SecRole(authority : "ROLE_USER").save()
//def adminRole= SecRole.findByAuthority("ROLE_ADMIN") ?: new SecRole(authority : "ROLE_ADMIN").save()
def adminRole = new SecRole(authority: 'ROLE_ADMIN').save(flush: true)
def userRole = new SecRole(authority: 'ROLE_USER').save(flush: true)
// def testUser = new SecUser(username: username, enabled: true, password: springSecurityService.encodePassword("password"))
/// testUser.save(flush: true)
//SecUserSecRole.create testUser, adminRole, true
def testUser = new SecUser(username: 'admin', enabled: true, password: 'admin')
testUser.save(flush: true)
SecUserSecRole.create testUser, adminRole, true
assert SecUser.count() == 1
assert SecRole.count() == 2
assert SecUserSecRole.count() == 1
}
def *destroy* = {
}
}

Sorry, we were not able to find a user with that username and password

I installed the Spring Security core plug-in 1.2.7.3 on Grails 2.1.1, ran the s2-quickstart command, and then initialized the initial user and roles in the bootstrap.groovy, but I still cannot login. Text of the relevant piece of BootStrap.groovy follows:
if (SecRole.count == 0) {
def fUserRole = SecRole.findByAuthority('ROLE_FlowUser') ?: new SecRole(authority: 'ROLE_FlowUser').save(failOnError: true, flush: true)
def fAdminRole = SecRole.findByAuthority('ROLE_FlowAdmin') ?: new SecRole(authority: 'ROLE_FlowAdmin').save(failOnError: true, flush: true)
def bf = SecUser.findByUsername('bill') ?: new SecUser(
username: 'bill',
password: 'eagle',
firstName: 'bill',
lastName: 'fly',
email: 'bill.fly#baylorhealth.edu',
accountExpired: false,
accountLocked: false,
passwordExpired: false,
enabled: true
).save(failOnError: true, flush: true)
if (!bf.authorities.contains(fAdminRole)) {
SecUserSecRole.create bf, fAdminRole, true
}
if (!bf.authorities.contains(fUserRole)) {
SecUserSecRole.create bf, fUserRole, true
}
}
I am not encrypting the password in bootstrap, as seems to be the answer to most of the questions of this type. All four records are getting written to the database tables, but of course, I cannot tell if the password is encrypted correctly. My initial controller has the following annotation ahead of the class statement:
#Secured(['IS_AUTHENTICATED_FULLY'])
Also, I added the following to the config.groovy:
// Added by the Spring Security Core plugin:
grails.plugins.springsecurity.userLookup.userDomainClassName = 'cocktail.SecUser'
grails.plugins.springsecurity.userLookup.authorityJoinClassName = 'cocktail.SecUserSecRole'
grails.plugins.springsecurity.authority.className = 'cocktail.SecRole'
grails.plugins.springsecurity.password.algorithm = 'SHA-256'
Your password may be encoded two times (problem may occure if you are using multi datasources).
Try this :
class User {
...
transient bEncoded = false
...
protected void encodePassword() {
if (!bEncoded ) {
password = springSecurityService.encodePassword(password);
bEncoded = true;
}
}
}
My guess is the authorities.contains check is failing because of missing hashCode and equals methods in your role class. But if there are no roles (your 1st check) then the user wouldn't have any granted, so you can just remove those checks:
SecUserSecRole.create bf, fAdminRole, true
SecUserSecRole.create bf, fUserRole, true
If that doesn't fix it, it's most likely a password encoding issue - add debug logging for Spring Security and it should show you why it's failing; add debug 'org.springframework.security' in your log4j block in Config.groovy
p.s. if (SecRole.count == 0) { should be if (SecRole.count() == 0) { or just if (!SecRole.count()) {

Resources