Ansicht von 13 Antwort-Threads
  • Autor
    Beiträge
    • #41833
      Sascha Behnsen
      Teilnehmer

        Hallo zusammen!

        Wir haben unser OTOBO 11 Docker via OpenIDConnect an Entra angebunden. Agent und Customer Login. Das hat soweit mittels Hilfe aus dem FAQ und Google Suche auch geklappt :-)

        Derzeit haben wir aber die Customeruser noch via LDAP Sync (genau wie ehem. unser OTRS) an unser lokales AD angebunden und hier ein „wildes“ Mapping mit zig zusätzlichen Feldern.
        Also via Module => ‚Kernel::System::CustomerUser::LDAP‘,

        Hier stellt sich mir die Frage und ich habe dazu nichts im Forum oder der Defaults.pm gefunden:
        Kann man das auch über Entra beziehen, so dass kein lokales AD mehr nötig ist? Die Felder / ExtensionAttibutes haben wir soweit bereits ins Entra gesynct.

        Bei OTRS scheint es zu geben:
        Kernel::System::CustomerUser::OIDC

        Bei OTOBO habe ich dazu – wie gesagt – nichts gefunden. Vielleicht hat ja jemand sowas schon mal probiert oder es ist in Planung?

        Danke und VG,
        Sascha

      • #41835
        Arnold
        Administrator

          Hi Sascha,

          man kann das Auth für CustomerUser auch über OIDC abbilden, aber man braucht dann noch einen Sync für die Daten. Das ist anders als bei den Agenten, deren Daten per Map komplett aus dem OIDC kommen können.

          Der Hintergrund ist die Art der Datenverarbeitung. Der Agent meldet sich an und hat dann frische Daten für seine Session. Die Daten der CustomerUser werden verarbeitet ohne das sich dieser anmeldet. OTOBO muss die Daten hier zum beliebigen Zeitpunkt anfragen können.

          Beispielkonfiguration, wie man das Auth für CustomerUser einrichtet, findest du in der Defaults.pm: https://github.com/RotherOSS/otobo/blob/rel-11_0/Kernel/Config/Defaults.pm#L1560

          Viel Spaß mit deinem OTOBO!

        • #41838
          Sascha Behnsen
          Teilnehmer

            Achso, also geht das mit dem CustomerUser Backend über OIDC dann also gar nicht?

            Weil es geht nicht um den „CustomerAuth“, das funktioniert via OIDC. Es geht um die befüllung der Tabelle CustomerUser, was wir jetzt noch über LDAP „Module => ‚Kernel::System::CustomerUser::LDAP‘,“ machen…

          • #41839
            Arnold
            Administrator

              Meines Wissens nach, ist OIDC ist eine Authentifizierungsschicht und kein Verziechnisdienst, zumindest von der Konzeption her.

              Hast du eine Quelle die sagt wie man beliebige Nutzer aus einer OIDC-Schnittstelle abfragen kann? Ich wäre hier interessiert.

            • #41840
              Sascha Behnsen
              Teilnehmer

                Leider nicht wirklich.

                Würde gerne auf die LDAP Anbindung für das CustomerUser Backend verzichten und das auch komplett über Entra machen…

                Das einzige, was ich dazu finden kann, ist etwas bei OTRS:

                Kernel::System::CustomerUser::OIDC – CustomerUser backend for OIDC

              • #41845
                Arnold
                Administrator

                  Hallo Sacha,

                  EntraID bietet noch mehr Schnittstellen als OIDC. Hier käme insbesondere die Graph API in Frage. Bisher scheint das aber Nutzern nicht wichtig genug um hier aktiv zu werden. OTOBO hat noch keine Unterstützung für diese Schnittstelle. Vielleicht ist das Interesse ja mal groß genug, dass jemand die durch die Entwicklung entstehenden Aufwände trägt. Bis dahin vertrauen wir auf LDAP und SQL für die Nutzerdaten.

                • #41848
                  marcel-graf
                  Teilnehmer

                    Hallo Zusammen,

                    ich habe mal bisschen Recheriert und auch die KI dazu befragt. Hier das Ergebnis:
                    Vorab, über den in Weg B beschribenen Prozess könnte man sicherlich was scripten.

                    Wenn Sie über OpenID Connect (OIDC) nicht nur das Login absichern, sondern auch das komplette Kundenverzeichnis im Hintergrund abgleichen (synchronisieren) möchten (z. B. damit Agenten beim Erstellen eines Telefontickets sofort alle OIDC-Kunden per Autovervollständigung finden), stehen Sie vor einer technischen Hürde: OIDC ist ein reines Authentifizierungsprotokoll und bietet von sich aus keine Such- oder Abfragefunktion für Verzeichnisse. [1, 2]
                    Um dies in OTOBO elegant zu lösen, kombiniert man das OIDC-Login mit einer Just-in-Time-Provisionierung (Daten werden beim Login in die lokale OTOBO-Datenbank geschrieben) oder nutzt ein Skript, das die Microsoft Graph API abfragt. [1]
                    Hier sind die zwei bewährten Wege, um den Abgleich in OTOBO zu realisieren:

                     

                    Weg A: Just-in-Time Provisionierung (Die Standard-OIDC-Methode)
                    Bei dieser Methode werden die Benutzerdaten synchronisiert, sobald sich ein Kunde das erste Mal per OIDC einloggt. Das OTOBO-Kunden-Backend wird dafür auf die lokale Datenbank (Kernel::System::CustomerUser::DB) umgestellt. OTOBO zieht sich beim Login alle Attribute aus dem Entra ID-Token und legt den Kunden lokal an bzw. aktualisiert ihn bei jedem Folge-Login. [1, 2, 3]
                    Vorteil: Extrem performant, keine Zusatzskripte nötig, Daten sind immer absolut up-to-date.
                    Nachteil: Ein Kunde taucht in der Agenten-Suche erst auf, nachdem er sich mindestens einmal am OTOBO-Kundenportal angemeldet hat. [1]

                    Konfiguration in der Kernel/Config.pm:

                    perl
                    # 1. OIDC Authentifizierung für das Kundenportal aktivieren
                    $Self->{‚Customer::AuthModule‘} = ‚Kernel::System::CustomerAuth::OpenIDConnect‘;
                    $Self->{‚Customer::AuthModule::OpenIDConnect‘} = {
                    URL => ‚https://microsoftonline.com{ihre-tenant-id}/v2.0‘,
                    ClientID => ‚IHRE_CLIENT_ID‘,
                    ClientSecret => ‚IHR_CLIENT_SECRET‘,
                    Scope => [‚openid‘, ‚profile‘, ‚email‘],
                    UserIDClaim => ‚preferred_username‘, # Entra-Loginname (E-Mail)
                    };

                    # 2. AUTOMATISCHER ABGLEICH (Just-in-Time Provisionierung in die DB)
                    $Self->{‚Customer::AuthModule::OpenIDConnect‘}->{ProvisionCustomerUser} = 1;

                    # Zuordnung: Welcher Entra-Claim wandert in welches OTOBO-Datenbankfeld
                    $Self->{‚Customer::AuthModule::OpenIDConnect‘}->{UserMap} = {
                    ‚UserFirstname‘ => ‚given_name‘,
                    ‚UserLastname‘ => ‚family_name‘,
                    ‚UserEmail‘ => ‚email‘,
                    ‚UserCustomerID‘ => ‚email‘, # Oder ein anderes Feld für die Firmenzuordnung
                    };

                    # 3. Das Kunden-Backend liest/sucht nun in dieser lokalen Datenbank
                    $Self->{CustomerUser} = {
                    Name => ‚Lokale DB (Synchronisiert via OIDC)‘,
                    Module => ‚Kernel::System::CustomerUser::DB‘,
                    Params => {
                    Table => ‚customer_user‘,
                    },
                    CustomerKey => ‚login‘,
                    CustomerID => ‚customer_id‘,
                    CustomerUserListFields => [‚first_name‘, ‚last_name‘, ‚email‘],
                    CustomerUserSearchFields => [‚login‘, ‚first_name‘, ‚last_name‘, ‚email‘],
                    CustomerUserPostMasterSearchFields => [‚email‘],
                    CustomerUserNameFields => [‚first_name‘, ‚last_name‘],
                    Map => [
                    [ ‚UserFirstname‘, ‚Firstname‘, ‚first_name‘, 1, 1, ‚var‘, “, 0 ],
                    [ ‚UserLastname‘, ‚Lastname‘, ‚last_name‘, 1, 1, ‚var‘, “, 0 ],
                    [ ‚UserLogin‘, ‚Username‘, ‚login‘, 1, 1, ‚var‘, “, 0 ],
                    [ ‚UserEmail‘, ‚Email‘, ‚email‘, 1, 1, ‚var‘, “, 0 ],
                    [ ‚UserCustomerID‘, ‚CustomerID‘, ‚customer_id‘, 0, 1, ‚var‘, “, 0 ],
                    ],
                    };

                     

                    Weg B:

                    Vollständiger Vorab-Abgleich via Microsoft Graph API (Empfohlen für Agenten-Suche)
                    Wenn Sie zwingend alle Mitarbeiter/Kunden sofort im System durchsuchbar machen möchten, ohne dass diese sich je eingeloggt haben, müssen Sie die Benutzerdaten aktiv aus der Cloud abrufen. Da OTOBO (und OTRS) nativ keinen „Graph-API-Verzeichnis-Sync“ im CustomerUser-Backend eingebaut haben, nutzt man hierfür ein automatisiertes Synchronisations-Skript. [1]

                    Ablauf:
                    Das OTOBO Kunden-Backend verbleibt (wie in Weg A) auf der lokalen Datenbank (Kernel::System::CustomerUser::DB).
                    Sie geben der App-Registrierung in Microsoft Entra ID unter API-Berechtigungen das Anwendungsrecht (Application Permission) User.Read.All für die Microsoft Graph API und stimmen als Admin zu.
                    Ein kleines Python- oder Bash-Skript läuft als stündlicher Cronjob auf dem OTOBO-Server. [1, 2, 3]

                    Beispiel für das Sync-Skript (Konzeptidee für Cronjob):
                    Das Skript holt per REST-API die Benutzer ab und schreibt sie direkt in die OTOBO-Tabelle customer_user:

                    python
                    import requests
                    import database_connector # Ihre OTOBO DB-Verbindung

                    # 1. Token von Entra ID holen
                    token_url = „https://microsoftonline.com{tenant_id}/oauth2/v2.0/token“
                    data = {
                    ‚grant_type‘: ‚client_credentials‘,
                    ‚client_id‘: ‚IHRE_CLIENT_ID‘,
                    ‚client_secret‘: ‚IHR_CLIENT_SECRET‘,
                    ’scope‘: ‚https://microsoftonline.com‘
                    }
                    token = requests.post(token_url, data=data).json()[‚access_token‘]

                    # 2. Benutzer aus Entra ID abfragen
                    headers = {‚Authorization‘: f’Bearer {token}‘}
                    users = requests.get(‚https://microsoftonline.com‘, headers=headers).json()

                    # 3. In OTOBO-Datenbank spiegeln
                    for user in users[‚value‘]:
                    # SQL-Logik: INSERT INTO customer_user … ON DUPLICATE KEY UPDATE …
                    pass

                     

                    Wir nutzen auch Entra ID, haben aber für die Kundenbenutzer aktuell auch nicht den Bedarf.

                    Gruß Marcel

                  • #41849
                    Arnold
                    Administrator

                      Weg A ist ja der Weg, der für Agenten gegangen wird.
                      Nachteil von Weg A ist zusätzlich, dass die Daten auch immer so alt sind wie das letzte Login war.
                      Die fehlende Praktikabilität ist der Grund warum das aktuell nicht für CustomerUser implementiert ist.

                      Weg B geht eben gegen die Graph API. Hier zwei Optionen:
                      1. Sync in die DB von Aussen
                      2. Inline sync als native OTOBO Schnittstelle

                      Ich kann mir vorstellen, dass Option 1 schlecht skaliert. Wenn ich stündlich 10.000 User über die GraphAPI ziehe, bin ich mir unklar über evtl. Rate Limits. Für eine kleine Anzahl an Datensätzen sicher ein probater Weg.  Man muss sicher hier noch gucken wie man CustomerUser handhaben will, die zwar in der DB sind aber nicht mehr im EntraID und so weiter…

                    • #41850
                      marcel-graf
                      Teilnehmer

                        Hallo Arnold,

                        man könnte sicher im AzureAD / Entra ein Flag setzen, dass nur die User  mit diesem Flag synchronisiert werden. Oder besser noch über eine AzureAD Gruppe „Otobo_CustomerUser“ die dann nur die bestimmten User enthält, die verarbeitet werden sollen.

                        Das Script würde dann so in etwa aussehen.

                        #————————————————

                        import requests

                        # 1. Token von Microsoft Entra ID holen
                        tenant_id = „IHR_TENANT_ID“
                        token_url = f“https://microsoftonline.com{tenant_id}/oauth2/v2.0/token“

                        token_data = {
                        „grant_type“: „client_credentials“,
                        „client_id“: „IHRE_CLIENT_ID“,
                        „client_secret“: „IHR_CLIENT_SECRET“,
                        „scope“: „https://microsoft.com“
                        }

                        token_response = requests.post(token_url, data=token_data).json()
                        token = token_response.get(„access_token“)

                        # 2. Mitglieder der Gruppe „otobo-customer“ abfragen
                        # ERSETZEN SIE DIESE ID MIT DER OBJEKT-ID IHRER ENTRA-GRUPPE
                        GROUP_ID = „IHRE_AZURE_AD_GRUPPEN_OBJEKT_ID“

                        # Endpunkt für Gruppenmitglieder (filtert automatisch auf Direktmitglieder)
                        graph_url = f“https://microsoft.com{GROUP_ID}/members“
                        headers = {„Authorization“: f“Bearer {token}“}
                        users_response = requests.get(graph_url, headers=headers).json()
                        users = users_response.get(„value“, [])

                        # 3. In OTOBO spiegeln via REST API
                        otobo_url = „https://ihr-otobo-system.de“
                        otobo_headers = {
                        „Content-Type“: „application/json“,
                        „X-OTOBO-API-Key“: „IHR_OTOBO_API_KEY“
                        }

                        for user in users:
                        # Wichtig: Der /members Endpunkt liefert verschiedene Objekttypen (z.B. auch Gruppen).
                        # Wir filtern hier explizit so, dass nur Benutzer-Objekte verarbeitet werden.
                        if user.get(„@odata.type“) == „#microsoft.graph.user“:
                        customer_data = {
                        „UserLogin“: user.get(„userPrincipalName“),
                        „UserFirstname“: user.get(„givenName“) or „Vorname“,
                        „UserLastname“: user.get(„surname“) or „Nachname“,
                        „UserEmail“: user.get(„mail“) or user.get(„userPrincipalName“),
                        „UserCustomerID“: user.get(„mail“) or user.get(„userPrincipalName“),
                        „ValidID“: 1
                        }

                        # Senden an OTOBO
                        response = requests.post(otobo_url, json=customer_data, headers=otobo_headers)
                        print(f“Synchronisiert: {customer_data[‚UserLogin‘]} – Status: {response.status_code}“)

                        #————————————————

                        Gruß Marcel

                      • #41851
                        marcel-graf
                        Teilnehmer

                          oder hier mit Bereinigung bzw. Deaktivierung  der Customer User aus Otobo

                          import requests

                          def azure_to_otobo_sync():
                          print(„Starte Synchronisation und Bereinigung…“)

                          tenant_id = „IHR_TENANT_ID“
                          token_url = f“https://microsoftonline.com{tenant_id}/oauth2/v2.0/token“

                          token_data = {
                          „grant_type“: „client_credentials“,
                          „client_id“: „IHRE_CLIENT_ID“,
                          „client_secret“: „IHR_CLIENT_SECRET“,
                          „scope“: „https://microsoft.com“
                          }

                          try:
                          # 1. Token von Azure AD holen
                          token_response = requests.post(token_url, data=token_data).json()
                          token = token_response.get(„access_token“)

                          # 2. Aktive Gruppenmitglieder aus Azure AD abfragen
                          GROUP_ID = „IHRE_AZURE_AD_GRUPPEN_OBJEKT_ID“
                          graph_url = f“https://microsoft.com{GROUP_ID}/members“
                          headers = {„Authorization“: f“Bearer {token}“}
                          users_response = requests.get(graph_url, headers=headers).json()
                          azure_users = users_response.get(„value“, [])

                          # Set für schnellen Abgleich erstellen (enthält alle gültigen Logins)
                          active_azure_logins = set()

                          # OTOBO API Konfiguration
                          otobo_base_url = „https://ihr-otobo-system.de“
                          otobo_headers = {„Content-Type“: „application/json“, „X-OTOBO-API-Key“: „IHR_OTOBO_API_KEY“}

                          # 3. Azure AD User anlegen oder aktualisieren
                          for user in azure_users:
                          if user.get(„@odata.type“) == „#microsoft.graph.user“:
                          login = user.get(„userPrincipalName“)
                          active_azure_logins.add(login)

                          customer_data = {
                          „UserLogin“: login,
                          „UserFirstname“: user.get(„givenName“) or „Vorname“,
                          „UserLastname“: user.get(„surname“) or „Nachname“,
                          „UserEmail“: user.get(„mail“) or login,
                          „UserCustomerID“: user.get(„mail“) or login,
                          „ValidID“: 1 # In Azure vorhanden = Aktiv in OTOBO
                          }

                          # Senden an OTOBO (erstellt oder reaktiviert den User)
                          requests.post(otobo_base_url, json=customer_data, headers=otobo_headers)

                          print(f“{len(active_azure_logins)} aktive Benutzer aus Azure AD verarbeitet.“)

                          # 4. Bereinigung: OTOBO-User finden, die NICHT mehr in Azure AD sind
                          search_payload = {„Search“: „*“}
                          search_url = f“{otobo_base_url}Search“

                          otobo_users_response = requests.post(search_url, json=search_payload, headers=otobo_headers)

                          if otobo_users_response.status_code == 200:
                          otobo_users = otobo_users_response.json().get(„CustomerUserList“, {})

                          for otobo_login, otobo_email in otobo_users.items():
                          # Wenn der OTOBO-User nicht in der aktuellen Azure-Gruppe ist: Deaktivieren!
                          if otobo_login not in active_azure_logins:
                          deactivate_data = {
                          „UserLogin“: otobo_login,
                          „ValidID“: 2 # 2 entspricht im OTOBO-Standard „ungültig“
                          }
                          update_url = f“{otobo_base_url}Update“
                          res = requests.post(update_url, json=deactivate_data, headers=otobo_headers)
                          if res.status_code == 200:
                          print(f“Benutzer deaktiviert (nicht mehr in Azure): {otobo_login}“)
                          else:
                          print(„Hinweis: Bestehende OTOBO-Kunden konnten für den Inaktivitäts-Abgleich nicht ausgelesen werden.“)

                          print(„Synchronisations- und Bereinigungszyklus erfolgreich beendet.“)

                          except Exception as e:
                          print(f“Fehler während des Abgleichs: {e}“)

                          # Einmalige Ausführung beim Skriptaufruf
                          if __name__ == „__main__“:
                          azure_to_otobo_sync()

                        • #41852
                          Arnold
                          Administrator

                            Oh da bin ich gespannt. Sollte jemand mal dieses Script einsetzten und GH Repo dazu anlegen, wäre ich an Erfahrungen interessiert.

                          • #41859
                            marcel-graf
                            Teilnehmer

                              Hallo Arnold,

                              hab eben mal etwas probiert. Es wird so aber nicht funktionieren, weil es für die Customer User keine Webservice Operationen gibt.

                              Gruß Marcel

                            • #41879
                              Arnold
                              Administrator

                                Hi Marcel,

                                ich muss gestehen das Skript nicht gelesen zu haben, sondern nahm an, dass du den ursprünglichen Plan verfolgst, die CustomerUser in die Datenbank zu schreiben. Entweder in die OTOBO-Datenbank direkt oder eine externe (die kann man ja auch anbinden).

                              • #41901
                                marcel-graf
                                Teilnehmer

                                  Hallo Arnold,

                                  wir selbst benötigen das ja auch niht, sondern eher der Ersteller.

                                  Du hast recht, wenn man es über eine externe DB (Mysql/MariaDB) realisiert, ist das natürlich einfacher.

                                  Mal schauen, ob ich diese Woche dazu komme.

                                  Gruß Marcel

                                   

                              Ansicht von 13 Antwort-Threads
                              • Du musst angemeldet sein, um auf dieses Thema antworten zu können.