Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,11 @@ export interface CreateUserParams extends BaseParams {
password: string
}

export interface UpdateUserParams extends BaseParams {
login: string
password: string
}

export interface DeactivateUserParams extends BaseParams {
login: string
anonymize: boolean
Expand Down Expand Up @@ -286,6 +291,11 @@ export class SonarqubeClientService {
await this.http.fetch('users/create', { method: 'POST', query: params })
}

@StartActiveSpan()
async updateUser(params: UpdateUserParams) {
await this.http.fetch('users/update', { method: 'POST', query: params })
}

@StartActiveSpan()
async deactivateUser(params: DeactivateUserParams) {
await this.http.fetch('users/deactivate', { method: 'POST', query: params })
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ describe('sonarqubeService', () => {
addPermissionUser: vi.fn().mockResolvedValue(undefined),
searchUsers: vi.fn().mockImplementation(async function* () { yield* makeEmptyUsersResponse().users }),
createUser: vi.fn().mockResolvedValue(undefined),
updateUser: vi.fn().mockResolvedValue(undefined),
deactivateUser: vi.fn().mockResolvedValue(undefined),
revokeUserToken: vi.fn().mockResolvedValue(undefined),
searchProject: vi.fn().mockImplementation(async function* () { yield* makeEmptyProjectsResponse().components }),
Expand Down Expand Up @@ -178,17 +179,17 @@ describe('sonarqubeService', () => {
expect(vault.writeSonarqubeUser).not.toHaveBeenCalled()
})

it('should rotate token when user exists but vault secret is missing', async () => {
it('should regenerate password and rotate token when user exists but vault secret is missing', async () => {
const project = makeProjectWithDetails({ repositories: [] })
client.generateUserToken.mockResolvedValue(makeUserToken({ login: project.slug }))
client.searchUsers.mockImplementation(async function* () { yield makeSonarqubeUser({ login: project.slug }) })

await service.handleUpsert(project)

expect(client.createUser).not.toHaveBeenCalled()
expect(client.updateUser).toHaveBeenCalledWith(expect.objectContaining({ login: project.slug, password: expect.any(String) }))
expect(client.generateUserToken).toHaveBeenCalledWith(expect.objectContaining({ login: project.slug }))
expect(vault.writeSonarqubeUser).toHaveBeenCalledWith(project.slug, expect.not.objectContaining({ SONAR_PASSWORD: expect.anything() }))
expect(vault.writeSonarqubeUser).toHaveBeenCalledWith(project.slug, expect.objectContaining({ SONAR_USERNAME: project.slug, SONAR_TOKEN: expect.any(String) }))
expect(vault.writeSonarqubeUser).toHaveBeenCalledWith(project.slug, expect.objectContaining({ SONAR_USERNAME: project.slug, SONAR_PASSWORD: expect.any(String), SONAR_TOKEN: expect.any(String) }))
})

it('should delete sonarqube projects for removed repositories', async () => {
Expand Down
6 changes: 4 additions & 2 deletions apps/server-nestjs/src/modules/sonarqube/sonarqube.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -212,9 +212,11 @@ export class SonarqubeService implements OnModuleInit {
} else if (existingSecret) {
this.logger.verbose(`SonarQube user already exists with vault credentials (login=${project.slug})`)
} else {
this.logger.warn(`SonarQube user exists but vault secret is missing, rotating token (login=${project.slug})`)
this.logger.warn(`SonarQube user exists but vault secret is missing, regenerating password and rotating token (login=${project.slug})`)
const password = generateRandomPassword(30)
await this.client.updateUser({ login: project.slug, password })
const token = await this.rotateToken(project.slug)
newSecret = { SONAR_USERNAME: project.slug, SONAR_TOKEN: token }
newSecret = { SONAR_USERNAME: project.slug, SONAR_PASSWORD: password, SONAR_TOKEN: token }
}

if (newSecret) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ export interface VaultIdentityGroupResponse {

export interface SonarqubeUserSecret {
SONAR_USERNAME: string
SONAR_PASSWORD?: string
SONAR_PASSWORD: string
Comment thread
shikanime marked this conversation as resolved.
SONAR_TOKEN: string
}

Expand Down
Loading