feat: qr-code api, major refactor, objectGUID as user id and group id
Build and Push Docker image / build-and-push (push) Failing after 29s Details

This commit is contained in:
Valentin Kolb 2024-03-27 15:36:45 +01:00
parent 382388eb91
commit d51a92f6e1
16 changed files with 306 additions and 71 deletions

View File

@ -1,5 +1,6 @@
.env.local .env.local
.gitignore .gitignore
.gitlab-ci.yml .gitea
.pb_data
docker-compose.yml docker-compose.yml
Readme.md Readme.md

View File

@ -0,0 +1,32 @@
name: Build and Push Docker image
on:
push:
branches:
- main
jobs:
build-and-push:
runs-on: ubuntu-latest
steps:
- name: Check out the repo
uses: actions/checkout@v2
- name: Log in to StuVe Gitea Container Registry
uses: docker/login-action@v1
with:
registry: git.stuve.uni-ulm.de
username: ${{ secrets.USER_NAME }}
password: ${{ secrets.USER_PASSWORD }}
- name: Build and push Docker image
uses: docker/build-push-action@v2
with:
context: .
file: ./Dockerfile
push: true
tags: git.stuve.uni-ulm.de/StuVe-IT/stuve-it-backend:latest
#- name: Trigger webhook
# run: curl -X POST ${{ secrets.WEBHOOK_URL }}

1
.gitignore vendored
View File

@ -10,6 +10,7 @@ pb_data
# env # env
*.env.local *.env.local
*.env
# macOS # macOS
.DS_Store .DS_Store

View File

@ -1,15 +0,0 @@
stages:
- build
build-container:
stage: build
image:
name: gcr.io/kaniko-project/executor:debug
entrypoint: [ "" ]
script:
- /kaniko/executor
--context "${CI_PROJECT_DIR}"
--dockerfile "${CI_PROJECT_DIR}/Dockerfile"
--destination "${CI_REGISTRY_IMAGE}:${CI_COMMIT_TAG}"
only:
- main

View File

@ -2,6 +2,10 @@
# This is the first stage of a multi-stage build. # This is the first stage of a multi-stage build.
FROM golang:1.19 as builder FROM golang:1.19 as builder
# Set labels
LABEL maintainer="Valentin Kolb"
LABEL version="1.0"
# Set the working directory inside the container. # Set the working directory inside the container.
WORKDIR /build WORKDIR /build

View File

@ -3,7 +3,7 @@
## Übersicht ## Übersicht
Als Backend für die StuVe IT Tools wird [Pocketbase](https://pocketbase.io) Als Backend für die StuVe IT Tools wird [Pocketbase](https://pocketbase.io)
verwendet. Pocketbase ist ein Backend-as-a-Service, der auf SQLite basiert. verwendet. Pocketbase ist ein self-hosted Backend-as-a-Service, der auf SQLite basiert.
## Was ist Pocketbase? ## Was ist Pocketbase?
@ -54,9 +54,9 @@ Funktionen für die einzelnen Tabellen und User erlaubt sind.
## Entwickeln ## Entwickeln
```bash ```bash
git clone ... git clone https://git.stuve.uni-ulm.de/StuVe-IT/stuve-it-backend.git
cd backend cd stuve-it-backend
go run main.go serve # --debug=0 for no debug output go run main/main.go serve # --debug=0 for no debug output
``` ```
## Installation ## Installation
@ -84,8 +84,10 @@ Die einzelnen custom go Packages sind weiter unten beschrieben.
│ ├── models.go # Structs für LDAP User und Group │ ├── models.go # Structs für LDAP User und Group
│ ├── tables.go # Erstellt die benötigten Tabellen in der Datenbank (Users, Groups, Logs) │ ├── tables.go # Erstellt die benötigten Tabellen in der Datenbank (Users, Groups, Logs)
│ └── ldapSync.go # Sync Funktionen (Sync Users, Groups, ...) │ └── ldapSync.go # Sync Funktionen (Sync Users, Groups, ...)
└── ldapLogin # LDAP Login Package ├── ldapLogin # LDAP Login Package
└── main.go # Login Funktionen (Login Route) │ └── main.go # Login Funktionen (Login Route)
└── qrApi # QR API Package
└── main.go # Api Route
``` ```
## Custom Packages ## Custom Packages
@ -98,7 +100,46 @@ wurden einige Packages erstellt, die Pocketbase erweitern.
Das Logger Package ist ein custom Logger, der die Log-Nachrichten Das Logger Package ist ein custom Logger, der die Log-Nachrichten
farblich in die Konsole schreibt. farblich in die Konsole schreibt.
Diese Package ist sehr minimal gehalten und bietet nur die Diese Package ist sehr minimal gehalten und bietet nur die
Funktionen `Info`, `Warn` und `Error` an. Funktionen `Success`, `Info` und `Error` an.
#### Beispiel
```go
package main
import "git.stuve.uni-ulm.de/StuVe-IT/stuve-it-backend/logger"
func main() {
logger.LogInfoF("Info Message")
logger.LogErrorF("Warn Message")
logger.LogErrorF("Error Message")
}
```
### QR API
Das QR API Package ist ein einfaches Package, das eine API Route
zum Generieren von QR-Codes in SVG anbietet.
#### API Route
Das Package bietet die Route `GET /api/qr/v1` an, welche die folgenden
Parameter als json entgegennimmt:
| Parameter | Datentyp | Beschreibung |
|-------------------|-------------|------------------------------------------------------------------------------------------------|
| `data` | string | Der Text, der im QR-Code codiert werden soll (**required**) |
| `ecc` | string-enum | Der Error Correction Level des QR-Codes (`L`, `M` - default, `Q`, `H`) |
| `scale` | int | Der Scale des QR-Codes (default `1`) |
| `border` | int | Der Border des QR-Codes (default `1`), eine Border Uni ist die Größe von einem QR-Code 'pixel' |
| `color` | hex-string | Die Farbe des QR-Codes (default `#000000`) |
| `colorBackground` | hex-string | Die Hintergrundfarbe des QR-Codes (default `#FFFFFF`) |
#### Beispiel
```bash
curl -X GET https://it.stuve.uni-ulm.de/api/qr/v1 -d '{"data": "https://stuve.uni-ulm.de"}'
```
### LDAP Sync ### LDAP Sync
@ -108,6 +149,36 @@ die LDAP Datenbank mit der Pocketbase Datenbank.
Dabei werden alle LDAP User und Gruppen in die Pocketbase Datenbank Dabei werden alle LDAP User und Gruppen in die Pocketbase Datenbank
kopiert. kopiert.
Dieses Package erfordert, dass alle Gruppen und User das Attribut `objectGUID` haben. Dies ist in der Regel bei Windows
ADs der Fall.
#### LDAP Attribute
Die folgenden LDAP Attribute werden in der Pocketbase Datenbank gespeichert.
##### User
| Attribut | Pocketbase Name | Beschreibung |
|----------------|--------------------------------------------|-------------------------------------------------------------------|
| objectGUID | id | Eindeutige ID des Users (nur die ersten 15 Zeichen werden genutzt |
| givenName | givenName | Vorname des Users |
| sn | sn | Nachname des Users |
| mail | email | E-Mail des Users |
| memberOf | memberOf (in Pocketbase eine SQL Relation) | Gruppenzugehörigkeit des Users |
| accountExpires | accountExpires (in Pocketbase UTC) | Account Ablaufdatum |
| dn | dn | Distinguished Name des Users |
| cn | cn & username | Common Name des Users |
##### Group
| Attribut | Pocketbase Name | Beschreibung |
|-------------|--------------------------------------------|--------------------------------------------------------------------|
| objectGUID | id | Eindeutige ID der Gruppe (nur die ersten 15 Zeichen werden genutzt |
| cn | cn | Common Name der Gruppe |
| dn | dn | Distinguished Name der Gruppe |
| description | description | Beschreibung der Gruppe |
| memberOf | memberOf (in Pocketbase eine SQL Relation) | Gruppenzugehörigkeit der Gruppe |
#### Konfiguration #### Konfiguration
Das Package benötigt folgende ENV Variablen: Das Package benötigt folgende ENV Variablen:
@ -147,7 +218,7 @@ noch nicht existiert. In diesem Fall wird die Beziehung erst beim
nächsten Sync erstellt. Falls eine `memberOf` Gruppe nicht gefunden wird, nächsten Sync erstellt. Falls eine `memberOf` Gruppe nicht gefunden wird,
wird dies in der Konsole geloggt. wird dies in der Konsole geloggt.
Für jede Synchronisation wird ein Log in der `ldap_sync_logs` Tabelle Für jede Synchronisation wird ein Log-in der `ldap_sync_logs` Tabelle
erstellt. erstellt.
Sowohl für die ldap_users als auch für die ldap_groups Tabelle wird Sowohl für die ldap_users als auch für die ldap_groups Tabelle wird

13
go.mod
View File

@ -1,9 +1,14 @@
module gitlab.uni-ulm.de/stuve-it/it-tools/backend module git.stuve.uni-ulm.de/StuVe-IT/stuve-it-backend
go 1.21 go 1.21
require ( require (
github.com/fatih/color v1.15.0
github.com/go-ldap/ldap/v3 v3.4.6 github.com/go-ldap/ldap/v3 v3.4.6
github.com/google/uuid v1.3.1
github.com/joho/godotenv v1.5.1
github.com/labstack/echo/v5 v5.0.0-20230722203903-ec5b858dab61
github.com/pocketbase/dbx v1.10.1
github.com/pocketbase/pocketbase v0.19.0 github.com/pocketbase/pocketbase v0.19.0
) )
@ -34,7 +39,6 @@ require (
github.com/disintegration/imaging v1.6.2 // indirect github.com/disintegration/imaging v1.6.2 // indirect
github.com/domodwyer/mailyak/v3 v3.6.2 // indirect github.com/domodwyer/mailyak/v3 v3.6.2 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect github.com/dustin/go-humanize v1.0.1 // indirect
github.com/fatih/color v1.15.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.3 // indirect github.com/gabriel-vasile/mimetype v1.4.3 // indirect
github.com/ganigeorgiev/fexpr v0.3.0 // indirect github.com/ganigeorgiev/fexpr v0.3.0 // indirect
github.com/go-asn1-ber/asn1-ber v1.5.5 // indirect github.com/go-asn1-ber/asn1-ber v1.5.5 // indirect
@ -43,23 +47,20 @@ require (
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.3 // indirect github.com/golang/protobuf v1.5.3 // indirect
github.com/google/uuid v1.3.1 // indirect
github.com/google/wire v0.5.0 // indirect github.com/google/wire v0.5.0 // indirect
github.com/googleapis/gax-go/v2 v2.12.0 // indirect github.com/googleapis/gax-go/v2 v2.12.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/joho/godotenv v1.5.1 // indirect
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
github.com/labstack/echo/v5 v5.0.0-20230722203903-ec5b858dab61 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.19 // indirect github.com/mattn/go-isatty v0.0.19 // indirect
github.com/mattn/go-sqlite3 v1.14.17 // indirect github.com/mattn/go-sqlite3 v1.14.17 // indirect
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
github.com/pocketbase/dbx v1.10.1 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/spf13/cast v1.5.1 // indirect github.com/spf13/cast v1.5.1 // indirect
github.com/spf13/cobra v1.7.0 // indirect github.com/spf13/cobra v1.7.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect github.com/spf13/pflag v1.0.5 // indirect
github.com/stretchr/testify v1.8.4 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasttemplate v1.2.2 // indirect github.com/valyala/fasttemplate v1.2.2 // indirect
go.opencensus.io v0.24.0 // indirect go.opencensus.io v0.24.0 // indirect

4
go.sum
View File

@ -207,8 +207,8 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=

View File

@ -2,13 +2,13 @@ package ldapLogin
import ( import (
"fmt" "fmt"
"git.stuve.uni-ulm.de/StuVe-IT/stuve-it-backend/ldapSync"
"git.stuve.uni-ulm.de/StuVe-IT/stuve-it-backend/logger"
"github.com/go-ldap/ldap/v3" "github.com/go-ldap/ldap/v3"
"github.com/labstack/echo/v5" "github.com/labstack/echo/v5"
"github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/apis" "github.com/pocketbase/pocketbase/apis"
"github.com/pocketbase/pocketbase/core" "github.com/pocketbase/pocketbase/core"
"gitlab.uni-ulm.de/stuve-it/it-tools/backend/ldapSync"
"gitlab.uni-ulm.de/stuve-it/it-tools/backend/logger"
"os" "os"
) )

View File

@ -6,11 +6,11 @@ this file contains functions to sync ldap users and groups to the database
import ( import (
"fmt" "fmt"
"git.stuve.uni-ulm.de/StuVe-IT/stuve-it-backend/logger"
"github.com/pocketbase/dbx" "github.com/pocketbase/dbx"
"github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/forms" "github.com/pocketbase/pocketbase/forms"
"github.com/pocketbase/pocketbase/models" "github.com/pocketbase/pocketbase/models"
"gitlab.uni-ulm.de/stuve-it/it-tools/backend/logger"
) )
// upsertLDAPGroup This function creates / updates a record in the ldap groups table // upsertLDAPGroup This function creates / updates a record in the ldap groups table
@ -27,8 +27,8 @@ func upsertLDAPGroup(app *pocketbase.PocketBase, ldapGroup *LDAPGroup) error {
// if record exists, update it // if record exists, update it
if res, _ := app.Dao().FindFirstRecordByFilter( if res, _ := app.Dao().FindFirstRecordByFilter(
ldapGroupsTableName, ldapGroupsTableName,
"gidNumber = {:gidNumber}", "id = {:id}",
dbx.Params{"gidNumber": ldapGroup.gidNumber}, dbx.Params{"id": ldapGroup.id},
); res != nil { ); res != nil {
record = res record = res
} else { // if record does not exist, create it } else { // if record does not exist, create it
@ -51,7 +51,7 @@ func upsertLDAPGroup(app *pocketbase.PocketBase, ldapGroup *LDAPGroup) error {
// load data // load data
err = form.LoadData(map[string]any{ err = form.LoadData(map[string]any{
"gidNumber": ldapGroup.gidNumber, "id": ldapGroup.id,
"cn": ldapGroup.cn, "cn": ldapGroup.cn,
"dn": ldapGroup.dn, "dn": ldapGroup.dn,
"description": ldapGroup.description, "description": ldapGroup.description,
@ -83,8 +83,8 @@ func upsertLDAPUser(app *pocketbase.PocketBase, ldapUser *LDAPUser) error {
// if record exists, update it // if record exists, update it
if res, _ := app.Dao().FindFirstRecordByFilter( if res, _ := app.Dao().FindFirstRecordByFilter(
ldapUsersTableName, ldapUsersTableName,
"uidNumber = {:uidNumber}", "id = {:id}",
dbx.Params{"uidNumber": ldapUser.uidNumber}, dbx.Params{"id": ldapUser.id},
); res != nil { ); res != nil {
record = res record = res
} else { // if record does not exist, create it } else { // if record does not exist, create it
@ -111,7 +111,7 @@ func upsertLDAPUser(app *pocketbase.PocketBase, ldapUser *LDAPUser) error {
} }
} }
record.Set("uidNumber", ldapUser.uidNumber) record.Set("id", ldapUser.id)
record.Set("givenName", ldapUser.givenName) record.Set("givenName", ldapUser.givenName)
record.Set("sn", ldapUser.sn) record.Set("sn", ldapUser.sn)
record.Set("username", ldapUser.cn) record.Set("username", ldapUser.cn)

View File

@ -3,12 +3,13 @@ package ldapSync
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"git.stuve.uni-ulm.de/StuVe-IT/stuve-it-backend/logger"
"github.com/go-ldap/ldap/v3" "github.com/go-ldap/ldap/v3"
"github.com/google/uuid"
"github.com/pocketbase/dbx" "github.com/pocketbase/dbx"
"github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/forms" "github.com/pocketbase/pocketbase/forms"
"github.com/pocketbase/pocketbase/models" "github.com/pocketbase/pocketbase/models"
"gitlab.uni-ulm.de/stuve-it/it-tools/backend/logger"
"os" "os"
"time" "time"
) )
@ -20,6 +21,21 @@ type SyncResult struct {
Errors []error Errors []error
} }
// getObjectGUID This function gets the objectGUID from an ldap entry
//
// since the objectGUID is a binary value, it is returned as a string (uuid)
func getObjectGUID(entry *ldap.Entry) (string, error) {
// get the objectGUID
var bytes = entry.GetRawAttributeValue("objectGUID")
// convert to uuid
u, err := uuid.FromBytes(bytes)
if err != nil {
return "", err
}
return u.String()[:15], nil // only use the first 15 characters of the uuid
}
// upsertLDAPGroup This function creates / updates a record in the ldap groups table // upsertLDAPGroup This function creates / updates a record in the ldap groups table
// //
// this function expects that the table ldapGroups already exists // this function expects that the table ldapGroups already exists
@ -34,7 +50,7 @@ func syncLdapGroups(app *pocketbase.PocketBase, ldapClient *ldap.Conn) SyncResul
os.Getenv("LDAP_GROUP_BASE_DN"), // The base dn to search os.Getenv("LDAP_GROUP_BASE_DN"), // The base dn to search
ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
os.Getenv("LDAP_GROUP_FILTER"), // The filter to apply os.Getenv("LDAP_GROUP_FILTER"), // The filter to apply
[]string{"gidNumber", "description", "dn", "cn", "msSFU30NisDomain", "memberOf"}, []string{"description", "dn", "cn", "msSFU30NisDomain", "memberOf", "objectGUID"},
nil, nil,
) )
@ -46,8 +62,15 @@ func syncLdapGroups(app *pocketbase.PocketBase, ldapClient *ldap.Conn) SyncResul
} }
for _, entry := range groupsFoundInLdap.Entries { for _, entry := range groupsFoundInLdap.Entries {
var id, e = getObjectGUID(entry)
if e != nil {
errors = append(errors, fmt.Errorf("unable to get objectGUID for group with dn: %s - %s", entry.DN, e))
}
err := upsertLDAPGroup(app, &LDAPGroup{ err := upsertLDAPGroup(app, &LDAPGroup{
gidNumber: entry.GetAttributeValue("gidNumber"), id: id,
description: entry.GetAttributeValue("description"), description: entry.GetAttributeValue("description"),
dn: entry.DN, dn: entry.DN,
cn: entry.GetAttributeValue("cn"), cn: entry.GetAttributeValue("cn"),
@ -115,7 +138,7 @@ func syncLdapUsers(app *pocketbase.PocketBase, ldapClient *ldap.Conn) SyncResult
os.Getenv("LDAP_BASE_DN"), // The base dn to search os.Getenv("LDAP_BASE_DN"), // The base dn to search
ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
os.Getenv("LDAP_USER_FILTER"), // The filter to apply os.Getenv("LDAP_USER_FILTER"), // The filter to apply
[]string{"givenName", "sn", "accountExpires", "uidNumber", "mail", "dn", "cn", "msSFU30NisDomain", "memberOf"}, []string{"givenName", "sn", "accountExpires", "mail", "dn", "cn", "msSFU30NisDomain", "memberOf", "objectGUID"},
nil, nil,
) )
@ -127,11 +150,18 @@ func syncLdapUsers(app *pocketbase.PocketBase, ldapClient *ldap.Conn) SyncResult
} }
for _, entry := range usersFoundInLdap.Entries { for _, entry := range usersFoundInLdap.Entries {
var id, e = getObjectGUID(entry)
if e != nil {
errors = append(errors, fmt.Errorf("unable to get objectGUID for user with dn: %s - %s", entry.DN, e))
}
err := upsertLDAPUser(app, &LDAPUser{ err := upsertLDAPUser(app, &LDAPUser{
givenName: entry.GetAttributeValue("givenName"), givenName: entry.GetAttributeValue("givenName"),
sn: entry.GetAttributeValue("sn"), sn: entry.GetAttributeValue("sn"),
accountExpires: entry.GetAttributeValue("accountExpires"), accountExpires: entry.GetAttributeValue("accountExpires"),
uidNumber: entry.GetAttributeValue("uidNumber"), id: id,
mail: entry.GetAttributeValue("mail"), mail: entry.GetAttributeValue("mail"),
dn: entry.DN, dn: entry.DN,
cn: entry.GetAttributeValue("cn"), cn: entry.GetAttributeValue("cn"),

View File

@ -5,9 +5,9 @@ package ldapSync
import ( import (
"fmt" "fmt"
"git.stuve.uni-ulm.de/StuVe-IT/stuve-it-backend/logger"
"github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/tools/cron" "github.com/pocketbase/pocketbase/tools/cron"
"gitlab.uni-ulm.de/stuve-it/it-tools/backend/logger"
"os" "os"
"strconv" "strconv"
"time" "time"
@ -88,7 +88,7 @@ func InitLdapSync(app *pocketbase.PocketBase) error {
ldapSyncSchedule := os.Getenv("LDAP_SYNC_SCHEDULE") ldapSyncSchedule := os.Getenv("LDAP_SYNC_SCHEDULE")
// syncs ldap every 2 minutes // syncs ldap - interval specified in the LDAP_SYNC_SCHEDULE env variable
scheduler.MustAdd("ldapSync", ldapSyncSchedule, func() { scheduler.MustAdd("ldapSync", ldapSyncSchedule, func() {
logger.LogInfoF("syncing LDAP ...") logger.LogInfoF("syncing LDAP ...")
syncLdap(app) syncLdap(app)

View File

@ -1,10 +1,10 @@
package ldapSync package ldapSync
type LDAPUser struct { type LDAPUser struct {
id string
givenName string givenName string
sn string sn string
accountExpires string accountExpires string
uidNumber string
mail string mail string
dn string dn string
@ -14,7 +14,7 @@ type LDAPUser struct {
} }
type LDAPGroup struct { type LDAPGroup struct {
gidNumber string id string
description string description string
dn string dn string

View File

@ -33,13 +33,6 @@ func createLDAPGroupsTable(app *pocketbase.PocketBase) error {
form.UpdateRule = nil form.UpdateRule = nil
form.DeleteRule = nil form.DeleteRule = nil
// add group ID field
form.Schema.AddField(&schema.SchemaField{
Name: "gidNumber",
Type: schema.FieldTypeText,
Required: true,
})
// add description field // add description field
form.Schema.AddField(&schema.SchemaField{ form.Schema.AddField(&schema.SchemaField{
Name: "description", Name: "description",
@ -64,7 +57,7 @@ func createLDAPGroupsTable(app *pocketbase.PocketBase) error {
// create index on cn // create index on cn
form.Indexes = types.JsonArray[string]{ form.Indexes = types.JsonArray[string]{
"CREATE UNIQUE INDEX idx_ldapGroups ON " + ldapGroupsTableName + " (cn, gidNumber, dn)", "CREATE UNIQUE INDEX idx_ldapGroups ON " + ldapGroupsTableName + " (cn, dn)",
} }
// validate and submit (internally it calls app.Dao().SaveCollection(collection) in a transaction) // validate and submit (internally it calls app.Dao().SaveCollection(collection) in a transaction)
@ -144,13 +137,6 @@ func createLDAPUsersTable(app *pocketbase.PocketBase) error {
Required: true, Required: true,
}) })
// add uidNumber field
form.Schema.AddField(&schema.SchemaField{
Name: "uidNumber",
Type: schema.FieldTypeText,
Required: true,
})
// add surname field // add surname field
form.Schema.AddField(&schema.SchemaField{ form.Schema.AddField(&schema.SchemaField{
Name: "sn", Name: "sn",
@ -185,7 +171,7 @@ func createLDAPUsersTable(app *pocketbase.PocketBase) error {
// create index on username // create index on username
form.Indexes = types.JsonArray[string]{ form.Indexes = types.JsonArray[string]{
"CREATE UNIQUE INDEX idx_ldapUsers ON " + ldapGroupsTableName + " (cn, uidNumber, dn)", "CREATE UNIQUE INDEX idx_ldapUsers ON " + ldapGroupsTableName + " (cn, dn)",
} }
return form.Submit() return form.Submit()

View File

@ -1,19 +1,27 @@
package main package main
import ( import (
"git.stuve.uni-ulm.de/StuVe-IT/stuve-it-backend/ldapLogin"
"git.stuve.uni-ulm.de/StuVe-IT/stuve-it-backend/ldapSync"
"git.stuve.uni-ulm.de/StuVe-IT/stuve-it-backend/logger"
"git.stuve.uni-ulm.de/StuVe-IT/stuve-it-backend/qrApi"
"github.com/joho/godotenv" "github.com/joho/godotenv"
"github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/core" "github.com/pocketbase/pocketbase/core"
"gitlab.uni-ulm.de/stuve-it/it-tools/backend/ldapLogin"
"gitlab.uni-ulm.de/stuve-it/it-tools/backend/ldapSync"
"log" "log"
) )
func main() { func main() {
// load env // load env
godotenv.Load(".env.local") err := godotenv.Load(".env.local")
godotenv.Load(".env") if err != nil {
logger.LogInfoF("The file '.env.local' could not be loaded.")
}
err = godotenv.Load(".env")
if err != nil {
logger.LogInfoF("The file '.env' could not be loaded.")
}
// create app // create app
app := pocketbase.New() app := pocketbase.New()
@ -28,6 +36,11 @@ func main() {
return ldapLogin.InitLDAPLogin(app, e) return ldapLogin.InitLDAPLogin(app, e)
}) })
// setup qr api
app.OnBeforeServe().Add(func(e *core.ServeEvent) error {
return qrApi.InitQRApi(app, e)
})
// start app // start app
if err := app.Start(); err != nil { if err := app.Start(); err != nil {
log.Fatal(err) log.Fatal(err)

111
qrApi/main.go Normal file
View File

@ -0,0 +1,111 @@
package qrApi
import (
"bytes"
goqr "git.stuve.uni-ulm.de/StuVe-IT/stuve-it-backend/go-qr"
"git.stuve.uni-ulm.de/StuVe-IT/stuve-it-backend/logger"
"github.com/labstack/echo/v5"
"github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/apis"
"github.com/pocketbase/pocketbase/core"
"io"
)
// InitQRApi initializes ar code api endpoint
//
// this endpoint can be used to create qr codes
// GET /api/qr/v1
//
// the endpoint expects the following request data:
//
// {
// "data": "what to be encoded",
// "ecc": "error correction level ('L', 'M' (default), 'Q', 'H')",
// "scale": "scale of qr code (default 1), must be greater than 0",
// "border": "border of qr code (default 1), must be equal or greater than 0, one is equal to one qr-code 'pixel'"
// "color": "color of qr code (default #000000)"
// "colorBackground": "background color of qr code (default #FFFFFF)",
// }
//
// if the user is authenticated successfully the endpoint returns and apis.RecordAuthResponse
func InitQRApi(app *pocketbase.PocketBase, e *core.ServeEvent) error {
// add endpoint to app
logger.LogInfoF("Adding QR API Endpoint")
e.Router.GET("/api/qr/v1", func(c echo.Context) error {
// get data from request
data := struct {
Data string `json:"data"`
ECC string `json:"ecc"`
Scale int `json:"scale"`
Border int `json:"border"`
ColorDark string `json:"color"`
ColorLight string `json:"colorBackground"`
}{}
if err := c.Bind(&data); err != nil {
return apis.NewBadRequestError("Failed to read request data", err)
}
// get correct error correction level
var errCorLvl goqr.Ecc
switch data.ECC {
case "L":
errCorLvl = goqr.Low
case "M":
errCorLvl = goqr.Medium
case "Q":
errCorLvl = goqr.Quartile
case "H":
errCorLvl = goqr.High
default:
errCorLvl = goqr.Medium
}
// encode data to qr code
qrCode, err := goqr.EncodeText(data.Data, errCorLvl)
if err != nil {
return apis.NewBadRequestError("Failed to encode data to qr code", err)
}
// set default scale and border
scale := data.Scale
border := data.Border
if scale == 0 {
scale = 1
}
if border == 0 {
border = 1
}
config := goqr.NewQrCodeImgConfig(scale, border)
// create new buffer and writer
var buffer bytes.Buffer
writer := io.Writer(&buffer)
// set default colors
colorLight := data.ColorLight
colorDark := data.ColorDark
if colorLight == "" {
colorLight = "#FFFFFF"
}
if colorDark == "" {
colorDark = "#000000"
}
// write qr code to buffer
err = qrCode.WriteAsSVG(config, writer, colorLight, colorDark)
// return error if writing qr code to buffer fails
if err != nil {
logger.LogErrorF("Failed to write QR Code: %e", err)
return apis.NewBadRequestError("Failed to write QR Code", err)
}
// return qr code as svg
return c.Stream(200, "image/svg+xml", io.Reader(&buffer))
}, apis.ActivityLogger(app))
return nil
}