Skip to content

Commit bf7aaa8

Browse files
committed
feat: add Azure DevOps enterprise integration support
This commit adds enterprise-grade Azure DevOps support with webhook-based automation and service principal authentication for multi-tenant SaaS deployments. Key changes: - Implemented webhook event processing for work items and pull requests - Added service principal authentication for automated operations - Implemented Azure DevOps manager for handling webhook events - Added work item and PR comment support with @mention detection - Created callback processor for asynchronous webhook responses - Added webhook registration and management endpoints - Implemented Azure DevOps user enrichment and token management - Added database schema for webhook storage - Fixed API response format handling for comments and field values - Removed linter suppressions for cleaner code The implementation enables: - Automated responses to work item assignments and @mentions - Pull request comment handling with inline code review support - Service principal-based API operations for scalability - Multi-organization support with webhook deduplication - Integration with Keycloak for user management - Webhook-driven conversation creation and management
1 parent 1d9cf72 commit bf7aaa8

34 files changed

+4924
-17
lines changed

enterprise/allhands-realm-github-provider.json.tmpl

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1771,6 +1771,47 @@
17711771
"sendIdTokenOnLogout": "true",
17721772
"passMaxAge": "false"
17731773
}
1774+
},
1775+
{
1776+
"alias": "azure_devops",
1777+
"displayName": "Azure DevOps",
1778+
"internalId": "7e42c8f1-9d3a-4f1e-b5c7-2a8f6e9d4c1b",
1779+
"providerId": "oidc",
1780+
"enabled": true,
1781+
"updateProfileFirstLoginMode": "on",
1782+
"trustEmail": true,
1783+
"storeToken": true,
1784+
"addReadTokenRoleOnCreate": true,
1785+
"authenticateByDefault": false,
1786+
"linkOnly": false,
1787+
"hideOnLogin": false,
1788+
"config": {
1789+
"acceptsPromptNoneForwardFromClient": "false",
1790+
"tokenUrl": "https://login.microsoftonline.com/$AZURE_DEVOPS_TENANT_ID/oauth2/v2.0/token",
1791+
"isAccessTokenJWT": "true",
1792+
"jwksUrl": "https://login.microsoftonline.com/$AZURE_DEVOPS_TENANT_ID/discovery/v2.0/keys",
1793+
"filteredByClaim": "false",
1794+
"backchannelSupported": "false",
1795+
"caseSensitiveOriginalUsername": "false",
1796+
"loginHint": "false",
1797+
"clientAuthMethod": "client_secret_post",
1798+
"syncMode": "IMPORT",
1799+
"clientSecret": "$AZURE_DEVOPS_CLIENT_SECRET",
1800+
"allowedClockSkew": "0",
1801+
"defaultScope": "openid email profile offline_access 499b84ac-1321-427f-aa17-267ca6975798/.default",
1802+
"userInfoUrl": "https://graph.microsoft.com/oidc/userinfo",
1803+
"validateSignature": "false",
1804+
"clientId": "$AZURE_DEVOPS_CLIENT_ID",
1805+
"uiLocales": "false",
1806+
"disableNonce": "false",
1807+
"useJwksUrl": "true",
1808+
"sendClientIdOnLogout": "false",
1809+
"pkceEnabled": "false",
1810+
"authorizationUrl": "https://login.microsoftonline.com/$AZURE_DEVOPS_TENANT_ID/oauth2/v2.0/authorize",
1811+
"disableUserInfo": "true",
1812+
"sendIdTokenOnLogout": "true",
1813+
"passMaxAge": "false"
1814+
}
17741815
}
17751816
],
17761817
"identityProviderMappers": [
@@ -1818,6 +1859,17 @@
18181859
"attribute": "identity_provider"
18191860
}
18201861
},
1862+
{
1863+
"id": "9e8d7c6b-5a4f-3e2d-1c0b-9a8e7d6c5b4a",
1864+
"name": "identity-provider",
1865+
"identityProviderAlias": "azure_devops",
1866+
"identityProviderMapper": "hardcoded-attribute-idp-mapper",
1867+
"config": {
1868+
"attribute.value": "azure_devops",
1869+
"syncMode": "FORCE",
1870+
"attribute": "identity_provider"
1871+
}
1872+
},
18211873
{
18221874
"id": "37238720-ccd7-4d91-a6a0-476851851d0f",
18231875
"name": "identity-provider",
@@ -1945,7 +1997,7 @@
19451997
"subComponents": {},
19461998
"config": {
19471999
"kc.user.profile.config": [
1948-
"{\"attributes\":[{\"name\":\"username\",\"displayName\":\"${username}\",\"validations\":{\"length\":{\"min\":3,\"max\":255},\"username-prohibited-characters\":{},\"up-username-not-idn-homograph\":{}},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"multivalued\":false},{\"name\":\"email\",\"displayName\":\"${email}\",\"validations\":{\"email\":{},\"length\":{\"max\":255}},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"multivalued\":false},{\"name\":\"firstName\",\"displayName\":\"${firstName}\",\"validations\":{\"length\":{\"max\":255},\"person-name-prohibited-characters\":{}},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"multivalued\":false},{\"name\":\"lastName\",\"displayName\":\"${lastName}\",\"validations\":{\"length\":{\"max\":255},\"person-name-prohibited-characters\":{}},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"multivalued\":false},{\"name\":\"github_id\",\"displayName\":\"GitHub ID\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"user\"],\"edit\":[\"admin\"]},\"multivalued\":false},{\"name\":\"identity_provider\",\"displayName\":\"Identity Provider\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\"]},\"multivalued\":false},{\"name\":\"gitlab_id\",\"displayName\":\"GitLab ID\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\"]},\"multivalued\":false}],\"groups\":[{\"name\":\"user-metadata\",\"displayHeader\":\"User metadata\",\"displayDescription\":\"Attributes, which refer to user metadata\"}]}"
2000+
"{\"attributes\":[{\"name\":\"username\",\"displayName\":\"${username}\",\"validations\":{\"length\":{\"min\":3,\"max\":255},\"username-prohibited-characters\":{},\"up-username-not-idn-homograph\":{}},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"multivalued\":false},{\"name\":\"email\",\"displayName\":\"${email}\",\"validations\":{\"email\":{},\"length\":{\"max\":255}},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"multivalued\":false},{\"name\":\"firstName\",\"displayName\":\"${firstName}\",\"validations\":{\"length\":{\"max\":255},\"person-name-prohibited-characters\":{}},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"multivalued\":false},{\"name\":\"lastName\",\"displayName\":\"${lastName}\",\"validations\":{\"length\":{\"max\":255},\"person-name-prohibited-characters\":{}},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"multivalued\":false},{\"name\":\"github_id\",\"displayName\":\"GitHub ID\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"user\"],\"edit\":[\"admin\"]},\"multivalued\":false},{\"name\":\"identity_provider\",\"displayName\":\"Identity Provider\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\"]},\"multivalued\":false},{\"name\":\"gitlab_id\",\"displayName\":\"GitLab ID\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\"]},\"multivalued\":false},{\"name\":\"azure_devops_id\",\"displayName\":\"Azure DevOps ID\",\"validations\":{},\"annotations\":{},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\"]},\"multivalued\":false}],\"groups\":[{\"name\":\"user-metadata\",\"displayHeader\":\"User metadata\",\"displayDescription\":\"Attributes, which refer to user metadata\"}]}"
19492001
]
19502002
}
19512003
}

enterprise/enterprise_local/convert_to_env.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,9 @@ def convert_yaml_to_env(yaml_file, target_parameters, output_env_file, prefix):
109109
lines.append(
110110
'OPENHANDS_BITBUCKET_SERVICE_CLS=integrations.bitbucket.bitbucket_service.SaaSBitBucketService'
111111
)
112+
lines.append(
113+
'OPENHANDS_AZURE_DEVOPS_SERVICE_CLS=integrations.azure_devops.azure_devops_service.SaaSAzureDevOpsService'
114+
)
112115
lines.append(
113116
'OPENHANDS_CONVERSATION_VALIDATOR_CLS=storage.saas_conversation_validator.SaasConversationValidator'
114117
)
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
"""Azure DevOps enterprise integration module."""
2+
3+
from integrations.azure_devops.azure_devops_service import SaaSAzureDevOpsService
4+
5+
__all__ = ['SaaSAzureDevOpsService']

0 commit comments

Comments
 (0)