mirror of
https://github.com/opf/openproject.git
synced 2026-06-14 03:30:14 +00:00
Merge remote-tracking branch 'origin/dev' into merge-release/17.5-20260605114354
This commit is contained in:
+18
-1
@@ -12,6 +12,7 @@ OpenProject offers different APIs:
|
||||
* SCIM (System for Cross-domain Identity Management)
|
||||
* MCP (Model Context Protocol)
|
||||
* BCF API v2.1 api targeted towards BIM use cases
|
||||
* .well-known endpoints
|
||||
|
||||
Please note that we intend to keep this specification as accurate and stable as possible, however work on APIs is still ongoing
|
||||
and not all resources and actions in OpenProject are yet accessible through the APIs.
|
||||
@@ -26,7 +27,7 @@ The API v3 is a general purpose API supporting multiple use cases.
|
||||
While by no means complete, a whole lot of different scenarios can be automatized which otherwise would have to be carried out by hand via the UI.
|
||||
Examples for this include managing work packages, projects and users.
|
||||
|
||||
We strive to maintain backward compatibility with this API in our stable OpenProject releases whenever possible.
|
||||
We strive to maintain backward compatibility with this API in our stable OpenProject releases whenever possible.
|
||||
|
||||
➔ [Go to OpenProject API](./introduction/)
|
||||
|
||||
@@ -53,3 +54,19 @@ This API supports BCF management in the context of BIM projects.
|
||||
While this API supports way less use cases than the more generic *API v3* it is compatible with the generic specification of a BCF API as [defined by the standard](https://github.com/buildingSMART/BCF-API/blob/release_2_1/README.md). Clients implementing the specification can manage topics and viewpoints.
|
||||
|
||||
➔ [Go to BCF API](./bcf-rest-api/)
|
||||
|
||||
## .well-known endpoints
|
||||
|
||||
Each OpenProject installation exposes some endpoints under the `/.well-known/` path:
|
||||
|
||||
* `/.well-known/oauth-authorization-server`: [RFC 8414](https://datatracker.ietf.org/doc/html/rfc8414): OAuth 2.0 Authorization Server Metadata
|
||||
* `/.well-known/oauth-protected-resource`: [RFC 9728](https://datatracker.ietf.org/doc/html/rfc9728): OAuth 2.0 Protected Resource Metadata
|
||||
* `/.well-known/openproject-metadata`: Exposing non-confidential metadata about the OpenProject installation
|
||||
|
||||
### OpenProject Metadata
|
||||
|
||||
The `/.well-known/openproject-metadata` endpoint exposes some non-confidential metadata about the OpenProject instance in JSON format. This endpoint is accessible without authentication.
|
||||
|
||||
The following keys are exposed:
|
||||
|
||||
* `installation_uuid`: A unique identifier that's different per installation of OpenProject
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
# Schema: MeetingOutcomeCollectionModel
|
||||
---
|
||||
type: object
|
||||
required:
|
||||
- _type
|
||||
- count
|
||||
- total
|
||||
- _embedded
|
||||
- _links
|
||||
properties:
|
||||
_type:
|
||||
type: string
|
||||
enum:
|
||||
- Collection
|
||||
count:
|
||||
type: integer
|
||||
total:
|
||||
type: integer
|
||||
_embedded:
|
||||
type: object
|
||||
required:
|
||||
- elements
|
||||
properties:
|
||||
elements:
|
||||
type: array
|
||||
items:
|
||||
$ref: './meeting_outcome_model.yml'
|
||||
_links:
|
||||
type: object
|
||||
required:
|
||||
- self
|
||||
properties:
|
||||
self:
|
||||
allOf:
|
||||
- $ref: './link.yml'
|
||||
- description: |-
|
||||
The link to this outcome collection resource
|
||||
|
||||
**Resource**: MeetingOutcomeCollection
|
||||
@@ -0,0 +1,69 @@
|
||||
# Schema: MeetingOutcomeModel
|
||||
---
|
||||
type: object
|
||||
required:
|
||||
- _type
|
||||
- id
|
||||
- kind
|
||||
- createdAt
|
||||
- updatedAt
|
||||
properties:
|
||||
_type:
|
||||
type: string
|
||||
enum:
|
||||
- MeetingOutcome
|
||||
id:
|
||||
type: integer
|
||||
description: Identifier of this outcome
|
||||
minimum: 1
|
||||
kind:
|
||||
type: string
|
||||
description: |-
|
||||
The type of this outcome. Possible values:
|
||||
|
||||
- *information*: an informational outcome with notes
|
||||
- *work_package*: an outcome linked to a work package
|
||||
enum:
|
||||
- information
|
||||
- work_package
|
||||
notes:
|
||||
$ref: "./formattable.yml"
|
||||
createdAt:
|
||||
type: string
|
||||
format: date-time
|
||||
description: Time of creation.
|
||||
updatedAt:
|
||||
type: string
|
||||
format: date-time
|
||||
description: Time of the most recent change.
|
||||
_links:
|
||||
type: object
|
||||
properties:
|
||||
self:
|
||||
allOf:
|
||||
- $ref: './link.yml'
|
||||
- description: |-
|
||||
This outcome
|
||||
|
||||
**Resource**: MeetingOutcome
|
||||
author:
|
||||
allOf:
|
||||
- $ref: './link.yml'
|
||||
- description: |-
|
||||
The user who created this outcome
|
||||
|
||||
**Resource**: User
|
||||
agendaItem:
|
||||
allOf:
|
||||
- $ref: './link.yml'
|
||||
- description: |-
|
||||
The agenda item this outcome belongs to
|
||||
|
||||
**Resource**: MeetingAgendaItem
|
||||
workPackage:
|
||||
allOf:
|
||||
- $ref: './link.yml'
|
||||
- description: |-
|
||||
The linked work package (for work_package type outcomes)
|
||||
|
||||
**Resource**: WorkPackage
|
||||
@@ -0,0 +1,26 @@
|
||||
# Schema: MeetingOutcomeWriteModel
|
||||
---
|
||||
type: object
|
||||
properties:
|
||||
kind:
|
||||
type: string
|
||||
description: |-
|
||||
The type of this outcome. Possible values:
|
||||
|
||||
- *information*: an informational outcome with notes
|
||||
- *work_package*: an outcome linked to a work package
|
||||
enum:
|
||||
- information
|
||||
- work_package
|
||||
notes:
|
||||
$ref: "./formattable.yml"
|
||||
_links:
|
||||
type: object
|
||||
properties:
|
||||
workPackage:
|
||||
allOf:
|
||||
- $ref: "./link.yml"
|
||||
- description: |-
|
||||
The linked work package (for work_package type outcomes)
|
||||
|
||||
**Resource**: WorkPackage
|
||||
@@ -275,6 +275,10 @@ paths:
|
||||
"$ref": "./paths/meeting_agenda_items.yml"
|
||||
"/api/v3/meetings/{meeting_id}/agenda_items/{id}":
|
||||
"$ref": "./paths/meeting_agenda_item.yml"
|
||||
"/api/v3/meetings/{meeting_id}/agenda_items/{agenda_item_id}/outcomes":
|
||||
"$ref": "./paths/meeting_agenda_item_outcomes.yml"
|
||||
"/api/v3/meetings/{meeting_id}/agenda_items/{agenda_item_id}/outcomes/{id}":
|
||||
"$ref": "./paths/meeting_agenda_item_outcome.yml"
|
||||
"/api/v3/meetings/{id}/attachments":
|
||||
"$ref": "./paths/meeting_attachments.yml"
|
||||
"/api/v3/meetings/{id}/form":
|
||||
@@ -892,6 +896,12 @@ components:
|
||||
"$ref": "./components/schemas/meeting_agenda_item_write_model.yml"
|
||||
MeetingAgendaItemCollectionModel:
|
||||
"$ref": "./components/schemas/meeting_agenda_item_collection_model.yml"
|
||||
MeetingOutcomeCollectionModel:
|
||||
"$ref": "./components/schemas/meeting_outcome_collection_model.yml"
|
||||
MeetingOutcomeModel:
|
||||
"$ref": "./components/schemas/meeting_outcome_model.yml"
|
||||
MeetingOutcomeWriteModel:
|
||||
"$ref": "./components/schemas/meeting_outcome_write_model.yml"
|
||||
MeetingCollectionModel:
|
||||
"$ref": "./components/schemas/meeting_collection_model.yml"
|
||||
MeetingModel:
|
||||
|
||||
@@ -0,0 +1,190 @@
|
||||
# /api/v3/meetings/{meeting_id}/agenda_items/{agenda_item_id}/outcomes/{id}
|
||||
---
|
||||
get:
|
||||
summary: Get a meeting outcome
|
||||
operationId: get_meeting_outcome
|
||||
tags:
|
||||
- Meetings
|
||||
description: Retrieve an individual outcome of a meeting agenda item.
|
||||
parameters:
|
||||
- description: Meeting identifier
|
||||
example: 1
|
||||
in: path
|
||||
name: meeting_id
|
||||
required: true
|
||||
schema:
|
||||
type: integer
|
||||
- description: Agenda item identifier
|
||||
example: 1
|
||||
in: path
|
||||
name: agenda_item_id
|
||||
required: true
|
||||
schema:
|
||||
type: integer
|
||||
- description: Outcome identifier
|
||||
example: 1
|
||||
in: path
|
||||
name: id
|
||||
required: true
|
||||
schema:
|
||||
type: integer
|
||||
responses:
|
||||
'200':
|
||||
description: OK
|
||||
content:
|
||||
application/hal+json:
|
||||
schema:
|
||||
$ref: "../components/schemas/meeting_outcome_model.yml"
|
||||
'404':
|
||||
content:
|
||||
application/hal+json:
|
||||
schema:
|
||||
$ref: "../components/schemas/error_response.yml"
|
||||
examples:
|
||||
response:
|
||||
value:
|
||||
_type: Error
|
||||
errorIdentifier: urn:openproject-org:api:v3:errors:NotFound
|
||||
message: The requested resource could not be found.
|
||||
description: |-
|
||||
Returned if the outcome, agenda item, or meeting does not exist or the client does not have sufficient permissions.
|
||||
|
||||
patch:
|
||||
summary: Update a meeting outcome
|
||||
operationId: update_meeting_outcome
|
||||
tags:
|
||||
- Meetings
|
||||
description: Updates the given meeting outcome.
|
||||
parameters:
|
||||
- description: Meeting identifier
|
||||
example: 1
|
||||
in: path
|
||||
name: meeting_id
|
||||
required: true
|
||||
schema:
|
||||
type: integer
|
||||
- description: Agenda item identifier
|
||||
example: 1
|
||||
in: path
|
||||
name: agenda_item_id
|
||||
required: true
|
||||
schema:
|
||||
type: integer
|
||||
- description: Outcome identifier
|
||||
example: 1
|
||||
in: path
|
||||
name: id
|
||||
required: true
|
||||
schema:
|
||||
type: integer
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "../components/schemas/meeting_outcome_write_model.yml"
|
||||
responses:
|
||||
'200':
|
||||
description: OK
|
||||
content:
|
||||
application/hal+json:
|
||||
schema:
|
||||
$ref: "../components/schemas/meeting_outcome_model.yml"
|
||||
'400':
|
||||
$ref: "../components/responses/invalid_request_body.yml"
|
||||
'403':
|
||||
content:
|
||||
application/hal+json:
|
||||
schema:
|
||||
$ref: "../components/schemas/error_response.yml"
|
||||
examples:
|
||||
response:
|
||||
value:
|
||||
_type: Error
|
||||
errorIdentifier: urn:openproject-org:api:v3:errors:MissingPermission
|
||||
message: You are not authorized to access this resource.
|
||||
description: |-
|
||||
Returned if the client does not have sufficient permissions.
|
||||
|
||||
**Required permission:** manage outcomes
|
||||
'404':
|
||||
content:
|
||||
application/hal+json:
|
||||
schema:
|
||||
$ref: "../components/schemas/error_response.yml"
|
||||
examples:
|
||||
response:
|
||||
value:
|
||||
_type: Error
|
||||
errorIdentifier: urn:openproject-org:api:v3:errors:NotFound
|
||||
message: The requested resource could not be found.
|
||||
description: |-
|
||||
Returned if the outcome, agenda item, or meeting does not exist.
|
||||
'406':
|
||||
$ref: "../components/responses/missing_content_type.yml"
|
||||
'415':
|
||||
$ref: "../components/responses/unsupported_media_type.yml"
|
||||
'422':
|
||||
description: |-
|
||||
Returned if:
|
||||
|
||||
* a constraint for a property was violated (`PropertyConstraintViolation`)
|
||||
|
||||
delete:
|
||||
summary: Delete a meeting outcome
|
||||
operationId: delete_meeting_outcome
|
||||
tags:
|
||||
- Meetings
|
||||
description: Deletes the outcome.
|
||||
parameters:
|
||||
- description: Meeting identifier
|
||||
example: 1
|
||||
in: path
|
||||
name: meeting_id
|
||||
required: true
|
||||
schema:
|
||||
type: integer
|
||||
- description: Agenda item identifier
|
||||
example: 1
|
||||
in: path
|
||||
name: agenda_item_id
|
||||
required: true
|
||||
schema:
|
||||
type: integer
|
||||
- description: Outcome identifier
|
||||
example: 1
|
||||
in: path
|
||||
name: id
|
||||
required: true
|
||||
schema:
|
||||
type: integer
|
||||
responses:
|
||||
'204':
|
||||
description: Returned if the outcome was successfully deleted
|
||||
'403':
|
||||
content:
|
||||
application/hal+json:
|
||||
schema:
|
||||
$ref: "../components/schemas/error_response.yml"
|
||||
examples:
|
||||
response:
|
||||
value:
|
||||
_type: Error
|
||||
errorIdentifier: urn:openproject-org:api:v3:errors:MissingPermission
|
||||
message: You are not authorized to access this resource.
|
||||
description: |-
|
||||
Returned if the client does not have sufficient permissions.
|
||||
|
||||
**Required permission:** manage outcomes
|
||||
'404':
|
||||
content:
|
||||
application/hal+json:
|
||||
schema:
|
||||
$ref: "../components/schemas/error_response.yml"
|
||||
examples:
|
||||
response:
|
||||
value:
|
||||
_type: Error
|
||||
errorIdentifier: urn:openproject-org:api:v3:errors:NotFound
|
||||
message: The requested resource could not be found.
|
||||
description: |-
|
||||
Returned if the outcome, agenda item, or meeting does not exist.
|
||||
@@ -0,0 +1,118 @@
|
||||
# /api/v3/meetings/{meeting_id}/agenda_items/{agenda_item_id}/outcomes
|
||||
---
|
||||
get:
|
||||
summary: List meeting outcomes
|
||||
operationId: list_meeting_outcomes
|
||||
tags:
|
||||
- Meetings
|
||||
description: Lists all outcomes for the given meeting agenda item.
|
||||
parameters:
|
||||
- description: Meeting identifier
|
||||
example: 1
|
||||
in: path
|
||||
name: meeting_id
|
||||
required: true
|
||||
schema:
|
||||
type: integer
|
||||
- description: Agenda item identifier
|
||||
example: 1
|
||||
in: path
|
||||
name: agenda_item_id
|
||||
required: true
|
||||
schema:
|
||||
type: integer
|
||||
responses:
|
||||
'200':
|
||||
description: OK
|
||||
content:
|
||||
application/hal+json:
|
||||
schema:
|
||||
$ref: "../components/schemas/meeting_outcome_collection_model.yml"
|
||||
'404':
|
||||
content:
|
||||
application/hal+json:
|
||||
schema:
|
||||
$ref: "../components/schemas/error_response.yml"
|
||||
examples:
|
||||
response:
|
||||
value:
|
||||
_type: Error
|
||||
errorIdentifier: urn:openproject-org:api:v3:errors:NotFound
|
||||
message: The requested resource could not be found.
|
||||
description: |-
|
||||
Returned if the agenda item or meeting does not exist or the client does not have sufficient permissions to see it.
|
||||
|
||||
**Required permission:** view meetings
|
||||
|
||||
post:
|
||||
summary: Create meeting outcome
|
||||
operationId: create_meeting_outcome
|
||||
tags:
|
||||
- Meetings
|
||||
description: Creates a new outcome for the given meeting agenda item.
|
||||
parameters:
|
||||
- description: Meeting identifier
|
||||
example: 1
|
||||
in: path
|
||||
name: meeting_id
|
||||
required: true
|
||||
schema:
|
||||
type: integer
|
||||
- description: Agenda item identifier
|
||||
example: 1
|
||||
in: path
|
||||
name: agenda_item_id
|
||||
required: true
|
||||
schema:
|
||||
type: integer
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: "../components/schemas/meeting_outcome_write_model.yml"
|
||||
responses:
|
||||
'201':
|
||||
description: Created
|
||||
content:
|
||||
application/hal+json:
|
||||
schema:
|
||||
$ref: "../components/schemas/meeting_outcome_model.yml"
|
||||
'400':
|
||||
$ref: "../components/responses/invalid_request_body.yml"
|
||||
'403':
|
||||
content:
|
||||
application/hal+json:
|
||||
schema:
|
||||
$ref: "../components/schemas/error_response.yml"
|
||||
examples:
|
||||
response:
|
||||
value:
|
||||
_type: Error
|
||||
errorIdentifier: urn:openproject-org:api:v3:errors:MissingPermission
|
||||
message: You are not authorized to access this resource.
|
||||
description: |-
|
||||
Returned if the client does not have sufficient permissions.
|
||||
|
||||
**Required permission:** manage outcomes
|
||||
'404':
|
||||
content:
|
||||
application/hal+json:
|
||||
schema:
|
||||
$ref: "../components/schemas/error_response.yml"
|
||||
examples:
|
||||
response:
|
||||
value:
|
||||
_type: Error
|
||||
errorIdentifier: urn:openproject-org:api:v3:errors:NotFound
|
||||
message: The requested resource could not be found.
|
||||
description: |-
|
||||
Returned if the agenda item or meeting does not exist or the client does not have sufficient permissions to see it.
|
||||
'406':
|
||||
$ref: "../components/responses/missing_content_type.yml"
|
||||
'415':
|
||||
$ref: "../components/responses/unsupported_media_type.yml"
|
||||
'422':
|
||||
description: |-
|
||||
Returned if:
|
||||
|
||||
* a constraint for a property was violated (`PropertyConstraintViolation`)
|
||||
@@ -652,13 +652,61 @@ OPENPROJECT_HIDDEN__MENU__ITEMS_ADMIN__MENU="roles types statuses workflows enum
|
||||
|
||||
#### Rate limiting
|
||||
|
||||
OpenProject provides some rate limiting protections. The default configuration protects against repeated access to authentication credential resets (e.g., lost password functionality).
|
||||
OpenProject includes HTTP-layer rate limiting via Rack::Attack. The rules below are configured through the `rate_limiting` setting and take effect without a restart when set via environment variable.
|
||||
|
||||
You can optionally enable additional rules on API rate limiting as follows:
|
||||
In addition to these application-level rules, consider applying rate limiting at your load balancer or reverse proxy (e.g. `ngx_http_limit_req_module`, `mod_security`) for IP-level protection.
|
||||
|
||||
`OPENPROJECT_RATE_LIMITING_API__V3=true`
|
||||
##### Login brute-force protection (enabled by default)
|
||||
|
||||
Additional application-level rate limiting rules will be added in the future. Additionally to these application level rules, use your load balancer / proxying web server to apply individual rate limiting rules using modules such as `ngx_http_limit_req_module` or `mod_security`.
|
||||
OpenProject blocks repeated login attempts per account at the HTTP layer. After **20 POST `/login` requests for the same username within one minute**, that account is blocked for **30 minutes**. This works alongside the application-level lockout (`brute_force_block_after_failed_logins` / `brute_force_block_minutes` settings).
|
||||
|
||||
This rule is **enabled by default**. To disable it:
|
||||
|
||||
```shell
|
||||
OPENPROJECT_RATE_LIMITING_LOGIN="false"
|
||||
```
|
||||
|
||||
The thresholds can be tuned independently:
|
||||
|
||||
| Variable | Default | Description |
|
||||
|---|---|---|
|
||||
| `OPENPROJECT_RATE_LIMITING_LOGIN_BURST__LIMIT` | `20` | Number of attempts allowed before the ban is triggered |
|
||||
| `OPENPROJECT_RATE_LIMITING_LOGIN_BURST__PERIOD` | `60` | Detection window in seconds |
|
||||
| `OPENPROJECT_RATE_LIMITING_LOGIN_BAN__PERIOD` | `1800` | Ban duration in seconds |
|
||||
|
||||
Example: Stricter limits (10 attempts per minute, 1-hour ban):
|
||||
|
||||
```shell
|
||||
OPENPROJECT_RATE_LIMITING_LOGIN_BURST__LIMIT="10"
|
||||
OPENPROJECT_RATE_LIMITING_LOGIN_BURST__PERIOD="60"
|
||||
OPENPROJECT_RATE_LIMITING_LOGIN_BAN__PERIOD="3600"
|
||||
```
|
||||
|
||||
> [!NOTE]
|
||||
> This rule and the application-level brute-force protection (`brute_force_block_after_failed_logins` /
|
||||
> `brute_force_block_minutes`) are independent controls that operate at different layers. The HTTP-layer
|
||||
> rule counts **all** login attempts (including successful ones) within its burst window, while the
|
||||
> application-level setting counts only **failed** attempts and operates over a longer rolling window.
|
||||
> If you lower `brute_force_block_after_failed_logins` below `BURST_LIMIT` (default 20), the
|
||||
> application-level lockout will fire before this rule does. Keep the two thresholds consistent to
|
||||
> avoid surprising behaviour. For example, set `BURST_LIMIT` to match or be lower than
|
||||
> `brute_force_block_after_failed_logins`.
|
||||
|
||||
##### Lost password rate limiting (disabled by default)
|
||||
|
||||
Limits password-reset requests per email address to 3 per hour.
|
||||
|
||||
```shell
|
||||
OPENPROJECT_RATE_LIMITING_LOST__PASSWORD="true"
|
||||
```
|
||||
|
||||
##### API v3 rate limiting (disabled by default)
|
||||
|
||||
Limits API form endpoint requests per session to 6 per 3 seconds.
|
||||
|
||||
```shell
|
||||
OPENPROJECT_RATE_LIMITING_API__V3="true"
|
||||
```
|
||||
|
||||
#### Blacklisted routes
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ For context: The connection of custom SSO providers is also described [here](../
|
||||
## I want to connect AD and LDAP to OpenProject. Which attribute for authentication sources does OpenProject use?
|
||||
|
||||
You can freely define the attributes that are taken from LDAP sources [in the LDAP auth source configuration screen](../ldap-connections/).
|
||||
For group synchronization, OpenProject supports the AD/LDAP standard for groups via "member / memberOf". The attribute cannot be configured at this time.
|
||||
For group synchronization, OpenProject defaults to reverse lookup via the `memberOf` attribute on user entries (Active Directory, OpenLDAP with memberof overlay). If your LDAP server does not maintain `memberOf` on user entries, you can configure forward lookup by setting the **Group member attribute** on a synchronized filter (e.g. `uniqueMember` for `groupOfUniqueNames`, or `member` for `groupOfNames`). See [LDAP group synchronization](../ldap-connections/ldap-group-synchronization/) for details.
|
||||
|
||||
## Is there an option to mass-create users in OpenProject via the LDAP?
|
||||
|
||||
|
||||
+29
-17
@@ -8,15 +8,15 @@ keywords: synchronize ldap groups
|
||||
|
||||
# Synchronize LDAP and OpenProject groups (Enterprise add-on)
|
||||
|
||||
Note: This feature is available for the Enterprise on-premises only, for OpenProject versions 7.4+. For more information and differences to Community edition, [see this page](https://www.openproject.org/enterprise-edition/).
|
||||
This feature is available for the Enterprise edition only. For more information and differences to Community edition, [see the Enterprise edition overview](https://www.openproject.org/enterprise-edition/).
|
||||
|
||||
In OpenProject Enterprise on-premises, you can provision and periodically synchronize groups and their members from your existing LDAP or Active Directory. Group memberships using the [groupOfNames](https://tools.ietf.org/html/rfc4519#section-3.5) LDAP object class are supported for this. This guide assumes that you:
|
||||
In OpenProject Enterprise edition, you can provision and periodically synchronize groups and their members from your existing LDAP or Active Directory. This guide assumes that you:
|
||||
|
||||
- have set up your LDAP authentication source (See the “[Manage LDAP authentication](../../ldap-connections/)” guide)
|
||||
- have at least one LDAP entry with a *groupOfNames* object class and members of that group to contain the *`memberOf: <DN of the group>`* attribute to determine the members of a group entry. Right now we do not support LDAP instances that only have *member* attributes, but not the inverse *memberOf* property.
|
||||
- have LDAP group entries whose members can be discovered either via the `memberOf` attribute on user entries (reverse lookup, default) or via a member list attribute on the group entry such as `uniqueMember` or `member` (forward lookup, configurable).
|
||||
|
||||
> [!NOTE]
|
||||
> OpenProject does not support other attributes other than the `memberOf` property to define groups. Please make sure that user objects have the `memberOf` property for the synchronization to work.
|
||||
> This feature is available for both Enterprise cloud and Enterprise on-premises. When using Enterprise cloud, your LDAP server must be reachable from the OpenProject cloud infrastructure, which typically means exposing it to the internet, which is not recommended. This is a network and security consideration for your organization to evaluate. Consider using [SAML](../../saml/) or [SCIM provisioning](../../scim/) as a more secure alternative.
|
||||
|
||||
For the sake of simplicity, we assume that in this guide, your LDAP structure looks like the following:
|
||||
|
||||
@@ -35,7 +35,7 @@ In order to get to the LDAP group sync administration pane, expand the LDAP auth
|
||||
|
||||
Synchronizing a single LDAP group allows you to connect an existing group in OpenProject with one from LDAP.
|
||||
|
||||
LDAP group synchronization extends the memberships defined by administrators in an existing OpenProject group. Important things to note are:
|
||||
LDAP group synchronization extends the memberships defined by administrators in an existing OpenProject group. Important things to note are:
|
||||
|
||||
- You need to have created at least one manual group in the OpenProject administration before you continue.
|
||||
- Group synchronization for this group is enabled by an administrator creating a *synchronized LDAP group* that ties the OpenProject group to an LDAP entry.
|
||||
@@ -43,30 +43,29 @@ LDAP group synchronization extends the memberships defined by administrators in
|
||||
|
||||
### Single synchronized groups
|
||||
|
||||
To create a new synchronized group, use the button on the top right of the page. There, you will select your LDAP authentication source that contains the group, as well as the existing OpenProject group that members should be synchronized to. The following options can be set:
|
||||
To create a new synchronized group, use the button on the top right of the page. There, you will select your LDAP authentication source that contains the group, as well as the existing OpenProject group that members should be synchronized to. The following options can be set:
|
||||
|
||||
- **LDAP connection:** Select the LDAP connection you want this synchronized group to use. Users created by group synchronization will be tied to that LDAP and may bind against it for authentication.
|
||||
- **DN:** Enter the full distinguished name (DN) of the group you want to synchronize. For example: `cn=team1,ou=groups,dc=example,dc=com`.
|
||||
- **Sync users:** Check this option if you want members of this group to be automatically created in OpenProject. When unchecked, only members of the group that also are existing users in OpenProject can be synchronized.
|
||||
- **Group:** Select an OpenProject group you want the members of the LDAP group to synchronize to.
|
||||
|
||||
Click on *Create* to finish the creation of the synchronized group. The LDAP memberships of each user will be synchronized hourly through a background job on your packaged installation. Changes and output will be logged to */var/log/openproject/cron-hourly.log*.
|
||||
Click on *Create* to finish the creation of the synchronized group. The LDAP memberships of each user will be synchronized hourly through a background job.
|
||||
|
||||
If you want to trigger the synchronization *manually* you can do so by running the respective rake task directly.
|
||||
In the packaged installation, for instance, this would work like this:
|
||||
To trigger a full synchronization manually (group discovery **and** member sync), run the following rake task in a [console](../../../../installation-and-operations/operation/control/):
|
||||
|
||||
```shell
|
||||
sudo openproject run bundle exec rake ldap_groups:synchronize
|
||||
bundle exec rake ldap_groups:synchronize
|
||||
```
|
||||
|
||||
This method of creating synchronized groups is well-suited for a small number of groups, or a very individual set of groups that you need to synchronize. It is very flexible by allowing individual groups to synchronize users into OpenProject.
|
||||
This method of creating synchronized groups is well-suited for a small number of groups, or a carefully selected set of groups that you need to synchronize. It is very flexible by allowing individual groups to synchronize users into OpenProject.
|
||||
|
||||
If you need to synchronize a large number of groups that follow a common pattern, consider using the following filter functionality.
|
||||
|
||||
|
||||
## Automatic groups using synchronized filters
|
||||
|
||||
Instead of manually synchronizing groups from a given group DN in your LDAP, you can also create filter objects that will query the LDAP not only for group members, but the groups themselves.
|
||||
Instead of manually synchronizing groups from a given group DN in your LDAP, you can also create filter objects that automatically discover matching groups in your LDAP and synchronize their members.
|
||||
|
||||
When the synchronization task is executed, the filter is being queried against the LDAP and resulting group objects will be created as synchronized groups *and* as OpenProject groups.
|
||||
|
||||
@@ -82,11 +81,23 @@ To create a new synchronized filter, use the button on the top right of the inde
|
||||
- **LDAP connection:** Select the LDAP connection you want this synchronized filter to use. Users created by group synchronization will be tied to that LDAP and may bind against it for authentication.
|
||||
- **Search base DN:** (optional) Enter the base DN of the LDAP subtree you want to perform the search in. If you leave this unset, the base DN of the LDAP connection will be used instead. The DN specified here must contain the base DN of the LDAP connection to be valid.
|
||||
- **LDAP filter:** The LDAP filter string to be used for identifying LDAP group entries to be synchronized with OpenProject.
|
||||
- **Group member attribute:** (optional) The attribute on group entries that lists member DNs, used for forward lookup. Leave empty (default) to use reverse lookup via the `memberOf` attribute on user entries. Set this to `uniqueMember` for `groupOfUniqueNames` schemas, or `member` for `groupOfNames` schemas. Use forward lookup when your LDAP server does not maintain `memberOf` on user entries.
|
||||
|
||||
Click on *Create* to finish the creation of the synchronized filter. This filter is being executed hourly as part of the background job before the actual group synchronization runs.
|
||||
Click on *Create* to finish the creation of the synchronized filter. This filter is being executed hourly as part of the background job before the actual group synchronization runs.
|
||||
|
||||
Once the filter is created, the **Discover LDAP groups** button on the filter detail page runs the discovery immediately: it queries LDAP using the filter and creates the corresponding synchronized groups and OpenProject groups. It does **not** synchronize group members — member synchronization is handled by the hourly background job.
|
||||
|
||||
To trigger a full synchronization (group discovery **and** member sync) manually, run the following rake task in a [console](../../../../installation-and-operations/operation/control/):
|
||||
|
||||
```shell
|
||||
bundle exec rake ldap_groups:synchronize
|
||||
```
|
||||
|
||||
> [!NOTE]
|
||||
> If you manually create a synchronized group that is also found by a filter, its properties (such as the *Sync users* setting) is being overridden by the filter setting.
|
||||
> Forward lookup via the **Group member attribute** is only available for groups created through a synchronized filter. Manually-created synchronized groups always use reverse lookup (`memberOf`).
|
||||
|
||||
> [!NOTE]
|
||||
> If you manually create a synchronized group that is also found by a filter, its properties (such as the *Sync users* setting) are overridden by the filter settings.
|
||||
|
||||
## FAQ
|
||||
|
||||
@@ -108,6 +119,7 @@ Please double check the DN of the groups and the LDAP connection. The base DN of
|
||||
For users to be automatically synchronized, the following conditions need to be met:
|
||||
|
||||
1. The connection, or the LDAP group need to have "Sync users" checked. This setting overrides the LDAP connection's "Sync users" attribute for fine-grained control over which groups will have users synchronized.
|
||||
2. The group needs to define their members using the `member` LDAP property, and users need to have the `memberOf` property or virtual property. OpenProject will look for users with the following filter: `(memberOf=<DN of the group>).` You can use `ldapsearch` to verify that this works as expected.
|
||||
3. The users defined in the groups need to have all required attributes present to be created. These are at *login, email, first and last name*. If any of these attributes are missing, the user cannot be saved to the database.
|
||||
4. If you enterprise license exceeds the user limit, new users can also not be synchronized through LDAP. A corresponding log entry will be logged.
|
||||
2. OpenProject needs to be able to resolve group members. By default it uses **reverse lookup**: it searches for users with the filter `(memberOf=<DN of the group>)`, which requires `memberOf` to be present on user entries (Active Directory, OpenLDAP with memberof overlay). If your LDAP server does not support `memberOf`, configure **forward lookup** by setting the **Group member attribute** on the synchronized filter (e.g. `uniqueMember` or `member`). You can use `ldapsearch` to verify either approach works as expected.
|
||||
3. The users defined in the groups need to have all required attributes present and non-empty to be created: *login, email, first and last name*. If any of these attributes are missing or empty, the user cannot be saved to the database.
|
||||
4. The **Login** attribute configured on the LDAP connection must match an attribute that is actually present on your LDAP user entries. If the attribute name is wrong or absent, users will be found in the group but not matched or created in OpenProject. Verify the attribute name against your LDAP schema and update the LDAP connection configuration if needed.
|
||||
5. If your enterprise license exceeds the user limit, new users cannot be synchronized through LDAP. On-premises users will find a corresponding entry in the application logs.
|
||||
|
||||
Reference in New Issue
Block a user