{"id":37588721,"url":"https://github.com/republique-et-canton-de-geneve/git-sync","last_synced_at":"2026-01-16T09:49:43.500Z","repository":{"id":43697363,"uuid":"188188193","full_name":"republique-et-canton-de-geneve/git-sync","owner":"republique-et-canton-de-geneve","description":"An application to map roles and users from an LDAP server to a GitLab server.","archived":false,"fork":false,"pushed_at":"2025-08-26T06:56:30.000Z","size":531,"stargazers_count":6,"open_issues_count":1,"forks_count":0,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-08-26T08:43:00.425Z","etag":null,"topics":["authentication","geneva","geneve","gitlab","gitlab-api","gitlab-groups","gitlab-server","gitlab-users","java","ldap","ldap-server","security","synchronization"],"latest_commit_sha":null,"homepage":"","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"agpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/republique-et-canton-de-geneve.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE.txt","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2019-05-23T07:57:22.000Z","updated_at":"2025-08-26T06:56:34.000Z","dependencies_parsed_at":"2022-07-12T23:30:30.303Z","dependency_job_id":"3886c821-704f-4bca-bdb5-4e8670c7539b","html_url":"https://github.com/republique-et-canton-de-geneve/git-sync","commit_stats":null,"previous_names":[],"tags_count":34,"template":false,"template_full_name":null,"purl":"pkg:github/republique-et-canton-de-geneve/git-sync","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/republique-et-canton-de-geneve%2Fgit-sync","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/republique-et-canton-de-geneve%2Fgit-sync/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/republique-et-canton-de-geneve%2Fgit-sync/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/republique-et-canton-de-geneve%2Fgit-sync/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/republique-et-canton-de-geneve","download_url":"https://codeload.github.com/republique-et-canton-de-geneve/git-sync/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/republique-et-canton-de-geneve%2Fgit-sync/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28478049,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-16T06:30:42.265Z","status":"ssl_error","status_checked_at":"2026-01-16T06:30:16.248Z","response_time":107,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["authentication","geneva","geneve","gitlab","gitlab-api","gitlab-groups","gitlab-server","gitlab-users","java","ldap","ldap-server","security","synchronization"],"created_at":"2026-01-16T09:49:41.048Z","updated_at":"2026-01-16T09:49:43.490Z","avatar_url":"https://github.com/republique-et-canton-de-geneve.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"Test\n\nBuild GitHub :\n\n[![Build with GitHub](https://github.com/republique-et-canton-de-geneve/git-sync/actions/workflows/maven.yml/badge.svg)](https://github.com/republique-et-canton-de-geneve/git-sync/actions/workflows/maven.yml)\n\nAnalyse SonarCloud :\n\n[![Bugs](https://sonarcloud.io/api/project_badges/measure?project=republique-et-canton-de-geneve_git-sync\u0026metric=bugs)](https://sonarcloud.io/dashboard?id=republique-et-canton-de-geneve_git-sync)\n[![Vulnerabilities](https://sonarcloud.io/api/project_badges/measure?project=republique-et-canton-de-geneve_git-sync\u0026metric=vulnerabilities)](https://sonarcloud.io/dashboard?id=republique-et-canton-de-geneve_git-sync)\n[![Code Smells](https://sonarcloud.io/api/project_badges/measure?project=republique-et-canton-de-geneve_git-sync\u0026metric=code_smells)](https://sonarcloud.io/dashboard?id=republique-et-canton-de-geneve_git-sync)\n[![Duplicated Lines (%)](https://sonarcloud.io/api/project_badges/measure?project=republique-et-canton-de-geneve_git-sync\u0026metric=duplicated_lines_density)](https://sonarcloud.io/dashboard?id=republique-et-canton-de-geneve_git-sync)\n[![Coverage](https://sonarcloud.io/api/project_badges/measure?project=republique-et-canton-de-geneve_git-sync\u0026metric=coverage)](https://sonarcloud.io/dashboard?id=republique-et-canton-de-geneve_git-sync)\n[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=republique-et-canton-de-geneve_git-sync\u0026metric=alert_status)](https://sonarcloud.io/dashboard?id=republique-et-canton-de-geneve_git-sync)\n\nLicence :\n\n[![License: AGPL v3](https://img.shields.io/badge/License-AGPL%20v3-blue.svg)](https://www.gnu.org/licenses/why-affero-gpl.html)\n\n\ngitSync is a standalone Java application which replicates the users from an LDAP source into a\n[GitLab](https://about.gitlab.com) server.\n\n# Presentation\n\n## Context\n\nAt État de Genève we use an Active Directory server to authenticate users, plus a home-made solution\n(hereafter denoted as \"the LDAP server\" ) to manage authorizations.\nAs is done in many organizations, the LDAP server organizes users in groups, such\nas \"IT-DEV-JAVA\", \"IT-DEV-PHP\" or \"FINANCE\".\nIn the LDAP server, access rights to systems and applications are granted to groups or to users.\nA user is assigned to any number of groups.\n\nNow GitLab comes into play.\nAt État de Genève, the GitLab server is an on-premise instance of the GitLab community edition.\nOur need is to *replicate automatically the LDAP configuration of the users onto the GitLab server*.\nFor example, we want the GitLab server to acquire automatically groups \"IT-DEV-JAVA', 'IT-DEV-PHP', etc.,\nfrom the LDAP server, as well as the members of every group.\nWe also want the GitLab group members to acquire the adequate right permissions.\n\n# Functional description\n\nThis section specifies the expected behavior of the application.\n\n## Definitions\n\n### Dry run mode\n\nThe parameter `dry-run` allows you to run the application without incurring any modification in GitLab.\nOnly logs are displayed so you can check future modifications before to set `dry-run` to false.\nIf the parameter is absent, False is the default value.\n\n### Standard groups and administrator group\n\nBy default, a LDAP group is considered as a \"standard\" groups. One group can be an \"administrator\" group.\nFor example, LDAP group \"IT-DEV-JAVA\" is a standard group, whereas LDAP group \"ADMIN\" is the\nadministrator group.\nThe name of the administrator group, if any, is supplied as parameter `admin-group` in the application's configuration file.\nThe name of the standard groups must follow the naming convention defined by the parameter `standard.groups` (regex pattern).\n\n### GitLab users not to be cleaned\n\nSome special GitLab users will not be removed from GitLab groups, even if they have been removed from the\ncorresponding LDAP role.\nThese users are usually technical users.\nAt État de Genève, such a wide-access GitLab user has been created to allow a\n[Fisheye](https://www.atlassian.com/software/fisheye) server to log on to GitLab and to freely retrieve commits\nfrom any Git repository.\nThe list of wide-access GitLab users, if any, is supplied as parameter `not-to-clean-users`\nin the application's configuration file.\n\n### Blacklisted groups\n\nFor some business rules below, some LDAP groups will be explicitly omitted. \nThe list of blacklisted LDAP groups, if any, is supplied as parameter `black-listed-groups`\nin the application's configuration file.\n\n### Limited-access groups\n\nAt État de Genève the baseline is to provide read-only access (Developer role) to all developers.\nThat is, every developer is able to discover code, clone and create merge requests, even on code that is outside\ntheir working group.\nFor historical reasons, some groups must remain preserved from such a wide access.\nThe list of such limited-access groups, if any, is supplied as parameter `limited-access-groups` in the\napplication's configuration file.\n\n## Business rules\n\nThis section lists the actions that are carried out at every execution of the application.\n\n### BR1 : create GitLab groups\n\nFor every standard LDAP group, create a GitLab group (hereafter coined the \"matching GitLab group\") with the same name,\nif such group does not exist yet.\n \n### BR2 : manage Maintainers\n\nFor every standard LDAP group, retrieve the list of users (LU).\n  * For every user in list LU:\n    * If the user does not exist in GitLab, do nothing\n      (see section [GitLab authentication](#gitlab-authentication)).\n    * If the user already exists in GitLab and is not assigned to the matching GitLab group, \n      assign it with the \"Maintainer\" GitLab role.\n    * If the user already exists in GitLab and is already assigned to the matching GitLab group, do nothing.\n  * Additionally:  \n    * If any user already exists in GitLab, is assigned to the matching GitLab group, has \"Maintainer role\",\n      but is not in list LU, remove it from the GitLab group,\n      unless the user belongs to the list of not-to-be-cleaned users.\n\n### BR3 : set all others users are Developers\n\nCreate Retrieve the list of users (LU) of all standard GitLab groups:\n  * For every user in list LU:\n    * For every GitLab group not in `limited-access-groups`:\n      * If the user is not assigned to the GitLab group, assign it with the \"Developer\" GitLab role.\n      * If the user is already assigned to the GitLab group with a role weaker than \"Developer\",\n        assign it with the \"Developer\" GitLab role.\n      * If the user is already assigned to the GitLab group with a role stronger than or equal to \"Developer\",\n        do nothing.\n\n### BR4 : manage administrators\n\nFor the administrator LDAP group (if any, defined by `admin-group`), retrieve the list of users. For every user in the list:\n  * If the user does not exist in GitLab, do nothing\n    (see section [GitLab authentication](#gitlab-authentication)).\n  * If the user already exists in GitLab:\n    * Give it the Admin (as opposed to Regular) access level.\n\n### BR5 : manage Owners\n\nFor the owner LDAP group (if any, defined by `owner-group`), retrieve the list of users. For every user in the list:\n  * If the user does not exist in GitLab, do nothing\n    (see section [GitLab authentication](#gitlab-authentication)).\n  * If the user exists in GitLab and the user is not a GitLab administrator:\n    * Assign it to all non-administrator groups (with Owner role permission), except the groups in a\n      black list supplied as a parameter to the application.\n\n### BR6 : block or unblock users\n\nRetrieve the list of users (LU) of all standard GitLab groups:\n  * For every user in list LU:\n    * If the user is blocked in Gitlab and is active in LDAP, unblock the user\n    * If the user is active in Gitlab and is inactive in LDAP, block the user\n\n### Notes\n  * The business rules for the standard groups (BR2) are applied before the business rules for the\n    administrator group (BR4), otherwise GitLab users having Admin access level would end up being\n    downgraded to Regular access level.\n  * In all business rules here above, whenever a list of users (LU) is mentioned, only the users\n    whose name comply with the regular expression supplied by the parameter `standard-group-users`\n    are considered. The other users are ignored.\n\n## Remarks\n\n* The application does not create GitLab users.\n* The application does not create GitLab sub-groups. It only creates GitLab groups.\n* Whenever the application creates a GitLab group, the GitLab role permission \"Owner\" group is automatically\n  assigned by GitLab to the user used for the connection.\n  That user is the user that matches the connection token ``gitlab.account.token`` to be supplied in the\n  configuration file.\n* If an existing GitLab group matches no LDAP group, nothing happens to it.\n* The replication is one-way: from the LDAP server to the GitLab server. Accordingly, read-only access\n  to the LDAP server is sufficient. \n\n## GitLab authentication\n\nThe application deals with authorization only, not with authentication.\nUser authentication on the GitLab server is configured directly on the GitLab server. \nThe authentication provider is typically an LDAP server, possibly the one the application extracts its data from. \n\nAt État de Genève, end users known to the LDAP server but still unknown to the GitLab server acquire their\nconfiguration on the GitLab server by means of the following sequence of operations:\n1. On a browser the user navigates to the GitLab server.\n   This performs authentication.\n   In response the GitLab displays a blank page, which leads the user to believe that connexion went wrong, but\n   this weird behavior is expected.\n   Behind the scenes GitLab creates a user with access level \"Regular\" (as opposed to \"Admin\").\n2. Next time the application is run, it loads the user's configuration into GitLab.\n3. When the user navigates again to the GitLab server, they readily see all projects they have access to.\n   For example, if the user belongs to groups \"GROUP-IT-DEV\" and \"GROUP-IT-NETWORK\", they will have access\n   to all Git projects defined in these 2 groups as well as in their sub-groups. \n\nFor each user, the hand-shake step 1 above occurs only once. \n\n# Technical facts\n\nThe application is a standalone application. It performs the replication once and then exits.\nIt requires Java 11+ to build and run.\n\nCommunication with the LDAP server is performed by means of a home-made Java library named\n[gina-ldap-client](https://github.com/republique-et-canton-de-geneve/gina-ldap-client)\n(in French) which internally resorts to the standard `javax.naming.ldap` API.\nThat library is specifically tuned to the LDAP data model of Gina, the État de Genève's LDAP server.\nTransforming the application to connect to another LDAP server than Gina entails the following changes:\n* Creating a class, say, `CustomLdapTreeBuilder` that implements interface\n  [LdapTreeBuilder](src/main/java/ch/ge/cti_composant/gitsync/util/ldap/LdapTreeBuilder.java);\n* In the top class\n  [GitSync](src/main/java/ch/ge/cti_composant/gitsync/GitSync.java), replacing the usage of\n  [GinalLdapTreeBuilder](src/main/java/ch/ge/cti_composant/gitsync/util/ldap/gina/GinaLdapTreeBuilder.java)\n  with that of `CustomLdapTreeBuilder`;\n* In the properties file [configuration.properties](./configuration.properties),\n  replacing the settings of the Gina LDAP server with those of the custom LDAP server.\n\nCommunication with the GitLab server is performed by means of GitLab's own\n[java-gitlab-api](https://mvnrepository.com/artifact/org.gitlab/java-gitlab-api)\nlibrary, which internally resorts to REST services. \nThe application is compatible with any GitLab server, for example GitLab 11.X or GitLab 14.X, \nthat complies with version 4 of the GitLab API.\n\nThe application stores the whole LDAP configuration of the groups in memory. For a few hundred users this has not\ncaused any trouble so far.\n\nIf some glitch happens when extracting the user configuration from the LDAP server (for example, the data have\nbeen moved to another zone of the LDAP tree) and the application comes up with an empty configuration, replication\nto the GitLab server will simply clean up the user assignments, thereby preventing all users from\naccessing their projects.\nIn order to prevent such bad situation from occurring, an extra configuration parameter `minimum-user-count`\nis provided:\nif the user configuration contains fewer users than the value of the parameter, the application exits without performing\nthe replication.\n\n# Build\n\nRun\n\n``mvn clean install``\n\nStarting from version 3.10, the dependency on `gina-ldap-client` 4.2.0 is no longer available in Maven Central.\nSo if you are a developer from outside État de Genève, before running the above command,\nyou will have to clone\n[projet gina-ldap-client](https://github.com/republique-et-canton-de-geneve/gina-ldap-client)\nfrom GitLab, then build it (`mvn install`), then modify the POM of this projet to make it depend on your\nsnapshot version of `gina-ldap-client` instead of the release version.\n\n# Execution\n\nThe mapping is carried out by running the application:\n\n``\njava -jar target/gitSync-XXX-SNAPSHOT.jar configuration.properties\n``\n\nCopy the file [configuration-base.properties](./configuration-base.properties) to configuration.properties and update it with the appropriate parameter values.\nThe unique parameter is the path to the configuration file.\nThe parameters in the supplied file configuration.properties must be adapted.\n\nLogging is performed by means of Logback. A basic configuration file `logback.xml` is included in the source\ntree and is present in the JAR file created by the `mvn install` command.\nIt is sufficient for standard analysis, although some people might find the INFO-level console output too verbose.\n\nAt État de Genève, execution typically takes a few minutes to execute. It processes\nabout 100 groups encompassing 1000 group users.\n\nPractical usage requires spawning the application regularly, for example every hour.\nThis can be done with a crontab-like job.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frepublique-et-canton-de-geneve%2Fgit-sync","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frepublique-et-canton-de-geneve%2Fgit-sync","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frepublique-et-canton-de-geneve%2Fgit-sync/lists"}