Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/main' into 96-implement-deleting…
Browse files Browse the repository at this point in the history
…-participant-organizations-for-authority-admins
  • Loading branch information
PaddseL committed Jan 9, 2025
2 parents ddc368b + f68cb2a commit d6b7f08
Show file tree
Hide file tree
Showing 11 changed files with 187 additions and 53 deletions.
2 changes: 0 additions & 2 deletions .github/ISSUE_TEMPLATE/documentation.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ labels: "task/documentation"
assignees: ""
---

# Documentation Update Request

## Description
<!-- Provide a brief overview of the documentation update request. What section or topic needs to be updated? -->

Expand Down
15 changes: 3 additions & 12 deletions .github/ISSUE_TEMPLATE/epic_template.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,10 @@ labels: "kind/epic"
assignees: ""
---

# Epic

## Description
<!-- Brief summary of what this Epic is, whether it's a larger project, goal, or user story. Describe the job to be done, which persona this Epic is mainly for, or if more multiple, break it down by user and job story. -->

### Requirements
## Requirements
<!-- Which requirements do you have to be fulfilled? -->
<!-- Which security-related requirements must be satisfied? -->

Expand All @@ -23,17 +21,10 @@ assignees: ""
- [ ] Create Stories which can be converted into issues
```

### Security Constraints
<!-- Which constraints can be checked that must be covered by the work breakdown? -->
- [ ] Final solution design has been challenged for security related topics

## Initiative / goal
<!-- Describe how this Epic impacts an initiative the business is working on. -->

### Hypothesis
## Hypothesis
<!-- What is your hypothesis on the success of this Epic? Describe how success will be measured and what leading indicators the team will have to know if success has been hit. -->

## Acceptance criteria and must have scope
## Acceptance Criteria
<!-- Define what is a must-have for launch and in-scope (e.g. security-related tasks like successful pen-tests). Keep this section fluid and dynamic until you lock-in priority during planning. Please list your criteria below. -->

## Stakeholders
Expand Down
17 changes: 9 additions & 8 deletions .github/ISSUE_TEMPLATE/feature_request.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,25 @@ labels: "kind/enhancement"
assignees: ""
---

# Feature Request

## Description
<!-- A clear and concise description of what the user wants to happen. Example below. Replace brackets! -->
As a {user} who {preconditions}, I want to {do something}, so I can {goal}.

## Which Areas Would Be Affected?
<!-- e.g., DPF, CI, build, transfer, etc. -->
## Affected Areas
<!-- Specify areas that this feature will impact (e.g., CI, DPF, transfer, etc.) -->

## Why Is the Feature Desired?
<!-- Are there any requirements? -->
## Motivation
<!-- Explain why this feature is needed and any specific requirements. -->

## How does this tie into our current product?
<!-- Describe whether this request is related to an existing workflow, feature, or otherwise something in the product today. Or, does this open us up to new markets and innovative ideas? -->
## Related Initiatives or Features
<!-- Describe how this request ties into existing workflows, features, or initiatives. -->

## Stakeholders
<!-- Add more on who asked for this, i.e. company, person, how much they pay us, what their tier is, are they a strategic account, etc. Who needs to be kept up-to-date about this feature? -->

## Acceptance Criteria
<!-- Define what is a must-have for launch and in-scope (e.g. security-related tasks like successful pen-tests). Keep this section fluid and dynamic until you lock-in priority during planning. Please list your criteria below. -->

## Solution Proposal and Work Breakdown
<!-- If you already know what needs to be done, please add a tasklist. -->

Expand Down
2 changes: 0 additions & 2 deletions .github/ISSUE_TEMPLATE/process.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ labels: ["task/refine-process","task/documentation"]
assignees: ""
---

# Process Refinement Request

## Description
<!-- Provide a brief description of the process that needs refinement and the reason behind it. -->

Expand Down
4 changes: 0 additions & 4 deletions .github/workflows/add_issue_to_project.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,3 @@ jobs:
with:
project-url: https://github.com/orgs/sovity/projects/9
github-token: ${{ secrets.ADD_ISSUE_TO_PROJECT_PAT }}
- uses: actions/[email protected]
with:
project-url: https://github.com/orgs/sovity/projects/25
github-token: ${{ secrets.ADD_ISSUE_TO_PROJECT_PAT }}
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ please see [changelog_updates.md](docs/dev/changelog_updates.md).

#### Patch

- Fixed Keycloak dev realm for local E2E development
- Fixed Keycloak dev realm for local E2E development ([PR #405](https://github.com/sovity/authority-portal/pull/405))
- Fixed Operator Admins being unable to delete connectors ([PR #408](https://github.com/sovity/authority-portal/pull/408))

### Known issues

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -312,9 +312,15 @@ class UiResourceImpl(

@Transactional
override fun deleteProvidedConnector(connectorId: String): IdResponse {
authUtils.requiresRole(Roles.UserRoles.SERVICE_PARTNER_ADMIN)
authUtils.requiresMemberOfAnyOrganization()
return connectorManagementApiService.deleteOwnOrProvidedConnector(
authUtils.requiresAnyRole(Roles.UserRoles.SERVICE_PARTNER_ADMIN, Roles.UserRoles.OPERATOR_ADMIN)

if (!authUtils.hasRole(Roles.UserRoles.OPERATOR_ADMIN)) {
authUtils.requiresMemberOfProviderOrganization(connectorId)
} else {
authUtils.requiresMemberOfAnyOrganization()
}

return connectorManagementApiService.deleteConnectorById(
connectorId,
loggedInUser.organizationId!!,
loggedInUser.userId
Expand Down Expand Up @@ -373,8 +379,8 @@ class UiResourceImpl(
@Transactional
override fun deleteOwnConnector(connectorId: String): IdResponse {
authUtils.requiresRole(Roles.UserRoles.PARTICIPANT_CURATOR)
authUtils.requiresMemberOfAnyOrganization()
return connectorManagementApiService.deleteOwnOrProvidedConnector(
authUtils.requiresMemberOfOwningOrganization(connectorId)
return connectorManagementApiService.deleteConnectorById(
connectorId,
loggedInUser.organizationId!!,
loggedInUser.userId
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ package de.sovity.authorityportal.web.auth

import de.sovity.authorityportal.db.jooq.enums.OrganizationRegistrationStatus
import de.sovity.authorityportal.db.jooq.enums.UserRegistrationStatus
import de.sovity.authorityportal.web.services.ConnectorService
import de.sovity.authorityportal.web.services.OrganizationService
import de.sovity.authorityportal.web.services.UserService
import de.sovity.authorityportal.web.utils.unauthorized
Expand All @@ -25,9 +26,9 @@ import jakarta.enterprise.context.ApplicationScoped
class AuthUtils(
val loggedInUser: LoggedInUser,
val userService: UserService,
val organizationService: OrganizationService
val organizationService: OrganizationService,
val connectorService: ConnectorService
) {

fun requiresAuthenticated() {
if (!loggedInUser.authenticated) {
Log.error("User is not authenticated.")
Expand Down Expand Up @@ -144,7 +145,6 @@ class AuthUtils(
}

fun requiresOrganizationRegistrationStatus(status: OrganizationRegistrationStatus) {
requiresAuthenticated()
requiresMemberOfAnyOrganization()

val organization = organizationService.getOrganizationOrThrow(loggedInUser.organizationId!!)
Expand All @@ -153,4 +153,24 @@ class AuthUtils(
unauthorized("User can only perform the action if their organization is in the onboarding phase")
}
}

fun requiresMemberOfProviderOrganization(connectorId: String) {
requiresMemberOfAnyOrganization()

val connector = connectorService.getConnectorOrThrow(connectorId)
if (connector.providerOrganizationId != loggedInUser.organizationId!!) {
Log.error("User is not a member of the provider organization. userId=${loggedInUser.userId}, providerOrganizationId=${connector.providerOrganizationId}")
unauthorized("User is not a member of the provider organization")
}
}

fun requiresMemberOfOwningOrganization(connectorId: String) {
requiresMemberOfAnyOrganization()

val connector = connectorService.getConnectorOrThrow(connectorId)
if (!connectorId.startsWith(loggedInUser.organizationId!!)) {
Log.error("User is not a member of the owning organization. userId=${loggedInUser.userId}, organizationId=${connector.organizationId}")
unauthorized("User is not a member of the owning organization")
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -335,26 +335,21 @@ class ConnectorManagementApiService(
return url.trim().removeSuffix("/")
}

fun deleteOwnOrProvidedConnector(
fun deleteConnectorById(
connectorId: String,
organizationId: String,
userId: String
): IdResponse {
val connector = connectorService.getConnectorOrThrow(connectorId)

if (!connectorId.startsWith(organizationId) && connector.providerOrganizationId != organizationId) {
Log.error("To be deleted connector does not belong to user's organization and is not hosted by it. connectorId=$connectorId, organizationId=$organizationId, userId=$userId.")
error("Connector ID does not match the ID of the user's organization or host organization")
}

deleteConnector(connector)
Log.info("Connector unregistered. connectorId=$connectorId, organizationId=$organizationId, userId=$userId.")

return IdResponse(connectorId, timeUtils.now())
}

fun deleteAllOrganizationConnectors(organizationid: String) {
val connectors = connectorService.getConnectorsByOrganizationId(organizationid)
fun deleteAllOrganizationConnectors(organizationId: String) {
val connectors = connectorService.getConnectorsByOrganizationId(organizationId)
connectors.forEach { deleteConnector(it) }
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -371,13 +371,6 @@ class ConnectorManagementApiServiceTest {
assertThat(result).isNotNull
assertThat(result.id).isEqualTo(connectorId)

fun count(table: TableLike<*>, condition: Condition): Int {
return dsl.selectCount()
.from(table)
.where(condition)
.fetchOne(0, Int::class.java) ?: 0
}

assertThat(
count(
Tables.CONNECTOR,
Expand Down Expand Up @@ -823,4 +816,139 @@ class ConnectorManagementApiServiceTest {
}
.isInstanceOf(NotAuthorizedException::class.java)
}

@Test
@TestTransaction
fun `delete own connector fails because of missing membership to owning organization`() {
// arrange
useDevUser(0, 0, setOf(Roles.UserRoles.PARTICIPANT_CURATOR))

ScenarioData().apply {
organization(0, 0)
organization(1, 1)
user(0, 0)
user(1, 1)
connector(0, 1, 1)

scenarioInstaller.install(this)
}

// act && assert
assertThatThrownBy { uiResource.deleteOwnConnector(dummyDevConnectorId(1, 0)) }
.isInstanceOf(NotAuthorizedException::class.java)
.hasMessageContaining("User is not a member of the owning organization")
}

@Test
@TestTransaction
fun `delete provided connector fails because of missing membership to provider organization`() {
// arrange
useDevUser(0, 0, setOf(Roles.UserRoles.SERVICE_PARTNER_ADMIN))

ScenarioData().apply {
organization(0, 0)
organization(1, 1)
organization(2, 2)
user(0, 0)
user(1, 1)
user(2, 2)
connector(0, 1, 2) {
it.providerOrganizationId = dummyDevOrganizationId(2)
}

scenarioInstaller.install(this)
}

// act && assert
assertThatThrownBy { uiResource.deleteProvidedConnector(dummyDevConnectorId(1, 0)) }
.isInstanceOf(NotAuthorizedException::class.java)
.hasMessageContaining("User is not a member of the provider organization")
}

@Test
@TestTransaction
fun `delete provided connector as service partner admin`() {
// arrange
useDevUser(0, 0, setOf(Roles.UserRoles.SERVICE_PARTNER_ADMIN))

val dapsClient = mock<DapsClient>()
whenever(dapsClientService.forEnvironment(any())).thenReturn(dapsClient)
doNothing().whenever(dapsClient).deleteClient(any())

ScenarioData().apply {
organization(0, 0)
organization(1, 1)
organization(2, 2)
user(0, 0)
user(1, 1)
user(2, 2)
connector(0, 1, 0) {
it.providerOrganizationId = dummyDevOrganizationId(0)
}

scenarioInstaller.install(this)
}
val connectorId = dummyDevConnectorId(1, 0)

// act
val result = uiResource.deleteProvidedConnector(connectorId)

// assert
assertThat(result).isNotNull
assertThat(result.id).isEqualTo(connectorId)

assertThat(
count(
Tables.CONNECTOR,
Tables.CONNECTOR.CONNECTOR_ID.eq(connectorId)
)
).isEqualTo(0)
}

@Test
@TestTransaction
fun `delete provided connector as operator admin`() {
// arrange
useDevUser(0, 0, setOf(Roles.UserRoles.OPERATOR_ADMIN))

val dapsClient = mock<DapsClient>()
whenever(dapsClientService.forEnvironment(any())).thenReturn(dapsClient)
doNothing().whenever(dapsClient).deleteClient(any())

ScenarioData().apply {
organization(0, 0)
organization(1, 1)
organization(2, 2)
user(0, 0)
user(1, 1)
user(2, 2)
connector(0, 1, 2) {
it.providerOrganizationId = dummyDevOrganizationId(2)
}

scenarioInstaller.install(this)
}
val connectorId = dummyDevConnectorId(1, 0)

// act
val result = uiResource.deleteProvidedConnector(connectorId)

// assert
assertThat(result).isNotNull
assertThat(result.id).isEqualTo(connectorId)

assertThat(
count(
Tables.CONNECTOR,
Tables.CONNECTOR.CONNECTOR_ID.eq(connectorId)
)
).isEqualTo(0)
}

private fun count(table: TableLike<*>, condition: Condition): Int {
return dsl.selectCount()
.from(table)
.where(condition)
.fetchOne(0, Int::class.java) ?: 0
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ export class AuthorityConnectorListPageStateImpl {
}
ctx.patchState({busy: true});

return this.apiService.deleteOwnConnector(action.connectorId).pipe(
return this.apiService.deleteProvidedConnector(action.connectorId).pipe(
switchMap(() => this.globalStateUtils.getDeploymentEnvironmentId()),
switchMap((deploymentEnvironmentId) =>
this.apiService.getAllConnectors(deploymentEnvironmentId),
Expand Down

0 comments on commit d6b7f08

Please sign in to comment.