Compare commits
2 Commits
crates
...
haveibeenp
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8c052c091e | ||
|
|
278fb1630d |
@@ -34,20 +34,12 @@ package.json
|
||||
.vscode
|
||||
.devcontainer
|
||||
|
||||
# Created databases
|
||||
*.db
|
||||
*.db-shm
|
||||
*.db-wal
|
||||
|
||||
# These are backup files generated by rustfmt
|
||||
**/*.rs.bk
|
||||
|
||||
# Various config files that shouldn't be tracked
|
||||
.env
|
||||
lldap_config.toml
|
||||
server_key
|
||||
users.db*
|
||||
screenshot.png
|
||||
recipe.json
|
||||
lldap_config.toml
|
||||
cert.pem
|
||||
key.pem
|
||||
|
||||
2
.github/codecov.yml
vendored
2
.github/codecov.yml
vendored
@@ -10,5 +10,3 @@ ignore:
|
||||
- "docs"
|
||||
- "example_configs"
|
||||
- "migration-tool"
|
||||
- "scripts"
|
||||
- "set-password"
|
||||
|
||||
14
.github/workflows/Dockerfile.ci.alpine
vendored
14
.github/workflows/Dockerfile.ci.alpine
vendored
@@ -11,10 +11,10 @@ RUN mkdir -p /lldap/app
|
||||
|
||||
RUN if [ "${TARGETPLATFORM}" = "linux/amd64" ]; then \
|
||||
mv bin/x86_64-unknown-linux-musl-lldap-bin/lldap target/lldap && \
|
||||
mv bin/x86_64-unknown-linux-musl-lldap_migration_tool-bin/lldap_migration_tool target/lldap_migration_tool && \
|
||||
mv bin/x86_64-unknown-linux-musl-migration-tool-bin/migration-tool target/migration-tool && \
|
||||
mv bin/x86_64-unknown-linux-musl-lldap_set_password-bin/lldap_set_password target/lldap_set_password && \
|
||||
chmod +x target/lldap && \
|
||||
chmod +x target/lldap_migration_tool && \
|
||||
chmod +x target/migration-tool && \
|
||||
chmod +x target/lldap_set_password && \
|
||||
ls -la target/ . && \
|
||||
pwd \
|
||||
@@ -22,10 +22,10 @@ RUN if [ "${TARGETPLATFORM}" = "linux/amd64" ]; then \
|
||||
|
||||
RUN if [ "${TARGETPLATFORM}" = "linux/arm64" ]; then \
|
||||
mv bin/aarch64-unknown-linux-musl-lldap-bin/lldap target/lldap && \
|
||||
mv bin/aarch64-unknown-linux-musl-lldap_migration_tool-bin/lldap_migration_tool target/lldap_migration_tool && \
|
||||
mv bin/aarch64-unknown-linux-musl-migration-tool-bin/migration-tool target/migration-tool && \
|
||||
mv bin/aarch64-unknown-linux-musl-lldap_set_password-bin/lldap_set_password target/lldap_set_password && \
|
||||
chmod +x target/lldap && \
|
||||
chmod +x target/lldap_migration_tool && \
|
||||
chmod +x target/migration-tool && \
|
||||
chmod +x target/lldap_set_password && \
|
||||
ls -la target/ . && \
|
||||
pwd \
|
||||
@@ -33,10 +33,10 @@ RUN if [ "${TARGETPLATFORM}" = "linux/arm64" ]; then \
|
||||
|
||||
RUN if [ "${TARGETPLATFORM}" = "linux/arm/v7" ]; then \
|
||||
mv bin/armv7-unknown-linux-gnueabihf-lldap-bin/lldap target/lldap && \
|
||||
mv bin/armv7-unknown-linux-gnueabihf-lldap_migration_tool-bin/lldap_migration_tool target/lldap_migration_tool && \
|
||||
mv bin/armv7-unknown-linux-gnueabihf-migration-tool-bin/migration-tool target/migration-tool && \
|
||||
mv bin/armv7-unknown-linux-gnueabihf-lldap_set_password-bin/lldap_set_password target/lldap_set_password && \
|
||||
chmod +x target/lldap && \
|
||||
chmod +x target/lldap_migration_tool && \
|
||||
chmod +x target/migration-tool && \
|
||||
chmod +x target/lldap_set_password && \
|
||||
ls -la target/ . && \
|
||||
pwd \
|
||||
@@ -47,7 +47,7 @@ COPY docker-entrypoint.sh /docker-entrypoint.sh
|
||||
COPY lldap_config.docker_template.toml /lldap/
|
||||
COPY web/index_local.html web/index.html
|
||||
RUN cp target/lldap /lldap/ && \
|
||||
cp target/lldap_migration_tool /lldap/ && \
|
||||
cp target/migration-tool /lldap/ && \
|
||||
cp target/lldap_set_password /lldap/ && \
|
||||
cp -R web/index.html \
|
||||
web/pkg \
|
||||
|
||||
14
.github/workflows/Dockerfile.ci.debian
vendored
14
.github/workflows/Dockerfile.ci.debian
vendored
@@ -11,10 +11,10 @@ RUN mkdir -p /lldap/app
|
||||
|
||||
RUN if [ "${TARGETPLATFORM}" = "linux/amd64" ]; then \
|
||||
mv bin/x86_64-unknown-linux-musl-lldap-bin/lldap target/lldap && \
|
||||
mv bin/x86_64-unknown-linux-musl-lldap_migration_tool-bin/lldap_migration_tool target/lldap_migration_tool && \
|
||||
mv bin/x86_64-unknown-linux-musl-migration-tool-bin/migration-tool target/migration-tool && \
|
||||
mv bin/x86_64-unknown-linux-musl-lldap_set_password-bin/lldap_set_password target/lldap_set_password && \
|
||||
chmod +x target/lldap && \
|
||||
chmod +x target/lldap_migration_tool && \
|
||||
chmod +x target/migration-tool && \
|
||||
chmod +x target/lldap_set_password && \
|
||||
ls -la target/ . && \
|
||||
pwd \
|
||||
@@ -22,10 +22,10 @@ RUN if [ "${TARGETPLATFORM}" = "linux/amd64" ]; then \
|
||||
|
||||
RUN if [ "${TARGETPLATFORM}" = "linux/arm64" ]; then \
|
||||
mv bin/aarch64-unknown-linux-musl-lldap-bin/lldap target/lldap && \
|
||||
mv bin/aarch64-unknown-linux-musl-lldap_migration_tool-bin/lldap_migration_tool target/lldap_migration_tool && \
|
||||
mv bin/aarch64-unknown-linux-musl-migration-tool-bin/migration-tool target/migration-tool && \
|
||||
mv bin/aarch64-unknown-linux-musl-lldap_set_password-bin/lldap_set_password target/lldap_set_password && \
|
||||
chmod +x target/lldap && \
|
||||
chmod +x target/lldap_migration_tool && \
|
||||
chmod +x target/migration-tool && \
|
||||
chmod +x target/lldap_set_password && \
|
||||
ls -la target/ . && \
|
||||
pwd \
|
||||
@@ -33,10 +33,10 @@ RUN if [ "${TARGETPLATFORM}" = "linux/arm64" ]; then \
|
||||
|
||||
RUN if [ "${TARGETPLATFORM}" = "linux/arm/v7" ]; then \
|
||||
mv bin/armv7-unknown-linux-gnueabihf-lldap-bin/lldap target/lldap && \
|
||||
mv bin/armv7-unknown-linux-gnueabihf-lldap_migration_tool-bin/lldap_migration_tool target/lldap_migration_tool && \
|
||||
mv bin/armv7-unknown-linux-gnueabihf-migration-tool-bin/migration-tool target/migration-tool && \
|
||||
mv bin/armv7-unknown-linux-gnueabihf-lldap_set_password-bin/lldap_set_password target/lldap_set_password && \
|
||||
chmod +x target/lldap && \
|
||||
chmod +x target/lldap_migration_tool && \
|
||||
chmod +x target/migration-tool && \
|
||||
chmod +x target/lldap_set_password && \
|
||||
ls -la target/ . && \
|
||||
pwd \
|
||||
@@ -47,7 +47,7 @@ COPY docker-entrypoint.sh /docker-entrypoint.sh
|
||||
COPY lldap_config.docker_template.toml /lldap/
|
||||
COPY web/index_local.html web/index.html
|
||||
RUN cp target/lldap /lldap/ && \
|
||||
cp target/lldap_migration_tool /lldap/ && \
|
||||
cp target/migration-tool /lldap/ && \
|
||||
cp target/lldap_set_password /lldap/ && \
|
||||
cp -R web/index.html \
|
||||
web/pkg \
|
||||
|
||||
223
.github/workflows/docker-build-static.yml
vendored
223
.github/workflows/docker-build-static.yml
vendored
@@ -84,12 +84,12 @@ jobs:
|
||||
build-ui:
|
||||
runs-on: ubuntu-latest
|
||||
needs: pre_job
|
||||
if: ${{ needs.pre_job.outputs.should_skip != 'true' || github.event_name == 'release' }}
|
||||
if: ${{ needs.pre_job.outputs.should_skip != 'true' }}
|
||||
container:
|
||||
image: nitnelave/rust-dev:latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3.5.2
|
||||
uses: actions/checkout@v3.5.0
|
||||
- uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
@@ -123,7 +123,7 @@ jobs:
|
||||
build-bin:
|
||||
runs-on: ubuntu-latest
|
||||
needs: pre_job
|
||||
if: ${{ needs.pre_job.outputs.should_skip != 'true' || github.event_name == 'release' }}
|
||||
if: ${{ needs.pre_job.outputs.should_skip != 'true' }}
|
||||
strategy:
|
||||
matrix:
|
||||
target: [armv7-unknown-linux-gnueabihf, aarch64-unknown-linux-musl, x86_64-unknown-linux-musl]
|
||||
@@ -138,7 +138,7 @@ jobs:
|
||||
CARGO_HOME: ${GITHUB_WORKSPACE}/.cargo
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3.5.2
|
||||
uses: actions/checkout@v3.5.0
|
||||
- uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
@@ -151,7 +151,7 @@ jobs:
|
||||
restore-keys: |
|
||||
lldap-bin-${{ matrix.target }}-
|
||||
- name: Compile ${{ matrix.target }} lldap and tools
|
||||
run: cargo build --target=${{ matrix.target }} --release -p lldap -p lldap_migration_tool -p lldap_set_password
|
||||
run: cargo build --target=${{ matrix.target }} --release -p lldap -p migration-tool -p lldap_set_password
|
||||
- name: Check path
|
||||
run: ls -al target/release
|
||||
- name: Upload ${{ matrix.target}} lldap artifacts
|
||||
@@ -162,8 +162,8 @@ jobs:
|
||||
- name: Upload ${{ matrix.target }} migration tool artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: ${{ matrix.target }}-lldap_migration_tool-bin
|
||||
path: target/${{ matrix.target }}/release/lldap_migration_tool
|
||||
name: ${{ matrix.target }}-migration-tool-bin
|
||||
path: target/${{ matrix.target }}/release/migration-tool
|
||||
- name: Upload ${{ matrix.target }} password tool artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
@@ -180,13 +180,11 @@ jobs:
|
||||
ports:
|
||||
- 3306:3306
|
||||
env:
|
||||
MARIADB_USER: lldapuser
|
||||
MARIADB_PASSWORD: lldappass
|
||||
MARIADB_DATABASE: lldap
|
||||
MARIADB_ALLOW_EMPTY_ROOT_PASSWORD: 1
|
||||
options: >-
|
||||
--name mariadb
|
||||
--health-cmd="mysqladmin ping" --health-interval=5s --health-timeout=2s --health-retries=3
|
||||
MYSQL_USER: lldapuser
|
||||
MYSQL_PASSWORD: lldappass
|
||||
MYSQL_DATABASE: lldap
|
||||
MYSQL_ROOT_PASSWORD: rootpass
|
||||
options: --name mariadb
|
||||
|
||||
postgresql:
|
||||
image: postgres:latest
|
||||
@@ -196,12 +194,7 @@ jobs:
|
||||
POSTGRES_USER: lldapuser
|
||||
POSTGRES_PASSWORD: lldappass
|
||||
POSTGRES_DB: lldap
|
||||
options: >-
|
||||
--health-cmd pg_isready
|
||||
--health-interval 10s
|
||||
--health-timeout 5s
|
||||
--health-retries 5
|
||||
--name postgresql
|
||||
options: --name postgresql
|
||||
|
||||
steps:
|
||||
- name: Download artifacts
|
||||
@@ -263,27 +256,17 @@ jobs:
|
||||
POSTGRES_USER: lldapuser
|
||||
POSTGRES_PASSWORD: lldappass
|
||||
POSTGRES_DB: lldap
|
||||
options: >-
|
||||
--health-cmd pg_isready
|
||||
--health-interval 10s
|
||||
--health-timeout 5s
|
||||
--health-retries 5
|
||||
--name postgresql
|
||||
|
||||
options: --name postgresql
|
||||
mariadb:
|
||||
image: mariadb:latest
|
||||
ports:
|
||||
- 3306:3306
|
||||
env:
|
||||
MARIADB_USER: lldapuser
|
||||
MARIADB_PASSWORD: lldappass
|
||||
MARIADB_DATABASE: lldap
|
||||
MARIADB_ALLOW_EMPTY_ROOT_PASSWORD: 1
|
||||
options: >-
|
||||
--name mariadb
|
||||
--health-cmd="mysqladmin ping" --health-interval=5s --health-timeout=2s --health-retries=3
|
||||
|
||||
|
||||
MYSQL_USER: lldapuser
|
||||
MYSQL_PASSWORD: lldappass
|
||||
MYSQL_DATABASE: lldap
|
||||
MYSQL_ROOT_PASSWORD: rootpass
|
||||
options: --name mariadb
|
||||
mysql:
|
||||
image: mysql:latest
|
||||
ports:
|
||||
@@ -292,10 +275,8 @@ jobs:
|
||||
MYSQL_USER: lldapuser
|
||||
MYSQL_PASSWORD: lldappass
|
||||
MYSQL_DATABASE: lldap
|
||||
MYSQL_ALLOW_EMPTY_PASSWORD: 1
|
||||
options: >-
|
||||
--name mysql
|
||||
--health-cmd="mysqladmin ping" --health-interval=5s --health-timeout=2s --health-retries=3
|
||||
MYSQL_ROOT_PASSWORD: rootpass
|
||||
options: --name mysql
|
||||
|
||||
|
||||
steps:
|
||||
@@ -333,7 +314,7 @@ jobs:
|
||||
|
||||
- name: Create dummy user
|
||||
run: |
|
||||
TOKEN=$(curl -X POST -H "Content-Type: application/json" -d '{"username": "admin", "password": "ldappass"}' http://localhost:17170/auth/simple/login | jq -r .token)
|
||||
TOKEN=$(curl -X POST -H "Content-Type: application/json" -d '{"username": "admin", "password": "ldappass"}' http://localhost:17170/auth/simple/login | cut -c 11-277)
|
||||
echo "$TOKEN"
|
||||
curl 'http://localhost:17170/api/graphql' -H 'Content-Type: application/json' -H "Authorization: Bearer ${TOKEN//[$'\t\r\n ']}" --data-binary '{"query":"mutation{\n createUser(user:\n {\n id: \"dummyuser\",\n email: \"dummyuser@example.com\"\n }\n )\n {\n id\n email\n }\n}\n\n\n"}' --compressed
|
||||
bin/lldap_set_password --base-url http://localhost:17170 --admin-username admin --admin-password ldappass --token $TOKEN --username dummyuser --password dummypassword
|
||||
@@ -347,7 +328,7 @@ jobs:
|
||||
|
||||
- name: Export and Converting to Postgress
|
||||
run: |
|
||||
curl -L https://raw.githubusercontent.com/lldap/lldap/main/scripts/sqlite_dump_commands.sh -o helper.sh
|
||||
curl -L https://raw.githubusercontent.com/nitnelave/lldap/main/scripts/sqlite_dump_commands.sh -o helper.sh
|
||||
chmod +x ./helper.sh
|
||||
./helper.sh | sqlite3 ./users.db > ./dump.sql
|
||||
sed -i -r -e "s/X'([[:xdigit:]]+'[^'])/'\\\x\\1/g" -e '1s/^/BEGIN;\n/' -e '$aCOMMIT;' ./dump.sql
|
||||
@@ -365,7 +346,7 @@ jobs:
|
||||
|
||||
- name: Export and Converting to mariadb
|
||||
run: |
|
||||
curl -L https://raw.githubusercontent.com/lldap/lldap/main/scripts/sqlite_dump_commands.sh -o helper.sh
|
||||
curl -L https://raw.githubusercontent.com/nitnelave/lldap/main/scripts/sqlite_dump_commands.sh -o helper.sh
|
||||
chmod +x ./helper.sh
|
||||
./helper.sh | sqlite3 ./users.db > ./dump.sql
|
||||
cp ./dump.sql ./dump-no-sed.sql
|
||||
@@ -384,7 +365,7 @@ jobs:
|
||||
|
||||
- name: Export and Converting to mysql
|
||||
run: |
|
||||
curl -L https://raw.githubusercontent.com/lldap/lldap/main/scripts/sqlite_dump_commands.sh -o helper.sh
|
||||
curl -L https://raw.githubusercontent.com/nitnelave/lldap/main/scripts/sqlite_dump_commands.sh -o helper.sh
|
||||
chmod +x ./helper.sh
|
||||
./helper.sh | sqlite3 ./users.db > ./dump.sql
|
||||
sed -i -r -e 's/^INSERT INTO "?([a-zA-Z0-9_]+)"?/INSERT INTO `\1`/' -e '1s/^/START TRANSACTION;\n/' -e '$aCOMMIT;' ./dump.sql
|
||||
@@ -444,40 +425,12 @@ jobs:
|
||||
needs: [build-ui, build-bin]
|
||||
name: Build Docker image
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
container: ["debian","alpine"]
|
||||
include:
|
||||
- container: alpine
|
||||
platforms: linux/amd64,linux/arm64
|
||||
tags: |
|
||||
type=ref,event=pr
|
||||
type=semver,pattern=v{{version}}
|
||||
type=semver,pattern=v{{major}}
|
||||
type=semver,pattern=v{{major}}.{{minor}}
|
||||
type=semver,pattern=v{{version}},suffix=
|
||||
type=semver,pattern=v{{major}},suffix=
|
||||
type=semver,pattern=v{{major}}.{{minor}},suffix=
|
||||
type=raw,value=latest,enable={{ is_default_branch }}
|
||||
type=raw,value=stable,enable=${{ startsWith(github.ref, 'refs/tags/v') }}
|
||||
type=raw,value=stable,enable=${{ startsWith(github.ref, 'refs/tags/v') }},suffix=
|
||||
type=raw,value=latest,enable={{ is_default_branch }},suffix=
|
||||
- container: debian
|
||||
platforms: linux/amd64,linux/arm64,linux/arm/v7
|
||||
tags: |
|
||||
type=ref,event=pr
|
||||
type=semver,pattern=v{{version}}
|
||||
type=semver,pattern=v{{major}}
|
||||
type=semver,pattern=v{{major}}.{{minor}}
|
||||
type=raw,value=latest,enable={{ is_default_branch }}
|
||||
type=raw,value=stable,enable=${{ startsWith(github.ref, 'refs/tags/v') }}
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3.5.2
|
||||
uses: actions/checkout@v3.5.0
|
||||
- name: Download all artifacts
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
@@ -493,66 +446,86 @@ jobs:
|
||||
uses: docker/setup-qemu-action@v2
|
||||
- uses: docker/setup-buildx-action@v2
|
||||
|
||||
- name: Docker ${{ matrix.container }} meta
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v4
|
||||
with:
|
||||
# list of Docker images to use as base name for tags
|
||||
images: |
|
||||
nitnelave/lldap
|
||||
lldap/lldap
|
||||
ghcr.io/lldap/lldap
|
||||
# Wanted Docker tags
|
||||
# vX-alpine
|
||||
# vX.Y-alpine
|
||||
# vX.Y.Z-alpine
|
||||
# latest
|
||||
# latest-alpine
|
||||
# stable
|
||||
# stable-alpine
|
||||
#################
|
||||
# vX-debian
|
||||
# vX.Y-debian
|
||||
# vX.Y.Z-debian
|
||||
# latest-debian
|
||||
# stable-debian
|
||||
#################
|
||||
# Check matrix for tag list definition
|
||||
flavor: |
|
||||
latest=false
|
||||
suffix=-${{ matrix.container }}
|
||||
tags: ${{ matrix.tags }}
|
||||
# generate Docker tags based on the following events/attributes
|
||||
tags: |
|
||||
type=ref,event=branch
|
||||
type=ref,event=pr
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
type=semver,pattern={{major}}
|
||||
type=sha
|
||||
|
||||
# Docker login to nitnelave/lldap and lldap/lldap
|
||||
- name: Login to Nitnelave/LLDAP Docker Hub
|
||||
- name: parse tag
|
||||
uses: gacts/github-slug@v1
|
||||
id: slug
|
||||
|
||||
- name: Login to Docker Hub
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: nitnelave
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
|
||||
########################################
|
||||
#### docker image build ####
|
||||
#### docker image :latest tag build ####
|
||||
########################################
|
||||
- name: Build ${{ matrix.container }} Docker Image
|
||||
- name: Build and push latest alpine
|
||||
if: github.event_name != 'release'
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: .
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
platforms: ${{ matrix.platforms }}
|
||||
file: ./.github/workflows/Dockerfile.ci.${{ matrix.container }}
|
||||
tags: |
|
||||
${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
platforms: linux/amd64,linux/arm64
|
||||
file: ./.github/workflows/Dockerfile.ci.alpine
|
||||
tags: nitnelave/lldap:latest, nitnelave/lldap:latest-alpine
|
||||
cache-from: type=gha,mode=max
|
||||
cache-to: type=gha,mode=max
|
||||
|
||||
- name: Build and push latest debian
|
||||
if: github.event_name != 'release'
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: .
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
platforms: linux/amd64,linux/arm64,linux/arm/v7
|
||||
file: ./.github/workflows/Dockerfile.ci.debian
|
||||
tags: nitnelave/lldap:latest-debian
|
||||
cache-from: type=gha,mode=max
|
||||
cache-to: type=gha,mode=max
|
||||
|
||||
########################################
|
||||
#### docker image :semver tag build ####
|
||||
########################################
|
||||
- name: Build and push release alpine
|
||||
if: github.event_name == 'release'
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
# Tag as latest, stable, semver, major, major.minor and major.minor.patch.
|
||||
file: ./.github/workflows/Dockerfile.ci.alpine
|
||||
tags: nitnelave/lldap:stable, nitnelave/lldap:stable-alpine, nitnelave/lldap:v${{ steps.slug.outputs.version-semantic }}, nitnelave/lldap:v${{ steps.slug.outputs.version-major }}, nitnelave/lldap:v${{ steps.slug.outputs.version-major }}.${{ steps.slug.outputs.version-minor }}, nitnelave/lldap:v${{ steps.slug.outputs.version-major }}.${{ steps.slug.outputs.version-minor }}.${{ steps.slug.outputs.version-patch }}, nitnelave/lldap:v${{ steps.slug.outputs.version-semantic }}-alpine, nitnelave/lldap:v${{ steps.slug.outputs.version-major }}-alpine, nitnelave/lldap:v${{ steps.slug.outputs.version-major }}-alpine.${{ steps.slug.outputs.version-minor }}-alpine, nitnelave/lldap:v${{ steps.slug.outputs.version-major }}.${{ steps.slug.outputs.version-minor }}.${{ steps.slug.outputs.version-patch }}-alpine
|
||||
cache-from: type=gha,mode=max
|
||||
cache-to: type=gha,mode=max
|
||||
|
||||
- name: Build and push release debian
|
||||
if: github.event_name == 'release'
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64,linux/arm64,linux/arm/v7
|
||||
push: true
|
||||
# Tag as latest, stable, semver, major, major.minor and major.minor.patch.
|
||||
file: ./.github/workflows/Dockerfile.ci.debian
|
||||
tags: nitnelave/lldap:stable-debian, nitnelave/lldap:v${{ steps.slug.outputs.version-semantic }}-debian, nitnelave/lldap:v${{ steps.slug.outputs.version-major }}-debian, nitnelave/lldap:v${{ steps.slug.outputs.version-major }}.${{ steps.slug.outputs.version-minor }}-debian, nitnelave/lldap:v${{ steps.slug.outputs.version-major }}.${{ steps.slug.outputs.version-minor }}.${{ steps.slug.outputs.version-patch }}-debian
|
||||
cache-from: type=gha,mode=max
|
||||
cache-to: type=gha,mode=max
|
||||
|
||||
@@ -564,14 +537,6 @@ jobs:
|
||||
password: ${{ secrets.DOCKERHUB_PASSWORD }}
|
||||
repository: nitnelave/lldap
|
||||
|
||||
- name: Update lldap repo description
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: peter-evans/dockerhub-description@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_PASSWORD }}
|
||||
repository: lldap/lldap
|
||||
|
||||
###############################################################
|
||||
### Download artifacts, clean up ui, upload to release page ###
|
||||
###############################################################
|
||||
@@ -594,14 +559,14 @@ jobs:
|
||||
mv bin/aarch64-unknown-linux-musl-lldap-bin/lldap bin/aarch64-lldap
|
||||
mv bin/x86_64-unknown-linux-musl-lldap-bin/lldap bin/amd64-lldap
|
||||
mv bin/armv7-unknown-linux-gnueabihf-lldap-bin/lldap bin/armhf-lldap
|
||||
mv bin/aarch64-unknown-linux-musl-lldap_migration_tool-bin/lldap_migration_tool bin/aarch64-lldap_migration_tool
|
||||
mv bin/x86_64-unknown-linux-musl-lldap_migration_tool-bin/lldap_migration_tool bin/amd64-lldap_migration_tool
|
||||
mv bin/armv7-unknown-linux-gnueabihf-lldap_migration_tool-bin/lldap_migration_tool bin/armhf-lldap_migration_tool
|
||||
mv bin/aarch64-unknown-linux-musl-migration-tool-bin/migration-tool bin/aarch64-migration-tool
|
||||
mv bin/x86_64-unknown-linux-musl-migration-tool-bin/migration-tool bin/amd64-migration-tool
|
||||
mv bin/armv7-unknown-linux-gnueabihf-migration-tool-bin/migration-tool bin/armhf-migration-tool
|
||||
mv bin/aarch64-unknown-linux-musl-lldap_set_password-bin/lldap_set_password bin/aarch64-lldap_set_password
|
||||
mv bin/x86_64-unknown-linux-musl-lldap_set_password-bin/lldap_set_password bin/amd64-lldap_set_password
|
||||
mv bin/armv7-unknown-linux-gnueabihf-lldap_set_password-bin/lldap_set_password bin/armhf-lldap_set_password
|
||||
chmod +x bin/*-lldap
|
||||
chmod +x bin/*-lldap_migration_tool
|
||||
chmod +x bin/*-migration-tool
|
||||
chmod +x bin/*-lldap_set_password
|
||||
|
||||
- name: Download llap ui artifacts
|
||||
@@ -627,9 +592,9 @@ jobs:
|
||||
mv bin/aarch64-lldap aarch64-lldap/lldap
|
||||
mv bin/amd64-lldap amd64-lldap/lldap
|
||||
mv bin/armhf-lldap armhf-lldap/lldap
|
||||
mv bin/aarch64-lldap_migration_tool aarch64-lldap/lldap_migration_tool
|
||||
mv bin/amd64-lldap_migration_tool amd64-lldap/lldap_migration_tool
|
||||
mv bin/armhf-lldap_migration_tool armhf-lldap/lldap_migration_tool
|
||||
mv bin/aarch64-migration-tool aarch64-lldap/migration-tool
|
||||
mv bin/amd64-migration-tool amd64-lldap/migration-tool
|
||||
mv bin/armhf-migration-tool armhf-lldap/migration-tool
|
||||
mv bin/aarch64-lldap_set_password aarch64-lldap/lldap_set_password
|
||||
mv bin/amd64-lldap_set_password amd64-lldap/lldap_set_password
|
||||
mv bin/armhf-lldap_set_password armhf-lldap/lldap_set_password
|
||||
|
||||
40
.github/workflows/rust.yml
vendored
40
.github/workflows/rust.yml
vendored
@@ -33,7 +33,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v3.5.2
|
||||
uses: actions/checkout@v3.5.0
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
- name: Build
|
||||
run: cargo build --verbose --workspace
|
||||
@@ -52,7 +52,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v3.5.2
|
||||
uses: actions/checkout@v3.5.0
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
|
||||
@@ -69,7 +69,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v3.5.2
|
||||
uses: actions/checkout@v3.5.0
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
|
||||
@@ -86,7 +86,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v3.5.2
|
||||
uses: actions/checkout@v3.5.0
|
||||
|
||||
- name: Install Rust
|
||||
run: rustup toolchain install nightly --component llvm-tools-preview && rustup component add llvm-tools-preview --toolchain stable-x86_64-unknown-linux-gnu
|
||||
@@ -101,39 +101,7 @@ jobs:
|
||||
run: cargo llvm-cov --no-run --lcov --output-path lcov.info
|
||||
- name: Upload coverage to Codecov
|
||||
uses: codecov/codecov-action@v3
|
||||
if: github.ref != 'refs/heads/main' || github.event_name != 'push'
|
||||
with:
|
||||
files: lcov.info
|
||||
fail_ci_if_error: true
|
||||
- name: Upload coverage to Codecov (main)
|
||||
uses: codecov/codecov-action@v3
|
||||
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
|
||||
with:
|
||||
files: lcov.info
|
||||
fail_ci_if_error: true
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
||||
publish-crates:
|
||||
name: Publish on crates.io
|
||||
if: ${{ needs.pre_job.outputs.should_skip != 'true' || (github.event_name == 'push' && github.ref == 'refs/heads/main') || github.event_name == 'release' }}
|
||||
needs: pre_job
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
target: [lldap_auth, lldap, lldap_app, lldap_set_password, lldap_migration_tool]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3.5.2
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
override: true
|
||||
- name: Publish ${{ matrix.target }} crate
|
||||
uses: katyo/publish-crates@v2
|
||||
with:
|
||||
args: -p ${{ matrix.target }}
|
||||
dry-run: ${{ github.event_name != 'release' }}
|
||||
check-repo: ${{ github.event_name != 'pull_request' }}
|
||||
registry-token: ${{ secrets.CARGO_REGISTRY_TOKEN }}
|
||||
ignore-unpublished-changes: ${{ github.event_name != 'release' }}
|
||||
|
||||
|
||||
29
CHANGELOG.md
29
CHANGELOG.md
@@ -5,35 +5,6 @@ All notable changes to this project will be documented in this file.
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [0.4.3] 2023-04-11
|
||||
|
||||
The repository has changed from `nitnelave/lldap` to `lldap/lldap`, both on GitHub
|
||||
and on DockerHub (although we will keep publishing the images to
|
||||
`nitnelave/lldap` for the foreseeable future). All data on GitHub has been
|
||||
migrated, and the new docker images are available both on DockerHub and on the
|
||||
GHCR under `lldap/lldap`.
|
||||
|
||||
### Added
|
||||
|
||||
- EC private keys are not supported for LDAPS.
|
||||
|
||||
### Changed
|
||||
|
||||
- SMTP user no longer has a default value (and instead defaults to unauthenticated).
|
||||
|
||||
### Fixed
|
||||
|
||||
- WASM payload is now delivered uncompressed to Safari due to a Safari bug.
|
||||
- Password reset no longer redirects to login page.
|
||||
- NextCloud config should add the "mail" attribute.
|
||||
- GraphQL parameters are now urldecoded, to support special characters in usernames.
|
||||
- Healthcheck correctly checks the server certificate.
|
||||
|
||||
### New services
|
||||
|
||||
- Home Assistant
|
||||
- Shaarli
|
||||
|
||||
## [0.4.2] - 2023-03-27
|
||||
|
||||
### Added
|
||||
|
||||
381
Cargo.lock
generated
381
Cargo.lock
generated
@@ -118,7 +118,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "465a6172cf69b960917811022d8f29bc0b7fa1398bc4f78b3c466673db1213b6"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -189,7 +189,7 @@ dependencies = [
|
||||
"pin-project-lite",
|
||||
"tokio-rustls",
|
||||
"tokio-util",
|
||||
"webpki-roots 0.22.6",
|
||||
"webpki-roots",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -252,7 +252,7 @@ dependencies = [
|
||||
"actix-router",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -278,7 +278,7 @@ checksum = "6d44b8fee1ced9671ba043476deddef739dd0959bf77030b26b738cc591737a7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -400,7 +400,7 @@ checksum = "726535892e8eae7e70657b4c8ea93d26b8553afb1ce617caee529ef96d7dee6c"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
"syn",
|
||||
"synstructure",
|
||||
]
|
||||
|
||||
@@ -412,21 +412,7 @@ checksum = "2777730b2039ac0f95f093556e61b6d26cebed5393ca6f152717777cec3a42ed"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "assert_cmd"
|
||||
version = "2.0.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9834fcc22e0874394a010230586367d4a3e9f11b560f469262678547e1d2575e"
|
||||
dependencies = [
|
||||
"bstr",
|
||||
"doc-comment",
|
||||
"predicates",
|
||||
"predicates-core",
|
||||
"predicates-tree",
|
||||
"wait-timeout",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -448,7 +434,7 @@ checksum = "e4655ae1a7b0cdf149156f780c5bf3f1352bc53cbd9e0a361a7ef7b22947e965"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -459,7 +445,7 @@ checksum = "1cd7fce9ba8c3c042128ce72d8b2ddbf3a05747efb67ea0313c635e10bda47a2"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -511,7 +497,7 @@ dependencies = [
|
||||
"proc-macro-error",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -620,18 +606,6 @@ dependencies = [
|
||||
"uuid 0.8.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bstr"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c3d4260bcc2e8fc9df1eac4919a720effeb63a3f0952f5bf4944adfa18897f09"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"once_cell",
|
||||
"regex-automata",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.12.0"
|
||||
@@ -721,7 +695,7 @@ dependencies = [
|
||||
"proc-macro-error",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -984,7 +958,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"scratch",
|
||||
"syn 1.0.109",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1001,7 +975,7 @@ checksum = "086c685979a698443656e5cf7856c95c642295a38599f12fb1ff76fb28d19892"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1025,7 +999,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"strsim",
|
||||
"syn 1.0.109",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1036,20 +1010,7 @@ checksum = "b36230598a2d5de7ec1c6f51f72d8a99a9208daff41de2084d06e3fd3ea56685"
|
||||
dependencies = [
|
||||
"darling_core",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dashmap"
|
||||
version = "5.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "907076dfda823b0b36d2a1bb5f90c96660a5bbcd7729e10727f07858f22c4edc"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"hashbrown 0.12.3",
|
||||
"lock_api",
|
||||
"once_cell",
|
||||
"parking_lot_core 0.9.7",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1101,7 +1062,7 @@ dependencies = [
|
||||
"darling",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1111,7 +1072,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ebcda35c7a396850a55ffeac740804b40ffec779b98fffbb1738f4033f0ee79e"
|
||||
dependencies = [
|
||||
"derive_builder_core",
|
||||
"syn 1.0.109",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1124,7 +1085,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"rustc_version",
|
||||
"syn 1.0.109",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1135,7 +1096,7 @@ checksum = "532b4c15dccee12c7044f1fcad956e98410860b22231e44a3b827464797ca7bf"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1192,7 +1153,7 @@ checksum = "adc2ab4d5a16117f9029e9a6b5e4e79f4c67f6519bc134210d4d4a04ba31f41b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1203,15 +1164,9 @@ checksum = "3bf95dc3f046b9da4f2d51833c0d3547d8564ef6910f5c1ed130306a75b92886"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "doc-comment"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10"
|
||||
|
||||
[[package]]
|
||||
name = "dotenvy"
|
||||
version = "0.15.6"
|
||||
@@ -1300,7 +1255,7 @@ checksum = "aa4da3c766cd7a0db8242e326e9e4e081edd567072893ed320008189715366a4"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
"syn",
|
||||
"synstructure",
|
||||
]
|
||||
|
||||
@@ -1394,16 +1349,6 @@ version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c2141d6d6c8512188a7891b4b01590a45f6dac67afb4f255c4124dbb86d4eaa"
|
||||
|
||||
[[package]]
|
||||
name = "fslock"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "04412b8935272e3a9bae6f48c7bfff74c2911f60525404edfdd28e49884c3bfb"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures"
|
||||
version = "0.3.26"
|
||||
@@ -1443,7 +1388,7 @@ checksum = "3422d14de7903a52e9dbc10ae05a7e14445ec61890100e098754e120b2bd7b1e"
|
||||
dependencies = [
|
||||
"derive_utils",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1482,7 +1427,7 @@ checksum = "95a73af87da33b5acf53acfebdc339fe592ecf5357ac7c0a7734ab9d8c876a70"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1773,7 +1718,7 @@ dependencies = [
|
||||
"quote",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"syn 1.0.109",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1790,7 +1735,7 @@ dependencies = [
|
||||
"quote",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"syn 1.0.109",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1801,7 +1746,7 @@ checksum = "e56b093bfda71de1da99758b036f4cc811fd2511c8a76f75680e9ffbd2bb4251"
|
||||
dependencies = [
|
||||
"graphql_client_codegen 0.10.0",
|
||||
"proc-macro2",
|
||||
"syn 1.0.109",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1812,7 +1757,7 @@ checksum = "a755cc59cda2641ea3037b4f9f7ef40471c329f55c1fa2db6fa0bb7ae6c1f7ce"
|
||||
dependencies = [
|
||||
"graphql_client_codegen 0.11.0",
|
||||
"proc-macro2",
|
||||
"syn 1.0.109",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2217,7 +2162,7 @@ dependencies = [
|
||||
"proc-macro-error",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2323,7 +2268,7 @@ dependencies = [
|
||||
"peg",
|
||||
"tokio-util",
|
||||
"tracing",
|
||||
"uuid 1.3.1",
|
||||
"uuid 1.3.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2351,7 +2296,7 @@ dependencies = [
|
||||
"socket2",
|
||||
"tokio",
|
||||
"tokio-rustls",
|
||||
"webpki-roots 0.22.6",
|
||||
"webpki-roots",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2400,7 +2345,7 @@ checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4"
|
||||
|
||||
[[package]]
|
||||
name = "lldap"
|
||||
version = "0.5.0-alpha"
|
||||
version = "0.4.3-alpha"
|
||||
dependencies = [
|
||||
"actix",
|
||||
"actix-files",
|
||||
@@ -2412,7 +2357,6 @@ dependencies = [
|
||||
"actix-web",
|
||||
"actix-web-httpauth",
|
||||
"anyhow",
|
||||
"assert_cmd",
|
||||
"async-trait",
|
||||
"base64 0.21.0",
|
||||
"bincode",
|
||||
@@ -2424,7 +2368,6 @@ dependencies = [
|
||||
"figment_file_provider_adapter",
|
||||
"futures",
|
||||
"futures-util",
|
||||
"graphql_client 0.11.0",
|
||||
"hmac 0.12.1",
|
||||
"http",
|
||||
"image",
|
||||
@@ -2432,17 +2375,14 @@ dependencies = [
|
||||
"juniper",
|
||||
"jwt 0.16.0",
|
||||
"lber 0.4.1",
|
||||
"ldap3",
|
||||
"ldap3_proto",
|
||||
"lettre",
|
||||
"lldap_auth 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lldap_auth",
|
||||
"log",
|
||||
"mockall",
|
||||
"nix",
|
||||
"opaque-ke",
|
||||
"orion",
|
||||
"rand 0.8.5",
|
||||
"rand_chacha 0.3.1",
|
||||
"reqwest",
|
||||
"rustls",
|
||||
"rustls-pemfile",
|
||||
@@ -2451,7 +2391,6 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_bytes",
|
||||
"serde_json",
|
||||
"serial_test",
|
||||
"sha2 0.10.6",
|
||||
"thiserror",
|
||||
"time 0.3.19",
|
||||
@@ -2465,14 +2404,13 @@ dependencies = [
|
||||
"tracing-forest",
|
||||
"tracing-log",
|
||||
"tracing-subscriber",
|
||||
"urlencoding",
|
||||
"uuid 1.3.1",
|
||||
"webpki-roots 0.23.0",
|
||||
"uuid 1.3.0",
|
||||
"webpki-roots",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lldap_app"
|
||||
version = "0.5.0-alpha"
|
||||
version = "0.4.3-alpha"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"base64 0.13.1",
|
||||
@@ -2480,18 +2418,20 @@ dependencies = [
|
||||
"gloo-console",
|
||||
"gloo-file",
|
||||
"gloo-net",
|
||||
"gloo-timers",
|
||||
"graphql_client 0.10.0",
|
||||
"http",
|
||||
"image",
|
||||
"indexmap",
|
||||
"jwt 0.13.0",
|
||||
"lldap_auth 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lldap_auth",
|
||||
"rand 0.8.5",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha1",
|
||||
"url-escape",
|
||||
"validator",
|
||||
"validator_derive 0.16.0",
|
||||
"validator_derive",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
"web-sys",
|
||||
@@ -2518,49 +2458,13 @@ dependencies = [
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lldap_auth"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c17867a28e09989643401bb7849a494b328416634a335ddf7d3dabc9806ba563"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"curve25519-dalek",
|
||||
"digest 0.9.0",
|
||||
"generic-array",
|
||||
"getrandom 0.2.8",
|
||||
"opaque-ke",
|
||||
"rand 0.8.5",
|
||||
"rust-argon2",
|
||||
"serde",
|
||||
"sha2 0.9.9",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lldap_migration_tool"
|
||||
version = "0.4.2"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"base64 0.13.1",
|
||||
"graphql_client 0.11.0",
|
||||
"ldap3",
|
||||
"lldap_auth 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand 0.8.5",
|
||||
"requestty",
|
||||
"reqwest",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"smallvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lldap_set_password"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
"lldap_auth 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lldap_auth",
|
||||
"rand 0.8.5",
|
||||
"reqwest",
|
||||
"serde",
|
||||
@@ -2635,12 +2539,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
|
||||
|
||||
[[package]]
|
||||
name = "memoffset"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4"
|
||||
name = "migration-tool"
|
||||
version = "0.4.2"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"anyhow",
|
||||
"base64 0.13.1",
|
||||
"graphql_client 0.11.0",
|
||||
"ldap3",
|
||||
"lldap_auth",
|
||||
"rand 0.8.5",
|
||||
"requestty",
|
||||
"reqwest",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"smallvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2710,21 +2622,7 @@ dependencies = [
|
||||
"cfg-if",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.26.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bfdda3d196821d6af13126e40375cdf7da646a96114af134d5f417a9a1dc8e1a"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"memoffset",
|
||||
"pin-utils",
|
||||
"static_assertions",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2934,7 +2832,7 @@ dependencies = [
|
||||
"proc-macro-error",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3017,7 +2915,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"proc-macro2-diagnostics",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3079,7 +2977,7 @@ checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3167,7 +3065,7 @@ dependencies = [
|
||||
"proc-macro-error-attr",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
"syn",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
@@ -3184,9 +3082,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.54"
|
||||
version = "1.0.51"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e472a104799c74b514a57226160104aa483546de37e839ec50e3c2e41dd87534"
|
||||
checksum = "5d727cae5b39d21da60fa540906919ad737832fe0b1c165da3a34d6548c849d6"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
@@ -3199,16 +3097,16 @@ checksum = "4bf29726d67464d49fa6224a1d07936a8c08bb3fba727c7493f6cf1616fdaada"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
"syn",
|
||||
"version_check",
|
||||
"yansi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.26"
|
||||
version = "1.0.23"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc"
|
||||
checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
@@ -3404,7 +3302,7 @@ dependencies = [
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
"web-sys",
|
||||
"webpki-roots 0.22.6",
|
||||
"webpki-roots",
|
||||
"winreg",
|
||||
]
|
||||
|
||||
@@ -3532,16 +3430,6 @@ dependencies = [
|
||||
"base64 0.21.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-webpki"
|
||||
version = "0.100.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d6207cd5ed3d8dca7816f8f3725513a34609c0c765bf652b8c3cb4cfd87db46b"
|
||||
dependencies = [
|
||||
"ring",
|
||||
"untrusted",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustversion"
|
||||
version = "1.0.11"
|
||||
@@ -3612,7 +3500,7 @@ dependencies = [
|
||||
"thiserror",
|
||||
"tracing",
|
||||
"url",
|
||||
"uuid 1.3.1",
|
||||
"uuid 1.3.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3625,7 +3513,7 @@ dependencies = [
|
||||
"heck 0.3.3",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3636,7 +3524,7 @@ checksum = "d2fbe015dbdaa7d8829d71c1e14fb6289e928ac256b93dfda543c85cd89d6f03"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"sea-query-derive",
|
||||
"uuid 1.3.1",
|
||||
"uuid 1.3.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3648,7 +3536,7 @@ dependencies = [
|
||||
"chrono",
|
||||
"sea-query",
|
||||
"sqlx",
|
||||
"uuid 1.3.1",
|
||||
"uuid 1.3.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3660,7 +3548,7 @@ dependencies = [
|
||||
"heck 0.4.1",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
"syn",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
@@ -3683,7 +3571,7 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"rustversion",
|
||||
"syn 1.0.109",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3763,7 +3651,7 @@ checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3790,30 +3678,6 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serial_test"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0e56dd856803e253c8f298af3f4d7eb0ae5e23a737252cd90bb4f3b435033b2d"
|
||||
dependencies = [
|
||||
"dashmap",
|
||||
"fslock",
|
||||
"lazy_static",
|
||||
"parking_lot 0.12.1",
|
||||
"serial_test_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serial_test_derive"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "91d129178576168c589c9ec973feedf7d3126c01ac2bf08795109aa35b69fb8f"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.12",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha1"
|
||||
version = "0.10.5"
|
||||
@@ -4032,8 +3896,8 @@ dependencies = [
|
||||
"thiserror",
|
||||
"tokio-stream",
|
||||
"url",
|
||||
"uuid 1.3.1",
|
||||
"webpki-roots 0.22.6",
|
||||
"uuid 1.3.0",
|
||||
"webpki-roots",
|
||||
"whoami",
|
||||
]
|
||||
|
||||
@@ -4051,7 +3915,7 @@ dependencies = [
|
||||
"quote",
|
||||
"sqlx-core",
|
||||
"sqlx-rt",
|
||||
"syn 1.0.109",
|
||||
"syn",
|
||||
"url",
|
||||
]
|
||||
|
||||
@@ -4105,17 +3969,6 @@ dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "79d9531f94112cfc3e4c8f5f02cb2b58f72c97b7efd85f70203cc6d8efda5927"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "synstructure"
|
||||
version = "0.12.6"
|
||||
@@ -4124,7 +3977,7 @@ checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
"syn",
|
||||
"unicode-xid",
|
||||
]
|
||||
|
||||
@@ -4185,7 +4038,7 @@ checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4279,7 +4132,7 @@ checksum = "d266c00fde287f55d3f1c3e96c500c362a2b8c695076ec180f27918820bc6df8"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4355,7 +4208,7 @@ dependencies = [
|
||||
"actix-web",
|
||||
"pin-project",
|
||||
"tracing",
|
||||
"uuid 1.3.1",
|
||||
"uuid 1.3.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4366,7 +4219,7 @@ checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4542,12 +4395,6 @@ dependencies = [
|
||||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "urlencoding"
|
||||
version = "2.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e8db7427f936968176eaa7cdf81b7f98b980b18495ec28f1b5791ac3bfe3eea9"
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "0.8.2"
|
||||
@@ -4556,9 +4403,9 @@ checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7"
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "1.3.1"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b55a3fef2a1e3b3a00ce878640918820d3c51081576ac657d23af9fc7928fdb"
|
||||
checksum = "1674845326ee10d37ca60470760d4288a6f80f304007d92e5c53bab78c9cfd79"
|
||||
dependencies = [
|
||||
"getrandom 0.2.8",
|
||||
"md-5",
|
||||
@@ -4577,7 +4424,7 @@ dependencies = [
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"url",
|
||||
"validator_types 0.14.0",
|
||||
"validator_types",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4592,24 +4439,8 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"regex",
|
||||
"syn 1.0.109",
|
||||
"validator_types 0.14.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "validator_derive"
|
||||
version = "0.16.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bc44ca3088bb3ba384d9aecf40c6a23a676ce23e09bdaca2073d99c207f864af"
|
||||
dependencies = [
|
||||
"if_chain",
|
||||
"lazy_static",
|
||||
"proc-macro-error",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"regex",
|
||||
"syn 1.0.109",
|
||||
"validator_types 0.16.0",
|
||||
"syn",
|
||||
"validator_types",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4619,17 +4450,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ded9d97e1d42327632f5f3bae6403c04886e2de3036261ef42deebd931a6a291"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"syn 1.0.109",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "validator_types"
|
||||
version = "0.16.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "111abfe30072511849c5910134e8baf8dc05de4c0e5903d681cbd5c9c4d611e3"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"syn 1.0.109",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4656,15 +4477,6 @@ version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
|
||||
|
||||
[[package]]
|
||||
name = "wait-timeout"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "want"
|
||||
version = "0.3.0"
|
||||
@@ -4714,7 +4526,7 @@ dependencies = [
|
||||
"once_cell",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
"syn",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
@@ -4748,7 +4560,7 @@ checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
"syn",
|
||||
"wasm-bindgen-backend",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
@@ -4788,15 +4600,6 @@ dependencies = [
|
||||
"webpki",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "webpki-roots"
|
||||
version = "0.23.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aa54963694b65584e170cf5dc46aeb4dcaa5584e652ff5f3952e56d66aff0125"
|
||||
dependencies = [
|
||||
"rustls-webpki",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "whoami"
|
||||
version = "1.3.0"
|
||||
@@ -4982,7 +4785,7 @@ dependencies = [
|
||||
"proc-macro-error",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5013,7 +4816,7 @@ checksum = "39049d193b52eaad4ffc80916bf08806d142c90b5edcebd527644de438a7e19a"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -5023,7 +4826,7 @@ source = "git+https://github.com/jfbilodeau/yew_form?rev=4b9fabffb63393ec7626a44
|
||||
dependencies = [
|
||||
"gloo-console",
|
||||
"validator",
|
||||
"validator_derive 0.14.0",
|
||||
"validator_derive",
|
||||
"wasm-bindgen",
|
||||
"web-sys",
|
||||
"yew",
|
||||
@@ -5036,7 +4839,7 @@ source = "git+https://github.com/jfbilodeau/yew_form?rev=4b9fabffb63393ec7626a44
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
"syn",
|
||||
"yew_form",
|
||||
]
|
||||
|
||||
@@ -5057,7 +4860,7 @@ checksum = "44bf07cb3e50ea2003396695d58bf46bc9887a1f362260446fad6bc4e79bd36c"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
"syn",
|
||||
"synstructure",
|
||||
]
|
||||
|
||||
|
||||
@@ -31,12 +31,12 @@ FROM chef AS builder
|
||||
COPY --from=planner /tmp/recipe.json recipe.json
|
||||
RUN cargo chef cook --release -p lldap_app --target wasm32-unknown-unknown \
|
||||
&& cargo chef cook --release -p lldap \
|
||||
&& cargo chef cook --release -p lldap_migration_tool \
|
||||
&& cargo chef cook --release -p migration-tool \
|
||||
&& cargo chef cook --release -p lldap_set_password
|
||||
|
||||
# Copy the source and build the app and server.
|
||||
COPY --chown=app:app . .
|
||||
RUN cargo build --release -p lldap -p lldap_migration_tool -p lldap_set_password \
|
||||
RUN cargo build --release -p lldap -p migration-tool -p lldap_set_password \
|
||||
# Build the frontend.
|
||||
&& ./app/build.sh
|
||||
|
||||
@@ -78,7 +78,7 @@ WORKDIR /app
|
||||
COPY --from=builder /app/app/index_local.html app/index.html
|
||||
COPY --from=builder /app/app/static app/static
|
||||
COPY --from=builder /app/app/pkg app/pkg
|
||||
COPY --from=builder /app/target/release/lldap /app/target/release/lldap_migration_tool /app/target/release/lldap_set_password ./
|
||||
COPY --from=builder /app/target/release/lldap /app/target/release/migration-tool /app/target/release/lldap_set_password ./
|
||||
COPY docker-entrypoint.sh lldap_config.docker_template.toml ./
|
||||
|
||||
RUN set -x \
|
||||
|
||||
12
README.md
12
README.md
@@ -13,7 +13,6 @@
|
||||
<a href="https://discord.gg/h5PEdRMNyP">
|
||||
<img alt="Discord" src="https://img.shields.io/discord/898492935446876200?label=discord&logo=discord" />
|
||||
</a>
|
||||
|
||||
<a href="https://twitter.com/nitnelave1?ref_src=twsrc%5Etfw">
|
||||
<img
|
||||
src="https://img.shields.io/twitter/follow/nitnelave1?style=social"
|
||||
@@ -24,12 +23,8 @@
|
||||
src="https://img.shields.io/badge/unsafe-forbidden-success.svg"
|
||||
alt="Unsafe forbidden"/>
|
||||
</a>
|
||||
<a href="https://app.codecov.io/gh/lldap/lldap">
|
||||
<img alt="Codecov" src="https://img.shields.io/codecov/c/github/lldap/lldap" />
|
||||
</a>
|
||||
<br/>
|
||||
<a href="https://www.buymeacoffee.com/nitnelave" target="_blank">
|
||||
<img src="https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png" alt="Buy Me A Coffee" style="height: 41px !important;width: 174px !important;box-shadow: 0px 3px 2px 0px rgba(190, 190, 190, 0.5) !important;-webkit-box-shadow: 0px 3px 2px 0px rgba(190, 190, 190, 0.5) !important;" >
|
||||
<a href="https://app.codecov.io/gh/nitnelave/lldap">
|
||||
<img alt="Codecov" src="https://img.shields.io/codecov/c/github/nitnelave/lldap" />
|
||||
</a>
|
||||
</p>
|
||||
|
||||
@@ -163,7 +158,7 @@ To compile the project, you'll need:
|
||||
Then you can compile the server (and the migration tool if you want):
|
||||
|
||||
```shell
|
||||
cargo build --release -p lldap -p lldap_migration_tool
|
||||
cargo build --release -p lldap -p migration-tool
|
||||
```
|
||||
|
||||
The resulting binaries will be in `./target/release/`. Alternatively, you can
|
||||
@@ -262,7 +257,6 @@ folder for help with:
|
||||
- [Dex](example_configs/dex_config.yml)
|
||||
- [Dokuwiki](example_configs/dokuwiki.md)
|
||||
- [Dolibarr](example_configs/dolibarr.md)
|
||||
- [Ejabberd](example_configs/ejabberd.md)
|
||||
- [Emby](example_configs/emby.md)
|
||||
- [Gitea](example_configs/gitea.md)
|
||||
- [Grafana](example_configs/grafana_ldap_config.toml)
|
||||
|
||||
@@ -1,12 +1,8 @@
|
||||
[package]
|
||||
authors = ["Valentin Tolmer <valentin@tolmer.fr>"]
|
||||
description = "Frontend for LLDAP"
|
||||
edition = "2021"
|
||||
homepage = "https://github.com/lldap/lldap"
|
||||
license = "GPL-3.0-only"
|
||||
name = "lldap_app"
|
||||
repository = "https://github.com/lldap/lldap"
|
||||
version = "0.5.0-alpha"
|
||||
version = "0.4.3-alpha"
|
||||
authors = ["Valentin Tolmer <valentin@tolmer.fr>"]
|
||||
edition = "2021"
|
||||
include = ["src/**/*", "queries/**/*", "Cargo.toml", "../schema.graphql"]
|
||||
|
||||
[dependencies]
|
||||
@@ -14,7 +10,7 @@ anyhow = "1"
|
||||
base64 = "0.13"
|
||||
gloo-console = "0.2.3"
|
||||
gloo-file = "0.2.3"
|
||||
gloo-net = "0.2"
|
||||
gloo-net = "*"
|
||||
graphql_client = "0.10"
|
||||
http = "0.2"
|
||||
jwt = "0.13"
|
||||
@@ -23,14 +19,16 @@ serde = "1"
|
||||
serde_json = "1"
|
||||
url-escape = "0.1.1"
|
||||
validator = "=0.14"
|
||||
validator_derive = "0.16"
|
||||
sha1 = "*"
|
||||
validator_derive = "*"
|
||||
wasm-bindgen = "0.2"
|
||||
wasm-bindgen-futures = "0.4"
|
||||
wasm-bindgen-futures = "*"
|
||||
yew = "0.19.3"
|
||||
yew-router = "0.16"
|
||||
|
||||
# Needed because of https://github.com/tkaitchuck/aHash/issues/95
|
||||
indexmap = "=1.6.2"
|
||||
gloo-timers = "0.2.6"
|
||||
|
||||
[dependencies.web-sys]
|
||||
version = "0.3"
|
||||
@@ -47,13 +45,13 @@ features = [
|
||||
]
|
||||
|
||||
[dependencies.chrono]
|
||||
version = "0.4"
|
||||
version = "*"
|
||||
features = [
|
||||
"wasmbind"
|
||||
]
|
||||
|
||||
[dependencies.lldap_auth]
|
||||
version = "0.3"
|
||||
path = "../auth"
|
||||
features = [ "opaque_client" ]
|
||||
|
||||
[dependencies.image]
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use crate::{
|
||||
components::password_field::PasswordField,
|
||||
components::router::{AppRoute, Link},
|
||||
infra::{
|
||||
api::HostService,
|
||||
@@ -254,14 +255,12 @@ impl Component for ChangePasswordForm {
|
||||
{":"}
|
||||
</label>
|
||||
<div class="col-sm-10">
|
||||
<Field
|
||||
<PasswordField<FormModel>
|
||||
form={&self.form}
|
||||
field_name="password"
|
||||
input_type="password"
|
||||
class="form-control"
|
||||
class_invalid="is-invalid has-error"
|
||||
class_valid="has-success"
|
||||
autocomplete="new-password"
|
||||
oninput={link.callback(|_| Msg::FormUpdate)} />
|
||||
<div class="invalid-feedback">
|
||||
{&self.form.field_message("password")}
|
||||
|
||||
@@ -149,9 +149,9 @@ impl Component for LoginForm {
|
||||
let link = &ctx.link();
|
||||
if self.refreshing {
|
||||
html! {
|
||||
<div>
|
||||
<img src={"spinner.gif"} alt={"Loading"} />
|
||||
</div>
|
||||
<div class="spinner-border" role="status">
|
||||
<span class="sr-only">{"Loading..."}</span>
|
||||
</div>
|
||||
}
|
||||
} else {
|
||||
html! {
|
||||
|
||||
@@ -10,6 +10,7 @@ pub mod group_details;
|
||||
pub mod group_table;
|
||||
pub mod login;
|
||||
pub mod logout;
|
||||
pub mod password_field;
|
||||
pub mod remove_user_from_group;
|
||||
pub mod reset_password_step1;
|
||||
pub mod reset_password_step2;
|
||||
|
||||
152
app/src/components/password_field.rs
Normal file
152
app/src/components/password_field.rs
Normal file
@@ -0,0 +1,152 @@
|
||||
use crate::infra::{
|
||||
api::{hash_password, HostService, PasswordHash, PasswordWasLeaked},
|
||||
common_component::{CommonComponent, CommonComponentParts},
|
||||
};
|
||||
use anyhow::Result;
|
||||
use gloo_timers::callback::Timeout;
|
||||
use web_sys::{HtmlInputElement, InputEvent};
|
||||
use yew::{html, Callback, Classes, Component, Context, Properties};
|
||||
use yew_form::{Field, Form, Model};
|
||||
|
||||
pub enum PasswordFieldMsg {
|
||||
OnInput(String),
|
||||
OnInputIdle,
|
||||
PasswordCheckResult(Result<(Option<PasswordWasLeaked>, PasswordHash)>),
|
||||
}
|
||||
|
||||
#[derive(PartialEq)]
|
||||
pub enum PasswordState {
|
||||
// Whether the password was found in a leak.
|
||||
Checked(PasswordWasLeaked),
|
||||
// Server doesn't support checking passwords (TODO: move to config).
|
||||
NotSupported,
|
||||
// Requested a check, no response yet from the server.
|
||||
Loading,
|
||||
// User is still actively typing.
|
||||
Typing,
|
||||
}
|
||||
|
||||
pub struct PasswordField<FormModel: Model> {
|
||||
common: CommonComponentParts<Self>,
|
||||
timeout_task: Option<Timeout>,
|
||||
password: String,
|
||||
password_check_state: PasswordState,
|
||||
_marker: std::marker::PhantomData<FormModel>,
|
||||
}
|
||||
|
||||
impl<FormModel: Model> CommonComponent<PasswordField<FormModel>> for PasswordField<FormModel> {
|
||||
fn handle_msg(
|
||||
&mut self,
|
||||
ctx: &Context<Self>,
|
||||
msg: <Self as Component>::Message,
|
||||
) -> anyhow::Result<bool> {
|
||||
match msg {
|
||||
PasswordFieldMsg::OnInput(password) => {
|
||||
self.password = password;
|
||||
if self.password_check_state != PasswordState::NotSupported {
|
||||
self.password_check_state = PasswordState::Typing;
|
||||
if self.password.len() >= 8 {
|
||||
let link = ctx.link().clone();
|
||||
self.timeout_task = Some(Timeout::new(500, move || {
|
||||
link.send_message(PasswordFieldMsg::OnInputIdle)
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
PasswordFieldMsg::PasswordCheckResult(result) => {
|
||||
self.timeout_task = None;
|
||||
// If there's an error from the backend, don't retry.
|
||||
self.password_check_state = PasswordState::NotSupported;
|
||||
if let (Some(check), hash) = result? {
|
||||
if hash == hash_password(&self.password) {
|
||||
self.password_check_state = PasswordState::Checked(check)
|
||||
}
|
||||
}
|
||||
}
|
||||
PasswordFieldMsg::OnInputIdle => {
|
||||
self.timeout_task = None;
|
||||
if self.password_check_state != PasswordState::NotSupported {
|
||||
self.password_check_state = PasswordState::Loading;
|
||||
self.common.call_backend(
|
||||
ctx,
|
||||
HostService::check_password_haveibeenpwned(hash_password(&self.password)),
|
||||
PasswordFieldMsg::PasswordCheckResult,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
fn mut_common(&mut self) -> &mut CommonComponentParts<PasswordField<FormModel>> {
|
||||
&mut self.common
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Properties, PartialEq, Clone)]
|
||||
pub struct PasswordFieldProperties<FormModel: Model> {
|
||||
pub field_name: String,
|
||||
pub form: Form<FormModel>,
|
||||
#[prop_or_else(|| { "form-control".into() })]
|
||||
pub class: Classes,
|
||||
#[prop_or_else(|| { "is-invalid".into() })]
|
||||
pub class_invalid: Classes,
|
||||
#[prop_or_else(|| { "is-valid".into() })]
|
||||
pub class_valid: Classes,
|
||||
#[prop_or_else(Callback::noop)]
|
||||
pub oninput: Callback<String>,
|
||||
}
|
||||
|
||||
impl<FormModel: Model> Component for PasswordField<FormModel> {
|
||||
type Message = PasswordFieldMsg;
|
||||
type Properties = PasswordFieldProperties<FormModel>;
|
||||
|
||||
fn create(_: &Context<Self>) -> Self {
|
||||
Self {
|
||||
common: CommonComponentParts::<Self>::create(),
|
||||
timeout_task: None,
|
||||
password: String::new(),
|
||||
password_check_state: PasswordState::Typing,
|
||||
_marker: std::marker::PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {
|
||||
CommonComponentParts::<Self>::update(self, ctx, msg)
|
||||
}
|
||||
|
||||
fn view(&self, ctx: &Context<Self>) -> yew::Html {
|
||||
let link = &ctx.link();
|
||||
html! {
|
||||
<div>
|
||||
<Field<FormModel>
|
||||
autocomplete={"new-password"}
|
||||
input_type={"password"}
|
||||
field_name={ctx.props().field_name.clone()}
|
||||
form={ctx.props().form.clone()}
|
||||
class={ctx.props().class.clone()}
|
||||
class_invalid={ctx.props().class_invalid.clone()}
|
||||
class_valid={ctx.props().class_valid.clone()}
|
||||
oninput={link.callback(|e: InputEvent| {
|
||||
use wasm_bindgen::JsCast;
|
||||
let target = e.target().unwrap();
|
||||
let input = target.dyn_into::<HtmlInputElement>().unwrap();
|
||||
PasswordFieldMsg::OnInput(input.value())
|
||||
})} />
|
||||
{
|
||||
match self.password_check_state {
|
||||
PasswordState::Checked(PasswordWasLeaked(true)) => html! { <i class="bi bi-x"></i> },
|
||||
PasswordState::Checked(PasswordWasLeaked(false)) => html! { <i class="bi bi-check"></i> },
|
||||
PasswordState::NotSupported | PasswordState::Typing => html!{},
|
||||
PasswordState::Loading =>
|
||||
html! {
|
||||
<div class="spinner-border spinner-border-sm" role="status">
|
||||
<span class="sr-only">{"Loading..."}</span>
|
||||
</div>
|
||||
},
|
||||
}
|
||||
}
|
||||
</div>
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,8 @@
|
||||
use crate::{
|
||||
components::router::{AppRoute, Link},
|
||||
components::{
|
||||
password_field::PasswordField,
|
||||
router::{AppRoute, Link},
|
||||
},
|
||||
infra::{
|
||||
api::HostService,
|
||||
common_component::{CommonComponent, CommonComponentParts},
|
||||
@@ -176,14 +179,12 @@ impl Component for ResetPasswordStep2Form {
|
||||
{"New password*:"}
|
||||
</label>
|
||||
<div class="col-sm-10">
|
||||
<Field
|
||||
<PasswordField<FormModel>
|
||||
form={&self.form}
|
||||
field_name="password"
|
||||
class="form-control"
|
||||
class_invalid="is-invalid has-error"
|
||||
class_valid="has-success"
|
||||
autocomplete="new-password"
|
||||
input_type="password"
|
||||
oninput={link.callback(|_| Msg::FormUpdate)} />
|
||||
<div class="invalid-feedback">
|
||||
{&self.form.field_message("password")}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use super::cookies::set_cookie;
|
||||
use crate::infra::cookies::set_cookie;
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use gloo_net::http::{Method, Request};
|
||||
use graphql_client::GraphQLQuery;
|
||||
@@ -74,6 +74,19 @@ fn set_cookies_from_jwt(response: login::ServerLoginResponse) -> Result<(String,
|
||||
.context("Error setting cookie")
|
||||
}
|
||||
|
||||
#[derive(PartialEq)]
|
||||
pub struct PasswordHash(String);
|
||||
|
||||
#[derive(PartialEq)]
|
||||
pub struct PasswordWasLeaked(pub bool);
|
||||
|
||||
pub fn hash_password(password: &str) -> PasswordHash {
|
||||
use sha1::{Digest, Sha1};
|
||||
let mut hasher = Sha1::new();
|
||||
hasher.update(password);
|
||||
PasswordHash(format!("{:X}", hasher.finalize()))
|
||||
}
|
||||
|
||||
impl HostService {
|
||||
pub async fn graphql_query<QueryType>(
|
||||
variables: QueryType::Variables,
|
||||
@@ -194,4 +207,35 @@ impl HostService {
|
||||
!= http::StatusCode::NOT_FOUND,
|
||||
)
|
||||
}
|
||||
|
||||
pub async fn check_password_haveibeenpwned(
|
||||
password_hash: PasswordHash,
|
||||
) -> Result<(Option<PasswordWasLeaked>, PasswordHash)> {
|
||||
use lldap_auth::password_reset::*;
|
||||
let hash_prefix = &password_hash.0[0..5];
|
||||
match call_server_json_with_error_message::<PasswordHashList, _>(
|
||||
&format!("/auth/password/check/{}", hash_prefix),
|
||||
NO_BODY,
|
||||
"Could not validate token",
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(r) => {
|
||||
for PasswordHashCount { hash, count } in r.hashes {
|
||||
if password_hash.0[5..] == hash && count != 0 {
|
||||
return Ok((Some(PasswordWasLeaked(true)), password_hash));
|
||||
}
|
||||
}
|
||||
Ok((Some(PasswordWasLeaked(false)), password_hash))
|
||||
}
|
||||
Err(e) => {
|
||||
if e.to_string().contains("[501]:") {
|
||||
// Unimplemented, no API key.
|
||||
Ok((None, password_hash))
|
||||
} else {
|
||||
Err(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 44 KiB |
@@ -1,12 +1,8 @@
|
||||
[package]
|
||||
authors = ["Valentin Tolmer <valentin@tolmer.fr>"]
|
||||
description = "Authentication protocol for LLDAP"
|
||||
edition = "2021"
|
||||
homepage = "https://github.com/lldap/lldap"
|
||||
license = "GPL-3.0-only"
|
||||
name = "lldap_auth"
|
||||
repository = "https://github.com/lldap/lldap"
|
||||
version = "0.3.0"
|
||||
authors = ["Valentin Tolmer <valentin@tolmer.fr>"]
|
||||
edition = "2021"
|
||||
|
||||
[features]
|
||||
default = ["opaque_server", "opaque_client"]
|
||||
@@ -18,17 +14,17 @@ js = []
|
||||
rust-argon2 = "0.8"
|
||||
curve25519-dalek = "3"
|
||||
digest = "0.9"
|
||||
generic-array = "0.14"
|
||||
generic-array = "*"
|
||||
rand = "0.8"
|
||||
serde = "1"
|
||||
serde = "*"
|
||||
sha2 = "0.9"
|
||||
thiserror = "1"
|
||||
thiserror = "*"
|
||||
|
||||
[dependencies.opaque-ke]
|
||||
version = "0.6"
|
||||
|
||||
[dependencies.chrono]
|
||||
version = "0.4"
|
||||
version = "*"
|
||||
features = [ "serde" ]
|
||||
|
||||
# For WASM targets, use the JS getrandom.
|
||||
|
||||
@@ -102,6 +102,17 @@ pub mod password_reset {
|
||||
pub user_id: String,
|
||||
pub token: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
pub struct PasswordHashCount {
|
||||
pub hash: String,
|
||||
pub count: u64,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone)]
|
||||
pub struct PasswordHashList {
|
||||
pub hashes: Vec<PasswordHashCount>,
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
# Migration from 0.4 to 0.5
|
||||
|
||||
Welcome! If you're here, it's probably that the migration from 0.4.x to 0.5
|
||||
didn't go smoothly for you. Don't worry, we can fix that.
|
||||
|
||||
## Multiple users with the same email
|
||||
|
||||
This is the most common case. You can see in the LLDAP logs that there are
|
||||
several users with the same email, and they are listed.
|
||||
|
||||
This is not allowed anymore in v0.5, to prevent a user from setting their email
|
||||
to someone else's email and gaining access to systems that identify by email.
|
||||
|
||||
The problem is that you currently have several users with the same email, so the
|
||||
constraint cannot be enforced.
|
||||
|
||||
### Step 1: Take a note of the users with duplicate emails
|
||||
|
||||
In the LLDAP logs when you tried to start v0.5+, you'll see some warnings with
|
||||
the list of users with the same emails. Take note of them.
|
||||
|
||||
### Step 2: Downgrade to v0.4.3
|
||||
|
||||
If using docker, switch to the `lldap/lldap:v0.4.3` image. Alternatively, grab
|
||||
the binaries at https://github.com/lldap/lldap/releases/tag/v0.4.3.
|
||||
|
||||
This downgrade is safe and supported.
|
||||
|
||||
### Step 3: Remove duplicate emails
|
||||
|
||||
Restart LLDAP with the v0.4.3 version, and using your notes from step 1, change
|
||||
the email of users with duplicate emails to make sure that each email is unique.
|
||||
|
||||
### Step 4: Upgrade again
|
||||
|
||||
You can now revert to the initial version.
|
||||
|
||||
## Multiple users/groups with the same UUID
|
||||
|
||||
This should be extremely rare. In this case, you'll need to find which users
|
||||
have the same UUID, revert to v0.4.3 to be able to apply the changes, and delete
|
||||
one of the duplicates.
|
||||
|
||||
## FAQ
|
||||
|
||||
### What if I want several users to be controlled by the same email?
|
||||
|
||||
You can use plus codes to set "the same" email to several users, while ensuring
|
||||
that they can't identify as each other. For instance:
|
||||
|
||||
- Admin: `admin@example.com`
|
||||
- Read-only admin: `admin+readonly@example.com`
|
||||
- Jellyfin admin: `admin+jellyfin@example.com`
|
||||
|
||||
### I'm upgrading to a higher version than v0.5.
|
||||
|
||||
This guide is still relevant: you can use whatever later version in place of
|
||||
v0.5. You'll still need to revert to v0.4.3 to apply the changes.
|
||||
@@ -1,30 +0,0 @@
|
||||
# Basic LDAP auth for a Ejabberd XMPP server
|
||||
|
||||
[Main documentation here.](https://docs.ejabberd.im/admin/configuration/ldap/)
|
||||
|
||||
For simple user auth add this to main ejabberd.yml:
|
||||
|
||||
```
|
||||
host_config:
|
||||
xmpp.example.org:
|
||||
auth_method: [ldap]
|
||||
ldap_servers:
|
||||
- 127.0.0.1 #IP or hostname of LLDAP server
|
||||
ldap_port: 3890
|
||||
ldap_uids:
|
||||
- uid
|
||||
ldap_rootdn: "uid=lldap_readonly,ou=people,dc=example,dc=org"
|
||||
ldap_password: "secret"
|
||||
ldap_base: "ou=people,dc=example,dc=org"
|
||||
```
|
||||
|
||||
## vCard from LDAP
|
||||
Theoretically possible, [see the documentation.](https://docs.ejabberd.im/admin/configuration/ldap/#vcard-in-ldap)
|
||||
|
||||
TODO
|
||||
|
||||
## Shared roster groups from LDAP
|
||||
|
||||
Theoretically possible, [see the documentation.](https://docs.ejabberd.im/admin/configuration/ldap/#shared-roster-in-ldap)
|
||||
|
||||
TODO
|
||||
@@ -44,6 +44,6 @@ username = "uid"
|
||||
# If you want to map your ldap groups to grafana's groups, see: https://grafana.com/docs/grafana/latest/auth/ldap/#group-mappings
|
||||
# As a quick example, here is how you would map lldap's admin group to grafana's admin
|
||||
# [[servers.group_mappings]]
|
||||
# group_dn = "cn=lldap_admin,ou=groups,dc=example,dc=org"
|
||||
# group_dn = "uid=lldap_admin,ou=groups,dc=example,dc=org"
|
||||
# org_role = "Admin"
|
||||
# grafana_admin = true
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
# Home Assistant Configuration
|
||||
|
||||
Home Assistant configures ldap auth via the [Command Line Auth Provider](https://www.home-assistant.io/docs/authentication/providers/#command-line). The wiki mentions a script that can be used for LDAP authentication, but it doesn't work in the container version (it is lacking both `ldapsearch` and `curl` ldap protocol support). Thankfully LLDAP has a graphql API to save the day!
|
||||
|
||||
## Graphql-based Auth Script
|
||||
|
||||
The [auth script](lldap-ha-auth.sh) attempts to authenticate a user against an LLDAP server, using credentials provided via `username` and `password` environment variables. The first argument must be the URL of your LLDAP server, accessible from Home Assistant. You can provide an additional optional argument to confine allowed logins to a single group. The script will output the user's display name as the `name` variable, if not empty.
|
||||
|
||||
1. Copy the [auth script](lldap-ha-auth.sh) to your home assistant instance. In this example, we use `/config/lldap-auth.sh`.
|
||||
- Set the script as executable by running `chmod +x /config/lldap-auth-sh`
|
||||
2. Add the following to your configuration.yaml in Home assistant:
|
||||
```yaml
|
||||
homeassistant:
|
||||
auth_providers:
|
||||
# Ensure you have the homeassistant provider enabled if you want to continue using your existing accounts
|
||||
- type: homeassistant
|
||||
- type: command_line
|
||||
command: /config/lldap-auth.sh
|
||||
# Only allow users in the 'homeassistant_user' group to login.
|
||||
# Change to ["https://lldap.example.com"] to allow all users
|
||||
args: ["https://lldap.example.com", "homeassistant_user"]
|
||||
meta: true
|
||||
```
|
||||
3. Reload your config or restart Home Assistant
|
||||
@@ -1,70 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Usernames should be validated using a regular expression to be of
|
||||
# a known format. Special characters will be escaped anyway, but it is
|
||||
# generally not recommended to allow more than necessary.
|
||||
# This pattern is set by default. In your config file, you can either
|
||||
# overwrite it with a different one or use "unset USERNAME_PATTERN" to
|
||||
# disable validation completely.
|
||||
USERNAME_PATTERN='^[a-z|A-Z|0-9|_|-|.]+$'
|
||||
|
||||
# When the timeout (in seconds) is exceeded (e.g. due to slow networking),
|
||||
# authentication fails.
|
||||
TIMEOUT=3
|
||||
|
||||
# Log messages to stderr.
|
||||
log() {
|
||||
echo "$1" >&2
|
||||
}
|
||||
|
||||
# Get server address
|
||||
if [ -z "$1" ]; then
|
||||
log "Usage: lldap-auth.sh <LLDAP server address> <Optional group to filter>"
|
||||
exit 2
|
||||
fi
|
||||
SERVER_URL="${1%/}"
|
||||
|
||||
# Check username and password are present and not malformed.
|
||||
if [ -z "$username" ] || [ -z "$password" ]; then
|
||||
log "Need username and password environment variables."
|
||||
exit 2
|
||||
elif [ ! -z "$USERNAME_PATTERN" ]; then
|
||||
username_match=$(echo "$username" | sed -r "s/$USERNAME_PATTERN/x/")
|
||||
if [ "$username_match" != "x" ]; then
|
||||
log "Username '$username' has an invalid format."
|
||||
exit 2
|
||||
fi
|
||||
fi
|
||||
|
||||
RESPONSE=$(curl -f -s -X POST -m "$TIMEOUT" -H "Content-type: application/json" -d '{"username":"'"$username"'","password":"'"$password"'"}' "$SERVER_URL/auth/simple/login")
|
||||
if [[ $? -ne 0 ]]; then
|
||||
log "Auth failed"
|
||||
exit 1
|
||||
fi
|
||||
TOKEN=$(jq -e -r .token <<< $RESPONSE)
|
||||
if [[ $? -ne 0 ]]; then
|
||||
log "Failed to parse token"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
RESPONSE=$(curl -f -s -m "$TIMEOUT" -H "Content-type: application/json" -H "Authorization: Bearer ${TOKEN}" -d '{"variables":{"id":"'"$username"'"},"query":"query($id:String!){user(userId:$id){displayName groups{displayName}}}"}' "$SERVER_URL/api/graphql")
|
||||
if [[ $? -ne 0 ]]; then
|
||||
log "Failed to get user"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
USER_JSON=$(jq -e .data.user <<< $RESPONSE)
|
||||
if [[ $? -ne 0 ]]; then
|
||||
log "Failed to parse user json"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ ! -z "$2" ]] && ! jq -e '.groups|map(.displayName)|index("'"$2"'")' <<< $USER_JSON > /dev/null 2>&1; then
|
||||
log "User is not in group '$2'"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
DISPLAY_NAME=$(jq -r .displayName <<< $USER_JSON)
|
||||
|
||||
[[ ! -z "$DISPLAY_NAME" ]] && echo "name = $DISPLAY_NAME"
|
||||
|
||||
@@ -1,113 +0,0 @@
|
||||
# Configuration for OPNsense
|
||||
|
||||
## Create a LDAP Server
|
||||
|
||||
- Login to OPNsense
|
||||
- Navigate to: `System > Access > Servers`
|
||||
- Create a new server by clicking on the `+` icon
|
||||
|
||||
## Server Config
|
||||
|
||||
- Descriptive Name: `A Descriptive Name`
|
||||
- Type: `LDAP`
|
||||
- Hostname or IP address: `Hostname or IP for your LLDAP host`
|
||||
- Port value: `Your LLDAP port`
|
||||
- Default: `3890`
|
||||
- Transport: `TCP - Standard`
|
||||
- Protocol version: `3`
|
||||
|
||||
Make sure the host running LLDAP is accessible to OPNsense and that you mapped the LLDAP port to the LLDAP host.
|
||||
|
||||
## LDAP Config
|
||||
|
||||
### Bind credentials
|
||||
|
||||
#### User DN
|
||||
|
||||
```
|
||||
uid=admin,ou=people,dc=example,dc=com
|
||||
```
|
||||
|
||||
It is recommended that you create a separate user account (e.g, `bind_user`) instead of `admin` for sharing Bind credentials with other services. The `bind_user` should be a member of the `lldap_strict_readonly` group to limit access to your LDAP configuration in LLDAP.
|
||||
|
||||
#### Password
|
||||
|
||||
```
|
||||
xxx
|
||||
```
|
||||
|
||||
Enter the password that you set for the user specified in the User DN field.
|
||||
|
||||
### Search Scope
|
||||
|
||||
```
|
||||
One Level
|
||||
```
|
||||
|
||||
### Base DN
|
||||
|
||||
```
|
||||
dc=example,dc=com
|
||||
```
|
||||
|
||||
This is the same LDAP Base DN that you set via the *LLDAP_LDAP_BASE_DN* environment variable or in `lldap_config.toml`.
|
||||
|
||||
### Authentication containers
|
||||
|
||||
```
|
||||
ou=people,dc=example,dc=com
|
||||
```
|
||||
|
||||
Note: The `Select` box may not work for selecting containers. You can just enter the `Authentication containers` directly into the text field.
|
||||
|
||||
### Extended Query
|
||||
|
||||
```
|
||||
&(objectClass=person)(memberof=cn=lldap_admin,ou=groups,dc=example,dc=com)
|
||||
```
|
||||
|
||||
It is recommended that you create a unique LDAP group (e.g., `lldap_opnsense`) in LLDAP and use that group in this query instead of `lldap_admin`. This will limit OPNsense access to users in the `lldap_opnsense` group and make it easier to synchronize LLDAP groups with OPNsense groups for managing OPNsense access.
|
||||
|
||||
### Initial Template
|
||||
|
||||
```
|
||||
OpenLDAP
|
||||
```
|
||||
|
||||
### User naming attribute
|
||||
|
||||
```
|
||||
uid
|
||||
```
|
||||
|
||||
## Optional Configuration
|
||||
|
||||
The above configuration will connect OPNsense to LLDAP. This optional configuration will synchronize groups between LLDAP and OPNsense and automate user creation when an authorized LLDAP user logs into OPNsense.
|
||||
|
||||
### Remaining Server Configuration
|
||||
|
||||
Enable the following options on the OPNsense configuration page for your LLDAP server (the same page where you entered the prior configuration):
|
||||
|
||||
- Read Properties: `Checked`
|
||||
- Synchronize groups: `Checked`
|
||||
- Automatic user creation: `Checked`
|
||||
|
||||
### Create OPNsense Group
|
||||
|
||||
Go to `System > Access > Groups` and create a new group with the **same** name as the LLDAP group used to authenticate users for OPNsense.
|
||||
|
||||
By default, you would name your OPNsense group `lldap_admin` unless you followed the recommended advice in this guide and created a separate `lldap_opnsense` group for managing OPNsense users.
|
||||
|
||||
If you want your LLDAP users to have full administrator access in OPNsense, then you need to edit the `Assigned Privileges` for the group and add the `GUI - All pages` system privilege.
|
||||
|
||||
### Enable LLDAP as an Authentication Option
|
||||
|
||||
Go to `System > Settings > Administration` page and scroll down to the `Authentication` section. Add your LLDAP server configuration to the `Server` field.
|
||||
|
||||
## Testing LLDAP
|
||||
|
||||
OPNsense includes a built-in feature for testing user authentication at `System > Access > Tester`. Select your LLDAP server configuration in the `Authentication Server` to test logins for your LLDAP users.
|
||||
|
||||
## More Information
|
||||
|
||||
Please read the [OPNsense docs](https://docs.opnsense.org/manual/how-tos/user-ldap.html) for more information on LDAP configuration and managing access to OPNsense.
|
||||
@@ -1,117 +0,0 @@
|
||||
# Configuration for pfSense
|
||||
|
||||
## Create a LDAP Server
|
||||
|
||||
- Login to pfSense
|
||||
- Navigate to: `System > User Manager > Authentication Servers`
|
||||
- Create a new server by clicking on the `+ Add` button
|
||||
|
||||
## LDAP Server Settings
|
||||
|
||||
- Descriptive Name: `A Descriptive Name`
|
||||
- Type: `LDAP`
|
||||
- Hostname or IP address: `Hostname or IP for your LLDAP host`
|
||||
- Port value: `Your LLDAP port`
|
||||
- Transport: `TCP - Standard`
|
||||
- Protocol version: `3`
|
||||
- Server Timeout: `25`
|
||||
|
||||
(Make sure the host running LLDAP is accessible to pfSense and that you mapped the LLDAP port to the LLDAP host)
|
||||
### Search Scope
|
||||
```
|
||||
Entire Subtree
|
||||
```
|
||||
### Base DN
|
||||
|
||||
```
|
||||
dc=example,dc=com
|
||||
```
|
||||
|
||||
This is the same LDAP Base DN that you set via the *LLDAP_LDAP_BASE_DN* environment variable or in `lldap_config.toml`.
|
||||
### Authentication containers
|
||||
|
||||
```
|
||||
ou=people
|
||||
```
|
||||
|
||||
Note: The `Select a container` box may not work for selecting containers. You can just enter the `Authentication containers` directly into the text field.
|
||||
|
||||
### Extended Query
|
||||
|
||||
Enable extended query: `Checked`
|
||||
|
||||
### Query:
|
||||
|
||||
```
|
||||
&(objectClass=person)(|(memberof=cn=pfsense_admin,ou=groups,dc=example,dc=com)(memberof=cn=pfsense_guest,ou=groups,dc=example,dc=com))
|
||||
```
|
||||
|
||||
This example gives you two groups in LLDAP, one for pfSense admin access (`pfsense_admin`) and one for guest access (`pfsense_guest`). You **must** create these exact same groups in both LLDAP and pfSense, then give them the correct permissions in pfSense.
|
||||
|
||||
### Bind Anonymous
|
||||
`Unchecked`
|
||||
|
||||
### Bind credentials
|
||||
|
||||
#### User DN
|
||||
|
||||
```
|
||||
uid=yourbinduser,ou=people,dc=example,dc=com
|
||||
```
|
||||
|
||||
It is recommended that you create a separate read-only user account (e.g, `readonly`) instead of `admin` for sharing Bind credentials with other services. The `readonly` should be a member of the `lldap_strict_readonly` group to limit access to your LDAP configuration in LLDAP.
|
||||
|
||||
#### Password
|
||||
|
||||
```
|
||||
LLDAPPasswordForBindUser
|
||||
```
|
||||
|
||||
### User naming attribute
|
||||
```
|
||||
uid
|
||||
```
|
||||
### Group naming attribute
|
||||
```
|
||||
cn
|
||||
```
|
||||
### Group member attribute
|
||||
```
|
||||
memberof
|
||||
```
|
||||
### RFC 2307 Groups
|
||||
`Unchecked`
|
||||
|
||||
### Group Object Class
|
||||
`groupOfUniqueNames`
|
||||
|
||||
### Shell Authentication Group DN
|
||||
`cn=pfsense_admin,ou=groups,dc=example,dc=com`
|
||||
|
||||
(This is only if you want to give a group shell access through LDAP. Leave blank and only the pfSense admin user will have shell access.
|
||||
|
||||
### Remaining Server Configuration
|
||||
|
||||
Enable the following options on the pfSense configuration page for your LLDAP server (the same page where you entered the prior configuration):
|
||||
|
||||
- UTF8 Encodes: `Checked`
|
||||
- Username Alterations: `Unchecked`
|
||||
- Allow unauthenticated bind: `Unchecked`
|
||||
|
||||
### Create pfSense Groups
|
||||
|
||||
Go to `System > User Manager > Groups` and create a new group(s) with the **same exact** name as the LLDAP group(s) used to authenticate users for pfSense (`pfsense_admin` and `pfsense_guest` in this example).
|
||||
|
||||
If you want your LLDAP users to have full administrator access in pfSense, then you need to edit the `Assigned Privileges` for the group and add the `WebCfg - All pages` system privilege. If you do not give any permissions to a group, you will be able to log in but only see an empty webUI.
|
||||
|
||||
### Enable LLDAP as an Authentication Option
|
||||
|
||||
Go to `System > User Manager > Settings` page. Add your LLDAP server configuration to the `Authentication Server` field. **The "Save & Test" Button will fail the test results at step 3. No clue why.**
|
||||
|
||||
## Testing LLDAP
|
||||
|
||||
pfSense includes a built-in feature for testing user authentication at `Diagnostics > Authentication`. Select your LLDAP server configuration in the `Authentication Server` to test logins for your LLDAP users. The groups (only the ones you added to pfSense) should show up when tested.
|
||||
|
||||
## More Information
|
||||
|
||||
Please read the [pfSense docs](https://docs.netgate.com/pfsense/en/latest/usermanager/ldap.html) for more information on LDAP configuration and managing access to pfSense.
|
||||
@@ -13,6 +13,6 @@ You setup https://zend.to/ for using LDAP by editing `/opt/zendto/config/prefere
|
||||
'authLDAPUsernameAttr' => 'uid',
|
||||
'authLDAPEmailAttr' => 'mail',
|
||||
'authLDAPMemberKey' => 'memberOf',
|
||||
'authLDAPMemberRole' => 'cn=zendto,ou=groups,dc=example,dc=com',
|
||||
'authLDAPMemberRole' => 'uid=zendto,ou=groups,dc=example,dc=com',
|
||||
```
|
||||
Every user of the group `zendto` is allowed to login.
|
||||
|
||||
@@ -93,17 +93,8 @@ database_url = "sqlite:///data/users.db?mode=rwc"
|
||||
## would still have to perform an (expensive) brute force attack to find
|
||||
## each password.
|
||||
## Randomly generated on first run if it doesn't exist.
|
||||
## Alternatively, you can use key_seed to override this instead of relying on
|
||||
## a file.
|
||||
## Env variable: LLDAP_KEY_FILE
|
||||
key_file = "/data/private_key"
|
||||
|
||||
## Seed to generate the server private key, see key_file above.
|
||||
## This can be any random string, the recommendation is that it's at least 12
|
||||
## characters long.
|
||||
## Env variable: LLDAP_KEY_SEED
|
||||
#key_seed = "RanD0m STR1ng"
|
||||
|
||||
## Ignored attributes.
|
||||
## Some services will request attributes that are not present in LLDAP. When it
|
||||
## is the case, LLDAP will warn about the attribute being unknown. If you want
|
||||
@@ -115,7 +106,7 @@ key_file = "/data/private_key"
|
||||
## Options to configure SMTP parameters, to send password reset emails.
|
||||
## To set these options from environment variables, use the following format
|
||||
## (example with "password"): LLDAP_SMTP_OPTIONS__PASSWORD
|
||||
[smtp_options]
|
||||
#[smtp_options]
|
||||
## Whether to enabled password reset via email, from LLDAP.
|
||||
#enable_password_reset=true
|
||||
## The SMTP server.
|
||||
@@ -137,7 +128,7 @@ key_file = "/data/private_key"
|
||||
## Options to configure LDAPS.
|
||||
## To set these options from environment variables, use the following format
|
||||
## (example with "port"): LLDAP_LDAPS_OPTIONS__PORT
|
||||
[ldaps_options]
|
||||
#[ldaps_options]
|
||||
## Whether to enable LDAPS.
|
||||
#enabled=true
|
||||
## Port on which to listen.
|
||||
|
||||
@@ -1,24 +1,20 @@
|
||||
[package]
|
||||
authors = ["Valentin Tolmer <valentin@tolmer.fr>"]
|
||||
description = "CLI migration tool to go from OpenLDAP to LLDAP"
|
||||
edition = "2021"
|
||||
homepage = "https://github.com/lldap/lldap"
|
||||
license = "GPL-3.0-only"
|
||||
name = "lldap_migration_tool"
|
||||
repository = "https://github.com/lldap/lldap"
|
||||
name = "migration-tool"
|
||||
version = "0.4.2"
|
||||
edition = "2021"
|
||||
authors = ["Valentin Tolmer <valentin@tolmer.fr>"]
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1"
|
||||
anyhow = "*"
|
||||
base64 = "0.13"
|
||||
rand = "0.8"
|
||||
requestty = "0.4.1"
|
||||
serde = "1"
|
||||
serde_json = "1"
|
||||
smallvec = "1"
|
||||
smallvec = "*"
|
||||
|
||||
[dependencies.lldap_auth]
|
||||
version = "0.3"
|
||||
path = "../auth"
|
||||
features = ["opaque_client"]
|
||||
|
||||
[dependencies.graphql_client]
|
||||
@@ -27,11 +23,11 @@ default-features = false
|
||||
version = "0.11"
|
||||
|
||||
[dependencies.reqwest]
|
||||
version = "0.11"
|
||||
version = "*"
|
||||
default-features = false
|
||||
features = ["json", "blocking", "rustls-tls"]
|
||||
|
||||
[dependencies.ldap3]
|
||||
version = "0.11"
|
||||
version = "*"
|
||||
default-features = false
|
||||
features = ["sync", "tls-rustls"]
|
||||
|
||||
@@ -1,14 +1,8 @@
|
||||
[package]
|
||||
authors = ["Valentin Tolmer <valentin@tolmer.fr>"]
|
||||
categories = ["authentication", "command-line-utilities"]
|
||||
description = "Super-simple and lightweight LDAP server"
|
||||
edition = "2021"
|
||||
homepage = "https://github.com/lldap/lldap"
|
||||
keywords = ["cli", "ldap", "graphql", "server", "authentication"]
|
||||
license = "GPL-3.0-only"
|
||||
name = "lldap"
|
||||
repository = "https://github.com/lldap/lldap"
|
||||
version = "0.5.0-alpha"
|
||||
version = "0.4.3-alpha"
|
||||
|
||||
[dependencies]
|
||||
actix = "0.13"
|
||||
@@ -19,45 +13,44 @@ actix-server = "2"
|
||||
actix-service = "2"
|
||||
actix-web = "4.3"
|
||||
actix-web-httpauth = "0.8"
|
||||
anyhow = "1"
|
||||
anyhow = "*"
|
||||
async-trait = "0.1"
|
||||
base64 = "0.21"
|
||||
bincode = "1.3"
|
||||
cron = "0.12"
|
||||
cron = "*"
|
||||
derive_builder = "0.12"
|
||||
figment_file_provider_adapter = "0.1"
|
||||
futures = "0.3"
|
||||
futures-util = "0.3"
|
||||
futures = "*"
|
||||
futures-util = "*"
|
||||
hmac = "0.12"
|
||||
http = "0.2"
|
||||
http = "*"
|
||||
itertools = "0.10"
|
||||
juniper = "0.15"
|
||||
jwt = "0.16"
|
||||
lber = "0.4.1"
|
||||
ldap3_proto = ">=0.3.1"
|
||||
log = "0.4"
|
||||
log = "*"
|
||||
orion = "0.17"
|
||||
rand_chacha = "0.3"
|
||||
rustls-pemfile = "1"
|
||||
serde = "1"
|
||||
serde_bytes = "0.11"
|
||||
rustls = "0.20"
|
||||
serde = "*"
|
||||
serde_json = "1"
|
||||
sha2 = "0.10"
|
||||
thiserror = "1"
|
||||
thiserror = "*"
|
||||
time = "0.3"
|
||||
tokio-rustls = "0.23"
|
||||
tokio-stream = "0.1"
|
||||
tokio-stream = "*"
|
||||
tokio-util = "0.7"
|
||||
tracing = "0.1"
|
||||
tracing = "*"
|
||||
tracing-actix-web = "0.7"
|
||||
tracing-attributes = "^0.1.21"
|
||||
tracing-log = "0.1"
|
||||
urlencoding = "2"
|
||||
webpki-roots = "0.23"
|
||||
tracing-log = "*"
|
||||
rustls-pemfile = "1"
|
||||
serde_bytes = "0.11"
|
||||
webpki-roots = "*"
|
||||
|
||||
[dependencies.chrono]
|
||||
features = ["serde"]
|
||||
version = "0.4"
|
||||
version = "*"
|
||||
|
||||
[dependencies.clap]
|
||||
features = ["std", "color", "suggestions", "derive", "env"]
|
||||
@@ -65,8 +58,7 @@ version = "4"
|
||||
|
||||
[dependencies.figment]
|
||||
features = ["env", "toml"]
|
||||
version = "0.10"
|
||||
|
||||
version = "*"
|
||||
[dependencies.tracing-subscriber]
|
||||
version = "0.3"
|
||||
features = ["env-filter", "tracing-log"]
|
||||
@@ -77,7 +69,7 @@ default-features = false
|
||||
version = "0.10.1"
|
||||
|
||||
[dependencies.lldap_auth]
|
||||
version = "0.3"
|
||||
path = "../auth"
|
||||
|
||||
[dependencies.opaque-ke]
|
||||
version = "0.6"
|
||||
@@ -88,7 +80,7 @@ version = "0.8"
|
||||
|
||||
[dependencies.secstr]
|
||||
features = ["serde"]
|
||||
version = "0.5"
|
||||
version = "*"
|
||||
|
||||
[dependencies.tokio]
|
||||
features = ["full"]
|
||||
@@ -96,7 +88,7 @@ version = "1.25"
|
||||
|
||||
[dependencies.uuid]
|
||||
features = ["v3"]
|
||||
version = "1"
|
||||
version = "*"
|
||||
|
||||
[dependencies.tracing-forest]
|
||||
features = ["smallvec", "chrono", "tokio"]
|
||||
@@ -121,35 +113,5 @@ version = "0.11"
|
||||
default-features = false
|
||||
features = ["rustls-tls-webpki-roots"]
|
||||
|
||||
[dependencies.rustls]
|
||||
version = "0.20"
|
||||
features = ["dangerous_configuration"]
|
||||
|
||||
[dev-dependencies]
|
||||
assert_cmd = "2.0"
|
||||
mockall = "0.11"
|
||||
nix = "0.26.2"
|
||||
|
||||
[dev-dependencies.graphql_client]
|
||||
features = ["graphql_query_derive", "reqwest-rustls"]
|
||||
default-features = false
|
||||
version = "0.11"
|
||||
|
||||
[dev-dependencies.ldap3]
|
||||
version = "0.11"
|
||||
default-features = false
|
||||
features = ["sync", "tls-rustls"]
|
||||
|
||||
[dev-dependencies.reqwest]
|
||||
version = "0.11"
|
||||
default-features = false
|
||||
features = ["json", "blocking", "rustls-tls"]
|
||||
|
||||
[dev-dependencies.serial_test]
|
||||
version = "2.0.0"
|
||||
default-features = false
|
||||
features = ["file_locks"]
|
||||
|
||||
[dev-dependencies.uuid]
|
||||
version = "1"
|
||||
features = ["v4"]
|
||||
|
||||
@@ -86,7 +86,7 @@ pub mod tests {
|
||||
handler
|
||||
.create_user(CreateUserRequest {
|
||||
user_id: UserId::new(name),
|
||||
email: format!("{}@bob.bob", name),
|
||||
email: "bob@bob.bob".to_string(),
|
||||
display_name: Some("display ".to_string() + name),
|
||||
first_name: Some("first ".to_string() + name),
|
||||
last_name: Some("last ".to_string() + name),
|
||||
|
||||
@@ -2,17 +2,15 @@ use crate::domain::{
|
||||
sql_tables::{DbConnection, SchemaVersion},
|
||||
types::{GroupId, UserId, Uuid},
|
||||
};
|
||||
use anyhow::Context;
|
||||
use itertools::Itertools;
|
||||
use sea_orm::{
|
||||
sea_query::{
|
||||
self, all, ColumnDef, Expr, ForeignKey, ForeignKeyAction, Func, Index, Query, Table, Value,
|
||||
},
|
||||
ConnectionTrait, FromQueryResult, Iden, Order, Statement, TransactionTrait,
|
||||
sea_query::{self, ColumnDef, Expr, ForeignKey, ForeignKeyAction, Query, Table, Value},
|
||||
ConnectionTrait, FromQueryResult, Iden, Statement, TransactionTrait,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tracing::{info, instrument, warn};
|
||||
|
||||
use super::sql_tables::LAST_SCHEMA_VERSION;
|
||||
|
||||
#[derive(Iden, PartialEq, Eq, Debug, Serialize, Deserialize, Clone, Copy)]
|
||||
pub enum Users {
|
||||
Table,
|
||||
@@ -462,136 +460,30 @@ async fn migrate_to_v3(pool: &DbConnection) -> anyhow::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn migrate_to_v4(pool: &DbConnection) -> anyhow::Result<()> {
|
||||
let builder = pool.get_database_backend();
|
||||
// Make emails and UUIDs unique.
|
||||
if let Err(e) = pool
|
||||
.execute(
|
||||
builder.build(
|
||||
Index::create()
|
||||
.if_not_exists()
|
||||
.name("unique-user-email")
|
||||
.table(Users::Table)
|
||||
.col(Users::Email)
|
||||
.unique(),
|
||||
),
|
||||
)
|
||||
.await
|
||||
.context(
|
||||
r#"while enforcing unicity on emails (2 users have the same email).
|
||||
|
||||
See https://github.com/lldap/lldap/blob/main/docs/migration_guides/v0.5.md for details.
|
||||
|
||||
"#,
|
||||
)
|
||||
{
|
||||
warn!("Found several users with the same email:");
|
||||
for (email, users) in &pool
|
||||
.query_all(
|
||||
builder.build(
|
||||
Query::select()
|
||||
.from(Users::Table)
|
||||
.columns([Users::Email, Users::UserId])
|
||||
.order_by_columns([(Users::Email, Order::Asc), (Users::UserId, Order::Asc)])
|
||||
.and_where(
|
||||
Expr::col(Users::Email).in_subquery(
|
||||
Query::select()
|
||||
.from(Users::Table)
|
||||
.column(Users::Email)
|
||||
.group_by_col(Users::Email)
|
||||
.cond_having(all![Expr::gt(
|
||||
Expr::expr(Func::count(Expr::col(Users::Email))),
|
||||
1
|
||||
)])
|
||||
.take(),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
.await
|
||||
.expect("Could not check duplicate users")
|
||||
.into_iter()
|
||||
.map(|row| {
|
||||
(
|
||||
row.try_get::<UserId>("", &Users::UserId.to_string())
|
||||
.unwrap(),
|
||||
row.try_get::<String>("", &Users::Email.to_string())
|
||||
.unwrap(),
|
||||
)
|
||||
})
|
||||
.group_by(|(_user, email)| email.to_owned())
|
||||
{
|
||||
warn!("Email: {email}");
|
||||
for (user, _email) in users {
|
||||
warn!(" User: {}", user.as_str());
|
||||
}
|
||||
}
|
||||
return Err(e);
|
||||
}
|
||||
pool.execute(
|
||||
builder.build(
|
||||
Index::create()
|
||||
.if_not_exists()
|
||||
.name("unique-user-uuid")
|
||||
.table(Users::Table)
|
||||
.col(Users::Uuid)
|
||||
.unique(),
|
||||
),
|
||||
)
|
||||
.await
|
||||
.context("while enforcing unicity on user UUIDs (2 users have the same UUID)")?;
|
||||
pool.execute(
|
||||
builder.build(
|
||||
Index::create()
|
||||
.if_not_exists()
|
||||
.name("unique-group-uuid")
|
||||
.table(Groups::Table)
|
||||
.col(Groups::Uuid)
|
||||
.unique(),
|
||||
),
|
||||
)
|
||||
.await
|
||||
.context("while enforcing unicity on group UUIDs (2 groups have the same UUID)")?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// This is needed to make an array of async functions.
|
||||
macro_rules! to_sync {
|
||||
($l:ident) => {
|
||||
|pool| -> std::pin::Pin<Box<dyn std::future::Future<Output = anyhow::Result<()>>>> {
|
||||
Box::pin($l(pool))
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub async fn migrate_from_version(
|
||||
pool: &DbConnection,
|
||||
version: SchemaVersion,
|
||||
last_version: SchemaVersion,
|
||||
) -> anyhow::Result<()> {
|
||||
match version.cmp(&last_version) {
|
||||
std::cmp::Ordering::Less => (),
|
||||
match version.cmp(&LAST_SCHEMA_VERSION) {
|
||||
std::cmp::Ordering::Less => info!(
|
||||
"Upgrading DB schema from {} to {}",
|
||||
version.0, LAST_SCHEMA_VERSION.0
|
||||
),
|
||||
std::cmp::Ordering::Equal => return Ok(()),
|
||||
std::cmp::Ordering::Greater => anyhow::bail!("DB version downgrading is not supported"),
|
||||
}
|
||||
info!("Upgrading DB schema from version {}", version.0);
|
||||
let migrations = [
|
||||
to_sync!(migrate_to_v2),
|
||||
to_sync!(migrate_to_v3),
|
||||
to_sync!(migrate_to_v4),
|
||||
];
|
||||
for migration in 2..=4 {
|
||||
if version < SchemaVersion(migration) && SchemaVersion(migration) <= last_version {
|
||||
info!("Upgrading DB schema to version {}", migration);
|
||||
migrations[(migration - 2) as usize](pool).await?;
|
||||
}
|
||||
if version < SchemaVersion(2) {
|
||||
migrate_to_v2(pool).await?;
|
||||
}
|
||||
if version < SchemaVersion(3) {
|
||||
migrate_to_v3(pool).await?;
|
||||
}
|
||||
let builder = pool.get_database_backend();
|
||||
pool.execute(
|
||||
builder.build(
|
||||
Query::update()
|
||||
.table(Metadata::Table)
|
||||
.value(Metadata::Version, Value::from(last_version)),
|
||||
.value(Metadata::Version, Value::from(LAST_SCHEMA_VERSION)),
|
||||
),
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -21,7 +21,7 @@ impl From<SchemaVersion> for Value {
|
||||
}
|
||||
}
|
||||
|
||||
const LAST_SCHEMA_VERSION: SchemaVersion = SchemaVersion(4);
|
||||
pub const LAST_SCHEMA_VERSION: SchemaVersion = SchemaVersion(3);
|
||||
|
||||
pub async fn init_table(pool: &DbConnection) -> anyhow::Result<()> {
|
||||
let version = {
|
||||
@@ -32,7 +32,7 @@ pub async fn init_table(pool: &DbConnection) -> anyhow::Result<()> {
|
||||
SchemaVersion(1)
|
||||
}
|
||||
};
|
||||
migrate_from_version(pool, version, LAST_SCHEMA_VERSION).await?;
|
||||
migrate_from_version(pool, version).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -46,7 +46,6 @@ mod tests {
|
||||
use super::*;
|
||||
use chrono::prelude::*;
|
||||
use sea_orm::{ConnectionTrait, Database, DbBackend, FromQueryResult};
|
||||
use tracing::error;
|
||||
|
||||
async fn get_in_memory_db() -> DbConnection {
|
||||
let mut sql_opt = sea_orm::ConnectOptions::new("sqlite::memory:".to_owned());
|
||||
@@ -101,21 +100,21 @@ mod tests {
|
||||
let sql_pool = get_in_memory_db().await;
|
||||
sql_pool
|
||||
.execute(raw_statement(
|
||||
r#"CREATE TABLE users ( user_id TEXT, display_name TEXT, first_name TEXT NOT NULL, last_name TEXT, avatar BLOB, creation_date TEXT, email TEXT);"#,
|
||||
r#"CREATE TABLE users ( user_id TEXT, display_name TEXT, first_name TEXT NOT NULL, last_name TEXT, avatar BLOB, creation_date TEXT);"#,
|
||||
))
|
||||
.await
|
||||
.unwrap();
|
||||
sql_pool
|
||||
.execute(raw_statement(
|
||||
r#"INSERT INTO users (user_id, display_name, first_name, creation_date, email)
|
||||
VALUES ("bôb", "", "", "1970-01-01 00:00:00", "bob@bob.com")"#,
|
||||
r#"INSERT INTO users (user_id, display_name, first_name, creation_date)
|
||||
VALUES ("bôb", "", "", "1970-01-01 00:00:00")"#,
|
||||
))
|
||||
.await
|
||||
.unwrap();
|
||||
sql_pool
|
||||
.execute(raw_statement(
|
||||
r#"INSERT INTO users (user_id, display_name, first_name, creation_date, email)
|
||||
VALUES ("john", "John Doe", "John", "1971-01-01 00:00:00", "bob2@bob.com")"#,
|
||||
r#"INSERT INTO users (user_id, display_name, first_name, creation_date)
|
||||
VALUES ("john", "John Doe", "John", "1971-01-01 00:00:00")"#,
|
||||
))
|
||||
.await
|
||||
.unwrap();
|
||||
@@ -207,69 +206,6 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_migration_to_v4() {
|
||||
crate::infra::logging::init_for_tests();
|
||||
let sql_pool = get_in_memory_db().await;
|
||||
upgrade_to_v1(&sql_pool).await.unwrap();
|
||||
migrate_from_version(&sql_pool, SchemaVersion(1), SchemaVersion(3))
|
||||
.await
|
||||
.unwrap();
|
||||
sql_pool
|
||||
.execute(raw_statement(
|
||||
r#"INSERT INTO users (user_id, email, display_name, first_name, creation_date, uuid)
|
||||
VALUES ("bob", "bob@bob.com", "", "", "1970-01-01 00:00:00", "a02eaf13-48a7-30f6-a3d4-040ff7c52b04")"#,
|
||||
))
|
||||
.await
|
||||
.unwrap();
|
||||
sql_pool
|
||||
.execute(raw_statement(
|
||||
r#"INSERT INTO users (user_id, email, display_name, first_name, creation_date, uuid)
|
||||
VALUES ("bob2", "bob@bob.com", "", "", "1970-01-01 00:00:00", "986765a5-3f03-389e-b47b-536b2d6e1bec")"#,
|
||||
))
|
||||
.await
|
||||
.unwrap();
|
||||
error!(
|
||||
"{}",
|
||||
migrate_from_version(&sql_pool, SchemaVersion(3), SchemaVersion(4))
|
||||
.await
|
||||
.expect_err("migration should fail")
|
||||
);
|
||||
assert_eq!(
|
||||
sql_migrations::JustSchemaVersion::find_by_statement(raw_statement(
|
||||
r#"SELECT version FROM metadata"#
|
||||
))
|
||||
.one(&sql_pool)
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap(),
|
||||
sql_migrations::JustSchemaVersion {
|
||||
version: SchemaVersion(3)
|
||||
}
|
||||
);
|
||||
sql_pool
|
||||
.execute(raw_statement(
|
||||
r#"UPDATE users SET email = "new@bob.com" WHERE user_id = "bob2""#,
|
||||
))
|
||||
.await
|
||||
.unwrap();
|
||||
migrate_from_version(&sql_pool, SchemaVersion(3), SchemaVersion(4))
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
sql_migrations::JustSchemaVersion::find_by_statement(raw_statement(
|
||||
r#"SELECT version FROM metadata"#
|
||||
))
|
||||
.one(&sql_pool)
|
||||
.await
|
||||
.unwrap()
|
||||
.unwrap(),
|
||||
sql_migrations::JustSchemaVersion {
|
||||
version: SchemaVersion(4)
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_too_high_version() {
|
||||
let sql_pool = get_in_memory_db().await;
|
||||
|
||||
@@ -1,21 +1,22 @@
|
||||
use std::collections::{hash_map::DefaultHasher, HashSet};
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
use std::task::Poll;
|
||||
|
||||
use actix_web::{
|
||||
cookie::{Cookie, SameSite},
|
||||
dev::{Service, ServiceRequest, ServiceResponse, Transform},
|
||||
error::{ErrorBadRequest, ErrorUnauthorized},
|
||||
web, HttpRequest, HttpResponse,
|
||||
web, FromRequest, HttpRequest, HttpResponse,
|
||||
};
|
||||
use actix_web_httpauth::extractors::bearer::BearerAuth;
|
||||
use anyhow::Result;
|
||||
use anyhow::{bail, Context, Result};
|
||||
use chrono::prelude::*;
|
||||
use futures::future::{ok, Ready};
|
||||
use futures_util::FutureExt;
|
||||
use hmac::Hmac;
|
||||
use jwt::{SignWithKey, VerifyWithKey};
|
||||
use secstr::SecUtf8;
|
||||
use sha2::Sha512;
|
||||
use time::ext::NumericalDuration;
|
||||
use tracing::{debug, info, instrument, warn};
|
||||
@@ -205,6 +206,24 @@ where
|
||||
.unwrap_or_else(error_to_http_response)
|
||||
}
|
||||
|
||||
async fn check_password_reset_token<'a, Backend>(
|
||||
backend_handler: &Backend,
|
||||
token: &Option<&'a str>,
|
||||
) -> TcpResult<Option<(&'a str, UserId)>>
|
||||
where
|
||||
Backend: TcpBackendHandler + 'static,
|
||||
{
|
||||
let token = match token {
|
||||
None => return Ok(None),
|
||||
Some(token) => token,
|
||||
};
|
||||
let user_id = backend_handler
|
||||
.get_user_id_for_password_reset_token(token)
|
||||
.await
|
||||
.map_err(|_| TcpError::UnauthorizedError("Invalid or expired token".to_string()))?;
|
||||
Ok(Some((token, user_id)))
|
||||
}
|
||||
|
||||
#[instrument(skip_all, level = "debug")]
|
||||
async fn get_password_reset_step2<Backend>(
|
||||
data: web::Data<AppState<Backend>>,
|
||||
@@ -213,22 +232,12 @@ async fn get_password_reset_step2<Backend>(
|
||||
where
|
||||
Backend: TcpBackendHandler + BackendHandler + 'static,
|
||||
{
|
||||
let token = request
|
||||
.match_info()
|
||||
.get("token")
|
||||
.ok_or_else(|| TcpError::BadRequest("Missing reset token".to_owned()))?;
|
||||
let user_id = data
|
||||
.get_tcp_handler()
|
||||
.get_user_id_for_password_reset_token(token)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
debug!("Reset token error: {e:#}");
|
||||
TcpError::NotFoundError("Wrong or expired reset token".to_owned())
|
||||
})?;
|
||||
let _ = data
|
||||
.get_tcp_handler()
|
||||
.delete_password_reset_token(token)
|
||||
.await;
|
||||
let tcp_handler = data.get_tcp_handler();
|
||||
let (token, user_id) =
|
||||
check_password_reset_token(tcp_handler, &request.match_info().get("token"))
|
||||
.await?
|
||||
.ok_or_else(|| TcpError::BadRequest("Missing token".to_string()))?;
|
||||
let _ = tcp_handler.delete_password_reset_token(token).await;
|
||||
let groups = HashSet::new();
|
||||
let token = create_jwt(&data.jwt_key, user_id.to_string(), groups);
|
||||
Ok(HttpResponse::Ok()
|
||||
@@ -403,6 +412,7 @@ where
|
||||
Backend: TcpBackendHandler + BackendHandler + OpaqueHandler + LoginHandler + 'static,
|
||||
{
|
||||
let user_id = UserId::new(&request.username);
|
||||
debug!(?user_id);
|
||||
let bind_request = BindRequest {
|
||||
name: user_id.clone(),
|
||||
password: request.password.clone(),
|
||||
@@ -449,6 +459,115 @@ where
|
||||
.unwrap_or_else(error_to_http_response)
|
||||
}
|
||||
|
||||
// Parse the response from the HaveIBeenPwned API. Sample response:
|
||||
//
|
||||
// 0018A45C4D1DEF81644B54AB7F969B88D65:1
|
||||
// 00D4F6E8FA6EECAD2A3AA415EEC418D38EC:2
|
||||
// 011053FD0102E94D6AE2F8B83D76FAF94F6:13
|
||||
fn parse_hash_list(response: &str) -> Result<password_reset::PasswordHashList> {
|
||||
use password_reset::*;
|
||||
let parse_line = |line: &str| -> Result<PasswordHashCount> {
|
||||
let split = line.trim().split(':').collect::<Vec<_>>();
|
||||
if let [hash, count] = &split[..] {
|
||||
if hash.len() == 35 {
|
||||
if let Ok(count) = str::parse::<u64>(count) {
|
||||
return Ok(PasswordHashCount {
|
||||
hash: hash.to_string(),
|
||||
count,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
bail!("Invalid password hash from API: {}", line)
|
||||
};
|
||||
Ok(PasswordHashList {
|
||||
hashes: response
|
||||
.split('\n')
|
||||
.map(parse_line)
|
||||
.collect::<Result<Vec<_>>>()?,
|
||||
})
|
||||
}
|
||||
|
||||
// TODO: Refactor that for testing.
|
||||
async fn get_password_hash_list(
|
||||
hash: &str,
|
||||
api_key: &SecUtf8,
|
||||
) -> Result<password_reset::PasswordHashList> {
|
||||
use reqwest::*;
|
||||
let client = Client::new();
|
||||
let resp = client
|
||||
.get(format!("https://api.pwnedpasswords.com/range/{}", hash))
|
||||
.header(header::USER_AGENT, "LLDAP")
|
||||
.header("hibp-api-key", api_key.unsecure())
|
||||
.send()
|
||||
.await
|
||||
.context("Could not get response from HIBP")?
|
||||
.text()
|
||||
.await?;
|
||||
parse_hash_list(&resp).context("Invalid HIBP response")
|
||||
}
|
||||
|
||||
async fn check_password_pwned<Backend>(
|
||||
data: web::Data<AppState<Backend>>,
|
||||
request: HttpRequest,
|
||||
payload: web::Payload,
|
||||
) -> TcpResult<HttpResponse>
|
||||
where
|
||||
Backend: TcpBackendHandler + BackendHandler + OpaqueHandler + 'static,
|
||||
{
|
||||
let has_reset_token = check_password_reset_token(
|
||||
data.get_tcp_handler(),
|
||||
&request
|
||||
.headers()
|
||||
.get("reset-token")
|
||||
.map(|v| v.to_str().unwrap()),
|
||||
)
|
||||
.await?
|
||||
.is_some();
|
||||
let inner_payload = &mut payload.into_inner();
|
||||
if !has_reset_token
|
||||
&& BearerAuth::from_request(&request, inner_payload)
|
||||
.await
|
||||
.ok()
|
||||
.and_then(|bearer| check_if_token_is_valid(&data, bearer.token()).ok())
|
||||
.is_none()
|
||||
{
|
||||
return Err(TcpError::UnauthorizedError(
|
||||
"No token or invalid token".to_string(),
|
||||
));
|
||||
}
|
||||
if data.hibp_api_key.unsecure().is_empty() {
|
||||
return Err(TcpError::NotImplemented("No HIBP API key".to_string()));
|
||||
}
|
||||
let hash = request
|
||||
.match_info()
|
||||
.get("hash")
|
||||
.ok_or_else(|| TcpError::BadRequest("Missing hash".to_string()))?;
|
||||
if hash.len() != 5 || !hash.chars().all(|c| c.is_ascii_hexdigit()) {
|
||||
return Err(TcpError::BadRequest(format!(
|
||||
"Bad request: invalid hash format \"{}\"",
|
||||
hash
|
||||
)));
|
||||
}
|
||||
get_password_hash_list(hash, &data.hibp_api_key)
|
||||
.await
|
||||
.map(|hashes| HttpResponse::Ok().json(hashes))
|
||||
.map_err(|e| TcpError::InternalServerError(e.to_string()))
|
||||
}
|
||||
|
||||
async fn check_password_pwned_handler<Backend>(
|
||||
data: web::Data<AppState<Backend>>,
|
||||
request: HttpRequest,
|
||||
payload: web::Payload,
|
||||
) -> HttpResponse
|
||||
where
|
||||
Backend: TcpBackendHandler + BackendHandler + OpaqueHandler + 'static,
|
||||
{
|
||||
check_password_pwned(data, request, payload)
|
||||
.await
|
||||
.unwrap_or_else(error_to_http_response)
|
||||
}
|
||||
|
||||
#[instrument(skip_all, level = "debug")]
|
||||
async fn opaque_register_start<Backend>(
|
||||
request: actix_web::HttpRequest,
|
||||
@@ -565,7 +684,7 @@ where
|
||||
#[allow(clippy::type_complexity)]
|
||||
type Future = Pin<Box<dyn core::future::Future<Output = Result<Self::Response, Self::Error>>>>;
|
||||
|
||||
fn poll_ready(&self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
fn poll_ready(&self, cx: &mut std::task::Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
self.service.poll_ready(cx)
|
||||
}
|
||||
|
||||
@@ -636,6 +755,11 @@ where
|
||||
web::resource("/simple/login").route(web::post().to(simple_login_handler::<Backend>)),
|
||||
)
|
||||
.service(web::resource("/refresh").route(web::get().to(get_refresh_handler::<Backend>)))
|
||||
.service(
|
||||
web::resource("/password/check/{hash}")
|
||||
.wrap(CookieToHeaderTranslatorFactory)
|
||||
.route(web::get().to(check_password_pwned_handler::<Backend>)),
|
||||
)
|
||||
.service(web::resource("/logout").route(web::get().to(get_logout_handler::<Backend>)))
|
||||
.service(
|
||||
web::scope("/opaque/register")
|
||||
|
||||
@@ -54,16 +54,9 @@ pub struct RunOpts {
|
||||
|
||||
/// Path to the file that contains the private server key.
|
||||
/// It will be created if it doesn't exist.
|
||||
/// Alternatively, you can set `server_key_seed`. If `server_key_seed` is given,
|
||||
/// `server_key_file` will be ignored.
|
||||
#[clap(long, env = "LLDAP_SERVER_KEY_FILE")]
|
||||
pub server_key_file: Option<String>,
|
||||
|
||||
/// Seed used to generate the private server key.
|
||||
/// Takes precedence over `server_key_file`.
|
||||
#[clap(long, env = "LLDAP_SERVER_KEY_SEED")]
|
||||
pub server_key_seed: Option<String>,
|
||||
|
||||
/// Change ldap host. Default: "0.0.0.0"
|
||||
#[clap(long, env = "LLDAP_LDAP_HOST")]
|
||||
pub ldap_host: Option<String>,
|
||||
@@ -88,6 +81,10 @@ pub struct RunOpts {
|
||||
#[clap(short, long, env = "LLDAP_DATABASE_URL")]
|
||||
pub database_url: Option<String>,
|
||||
|
||||
/// HaveIBeenPwned API key, to check passwords against leaks.
|
||||
#[clap(long, env = "LLDAP_HIBP_API_KEY")]
|
||||
pub hibp_api_key: Option<String>,
|
||||
|
||||
#[clap(flatten)]
|
||||
pub smtp_opts: SmtpOpts,
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ use serde::{Deserialize, Serialize};
|
||||
pub struct MailOptions {
|
||||
#[builder(default = "false")]
|
||||
pub enable_password_reset: bool,
|
||||
#[builder(default)]
|
||||
#[builder(default = "None")]
|
||||
pub from: Option<Mailbox>,
|
||||
#[builder(default = "None")]
|
||||
pub reply_to: Option<Mailbox>,
|
||||
@@ -25,7 +25,7 @@ pub struct MailOptions {
|
||||
pub server: String,
|
||||
#[builder(default = "587")]
|
||||
pub port: u16,
|
||||
#[builder(default)]
|
||||
#[builder(default = r#"String::default()"#)]
|
||||
pub user: String,
|
||||
#[builder(default = r#"SecUtf8::from("")"#)]
|
||||
pub password: SecUtf8,
|
||||
@@ -78,7 +78,7 @@ pub struct Configuration {
|
||||
pub ldap_base_dn: String,
|
||||
#[builder(default = r#"UserId::new("admin")"#)]
|
||||
pub ldap_user_dn: UserId,
|
||||
#[builder(default)]
|
||||
#[builder(default = r#"String::default()"#)]
|
||||
pub ldap_user_email: String,
|
||||
#[builder(default = r#"SecUtf8::from("password")"#)]
|
||||
pub ldap_user_pass: SecUtf8,
|
||||
@@ -92,16 +92,14 @@ pub struct Configuration {
|
||||
pub verbose: bool,
|
||||
#[builder(default = r#"String::from("server_key")"#)]
|
||||
pub key_file: String,
|
||||
// We want an Option to see whether there is a value or not, since the value is printed as
|
||||
// "***SECRET***".
|
||||
#[builder(default)]
|
||||
pub key_seed: Option<SecUtf8>,
|
||||
#[builder(default)]
|
||||
pub smtp_options: MailOptions,
|
||||
#[builder(default)]
|
||||
pub ldaps_options: LdapsOptions,
|
||||
#[builder(default = r#"String::from("http://localhost")"#)]
|
||||
pub http_url: String,
|
||||
#[builder(default = r#"SecUtf8::from("")"#)]
|
||||
pub hibp_api_key: SecUtf8,
|
||||
#[serde(skip)]
|
||||
#[builder(field(private), default = "None")]
|
||||
server_setup: Option<ServerSetup>,
|
||||
@@ -115,14 +113,7 @@ impl std::default::Default for Configuration {
|
||||
|
||||
impl ConfigurationBuilder {
|
||||
pub fn build(self) -> Result<Configuration> {
|
||||
let server_setup = get_server_setup(
|
||||
self.key_file.as_deref().unwrap_or("server_key"),
|
||||
self.key_seed
|
||||
.as_ref()
|
||||
.and_then(|o| o.as_ref())
|
||||
.map(SecUtf8::unsecure)
|
||||
.unwrap_or_default(),
|
||||
)?;
|
||||
let server_setup = get_server_setup(self.key_file.as_deref().unwrap_or("server_key"))?;
|
||||
Ok(self.server_setup(Some(server_setup)).private_build()?)
|
||||
}
|
||||
|
||||
@@ -165,25 +156,10 @@ fn write_to_readonly_file(path: &std::path::Path, buffer: &[u8]) -> Result<()> {
|
||||
Ok(file.write_all(buffer)?)
|
||||
}
|
||||
|
||||
fn get_server_setup(file_path: &str, key_seed: &str) -> Result<ServerSetup> {
|
||||
fn get_server_setup(file_path: &str) -> Result<ServerSetup> {
|
||||
use std::fs::read;
|
||||
let path = std::path::Path::new(file_path);
|
||||
if !key_seed.is_empty() {
|
||||
if file_path != "server_key" || path.exists() {
|
||||
eprintln!("WARNING: A key_seed was given, we will ignore the server_key and generate one from the seed!");
|
||||
} else {
|
||||
println!("Got a key_seed, ignoring key_file");
|
||||
}
|
||||
let hash = |val: &[u8]| -> [u8; 32] {
|
||||
use sha2::{Digest, Sha256};
|
||||
let mut seed_hasher = Sha256::new();
|
||||
seed_hasher.update(val);
|
||||
seed_hasher.finalize().into()
|
||||
};
|
||||
use rand::SeedableRng;
|
||||
let mut rng = rand_chacha::ChaCha20Rng::from_seed(hash(key_seed.as_bytes()));
|
||||
Ok(ServerSetup::new(&mut rng))
|
||||
} else if path.exists() {
|
||||
if path.exists() {
|
||||
let bytes = read(file_path).context(format!("Could not read key file `{}`", file_path))?;
|
||||
Ok(ServerSetup::deserialize(&bytes)?)
|
||||
} else {
|
||||
@@ -224,10 +200,6 @@ impl ConfigOverrider for RunOpts {
|
||||
config.key_file = path.to_string();
|
||||
}
|
||||
|
||||
if let Some(seed) = self.server_key_seed.as_ref() {
|
||||
config.key_seed = Some(SecUtf8::from(seed));
|
||||
}
|
||||
|
||||
if let Some(port) = self.ldap_port {
|
||||
config.ldap_port = port;
|
||||
}
|
||||
@@ -243,6 +215,10 @@ impl ConfigOverrider for RunOpts {
|
||||
if let Some(database_url) = self.database_url.as_ref() {
|
||||
config.database_url = database_url.to_string();
|
||||
}
|
||||
|
||||
if let Some(api_key) = self.hibp_api_key.as_ref() {
|
||||
config.hibp_api_key = SecUtf8::from(api_key.clone());
|
||||
}
|
||||
self.smtp_opts.override_config(config);
|
||||
self.ldaps_opts.override_config(config);
|
||||
}
|
||||
@@ -336,14 +312,7 @@ where
|
||||
if config.verbose {
|
||||
println!("Configuration: {:#?}", &config);
|
||||
}
|
||||
config.server_setup = Some(get_server_setup(
|
||||
&config.key_file,
|
||||
config
|
||||
.key_seed
|
||||
.as_ref()
|
||||
.map(SecUtf8::unsecure)
|
||||
.unwrap_or_default(),
|
||||
)?);
|
||||
config.server_setup = Some(get_server_setup(&config.key_file)?);
|
||||
if config.jwt_secret == SecUtf8::from("secretjwtsecret") {
|
||||
println!("WARNING: Default JWT secret used! This is highly unsafe and can allow attackers to log in as admin.");
|
||||
}
|
||||
@@ -355,29 +324,3 @@ where
|
||||
}
|
||||
Ok(config)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn check_generated_server_key() {
|
||||
assert_eq!(
|
||||
bincode::serialize(&get_server_setup("/doesnt/exist", "key seed").unwrap()).unwrap(),
|
||||
[
|
||||
255, 206, 202, 50, 247, 13, 59, 191, 69, 244, 148, 187, 150, 227, 12, 250, 20, 207,
|
||||
211, 151, 147, 33, 107, 132, 2, 252, 121, 94, 97, 6, 97, 232, 163, 168, 86, 246,
|
||||
249, 186, 31, 204, 59, 75, 65, 134, 108, 159, 15, 70, 246, 250, 150, 195, 54, 197,
|
||||
195, 176, 150, 200, 157, 119, 13, 173, 119, 8, 32, 0, 0, 0, 0, 0, 0, 0, 248, 123,
|
||||
35, 91, 194, 51, 52, 57, 191, 210, 68, 227, 107, 166, 232, 37, 195, 244, 100, 84,
|
||||
88, 212, 190, 12, 195, 57, 83, 72, 127, 189, 179, 16, 32, 0, 0, 0, 0, 0, 0, 0, 128,
|
||||
112, 60, 207, 205, 69, 67, 73, 24, 175, 187, 62, 16, 45, 59, 136, 78, 40, 187, 54,
|
||||
159, 94, 116, 33, 133, 119, 231, 43, 199, 164, 141, 7, 32, 0, 0, 0, 0, 0, 0, 0,
|
||||
212, 134, 53, 203, 131, 24, 138, 211, 162, 28, 23, 233, 251, 82, 34, 66, 98, 12,
|
||||
249, 205, 35, 208, 241, 50, 128, 131, 46, 189, 211, 51, 56, 109, 32, 0, 0, 0, 0, 0,
|
||||
0, 0, 84, 20, 147, 25, 50, 5, 243, 203, 216, 180, 175, 121, 159, 96, 123, 183, 146,
|
||||
251, 22, 44, 98, 168, 67, 224, 255, 139, 159, 25, 24, 254, 88, 3
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,12 +124,10 @@ impl<Handler: BackendHandler> Query<Handler> {
|
||||
}
|
||||
|
||||
pub async fn user(context: &Context<Handler>, user_id: String) -> FieldResult<User<Handler>> {
|
||||
use anyhow::Context;
|
||||
let span = debug_span!("[GraphQL query] user");
|
||||
span.in_scope(|| {
|
||||
debug!(?user_id);
|
||||
});
|
||||
let user_id = urlencoding::decode(&user_id).context("Invalid user parameter")?;
|
||||
let user_id = UserId::new(&user_id);
|
||||
let handler = context
|
||||
.get_readable_handler(&user_id)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::infra::{configuration::LdapsOptions, ldap_server::read_certificates};
|
||||
use crate::infra::configuration::LdapsOptions;
|
||||
use anyhow::{anyhow, bail, ensure, Context, Result};
|
||||
use futures_util::SinkExt;
|
||||
use ldap3_proto::{
|
||||
@@ -65,7 +65,6 @@ where
|
||||
invalid_answer
|
||||
);
|
||||
info!("Success");
|
||||
resp.close().await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -86,44 +85,15 @@ fn get_root_certificates() -> rustls::RootCertStore {
|
||||
root_store
|
||||
}
|
||||
|
||||
fn get_tls_connector(ldaps_options: &LdapsOptions) -> Result<RustlsTlsConnector> {
|
||||
let mut client_config = rustls::ClientConfig::builder()
|
||||
.with_safe_defaults()
|
||||
.with_root_certificates(get_root_certificates())
|
||||
.with_no_client_auth();
|
||||
let (certs, _private_key) = read_certificates(ldaps_options)?;
|
||||
// Check that the server cert is the one in the config file.
|
||||
struct CertificateVerifier {
|
||||
certificate: rustls::Certificate,
|
||||
certificate_path: String,
|
||||
}
|
||||
impl rustls::client::ServerCertVerifier for CertificateVerifier {
|
||||
fn verify_server_cert(
|
||||
&self,
|
||||
end_entity: &rustls::Certificate,
|
||||
_intermediates: &[rustls::Certificate],
|
||||
_server_name: &rustls::ServerName,
|
||||
_scts: &mut dyn Iterator<Item = &[u8]>,
|
||||
_ocsp_response: &[u8],
|
||||
_now: std::time::SystemTime,
|
||||
) -> std::result::Result<rustls::client::ServerCertVerified, rustls::Error> {
|
||||
if end_entity != &self.certificate {
|
||||
return Err(rustls::Error::InvalidCertificateData(format!(
|
||||
"Server certificate doesn't match the one in the config file {}",
|
||||
&self.certificate_path
|
||||
)));
|
||||
}
|
||||
Ok(rustls::client::ServerCertVerified::assertion())
|
||||
}
|
||||
}
|
||||
let mut dangerous_config = rustls::client::DangerousClientConfig {
|
||||
cfg: &mut client_config,
|
||||
};
|
||||
dangerous_config.set_certificate_verifier(std::sync::Arc::new(CertificateVerifier {
|
||||
certificate: certs.first().expect("empty certificate chain").clone(),
|
||||
certificate_path: ldaps_options.cert_file.clone(),
|
||||
}));
|
||||
Ok(std::sync::Arc::new(client_config).into())
|
||||
fn get_tls_connector() -> Result<RustlsTlsConnector> {
|
||||
use rustls::ClientConfig;
|
||||
let client_config = std::sync::Arc::new(
|
||||
ClientConfig::builder()
|
||||
.with_safe_defaults()
|
||||
.with_root_certificates(get_root_certificates())
|
||||
.with_no_client_auth(),
|
||||
);
|
||||
Ok(client_config.into())
|
||||
}
|
||||
|
||||
#[instrument(skip_all, level = "info", err)]
|
||||
@@ -132,20 +102,15 @@ pub async fn check_ldaps(ldaps_options: &LdapsOptions) -> Result<()> {
|
||||
info!("LDAPS not enabled");
|
||||
return Ok(());
|
||||
};
|
||||
let tls_connector =
|
||||
get_tls_connector(ldaps_options).context("while preparing the tls connection")?;
|
||||
let tls_connector = get_tls_connector()?;
|
||||
let url = format!("localhost:{}", ldaps_options.port);
|
||||
check_ldap_endpoint(
|
||||
tls_connector
|
||||
.connect(
|
||||
rustls::ServerName::try_from("localhost")
|
||||
.context("while parsing the server name")?,
|
||||
TcpStream::connect(&url)
|
||||
.await
|
||||
.context("while connecting TCP")?,
|
||||
rustls::ServerName::try_from(url.as_str())?,
|
||||
TcpStream::connect(&url).await?,
|
||||
)
|
||||
.await
|
||||
.context("while connecting TLS")?,
|
||||
.await?,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
@@ -4,8 +4,7 @@ use crate::{
|
||||
opaque_handler::OpaqueHandler,
|
||||
},
|
||||
infra::{
|
||||
access_control::AccessControlledBackendHandler,
|
||||
configuration::{Configuration, LdapsOptions},
|
||||
access_control::AccessControlledBackendHandler, configuration::Configuration,
|
||||
ldap_handler::LdapHandler,
|
||||
},
|
||||
};
|
||||
@@ -95,7 +94,7 @@ where
|
||||
}
|
||||
|
||||
fn read_private_key(key_file: &str) -> Result<PrivateKey> {
|
||||
use rustls_pemfile::{ec_private_keys, pkcs8_private_keys, rsa_private_keys};
|
||||
use rustls_pemfile::{pkcs8_private_keys, rsa_private_keys};
|
||||
use std::{fs::File, io::BufReader};
|
||||
pkcs8_private_keys(&mut BufReader::new(File::open(key_file)?))
|
||||
.map_err(anyhow::Error::from)
|
||||
@@ -113,36 +112,29 @@ fn read_private_key(key_file: &str) -> Result<PrivateKey> {
|
||||
.ok_or_else(|| anyhow!("No PKCS1 key"))
|
||||
})
|
||||
})
|
||||
.or_else(|_| {
|
||||
ec_private_keys(&mut BufReader::new(File::open(key_file)?))
|
||||
.map_err(anyhow::Error::from)
|
||||
.and_then(|keys| keys.into_iter().next().ok_or_else(|| anyhow!("No EC key")))
|
||||
})
|
||||
.with_context(|| {
|
||||
format!(
|
||||
"Cannot read either PKCS1, PKCS8 or EC private key from {}",
|
||||
"Cannot read either PKCS1 or PKCS8 private key from {}",
|
||||
key_file
|
||||
)
|
||||
})
|
||||
.map(rustls::PrivateKey)
|
||||
}
|
||||
|
||||
pub fn read_certificates(
|
||||
ldaps_options: &LdapsOptions,
|
||||
) -> Result<(Vec<rustls::Certificate>, rustls::PrivateKey)> {
|
||||
fn get_tls_acceptor(config: &Configuration) -> Result<RustlsTlsAcceptor> {
|
||||
use rustls::{Certificate, ServerConfig};
|
||||
use rustls_pemfile::certs;
|
||||
use std::{fs::File, io::BufReader};
|
||||
let certs = rustls_pemfile::certs(&mut BufReader::new(File::open(&ldaps_options.cert_file)?))?
|
||||
.into_iter()
|
||||
.map(rustls::Certificate)
|
||||
.collect::<Vec<_>>();
|
||||
let private_key = read_private_key(&ldaps_options.key_file)?;
|
||||
Ok((certs, private_key))
|
||||
}
|
||||
|
||||
fn get_tls_acceptor(ldaps_options: &LdapsOptions) -> Result<RustlsTlsAcceptor> {
|
||||
let (certs, private_key) = read_certificates(ldaps_options)?;
|
||||
// Load TLS key and cert files
|
||||
let certs = certs(&mut BufReader::new(File::open(
|
||||
&config.ldaps_options.cert_file,
|
||||
)?))?
|
||||
.into_iter()
|
||||
.map(Certificate)
|
||||
.collect::<Vec<_>>();
|
||||
let private_key = read_private_key(&config.ldaps_options.key_file)?;
|
||||
let server_config = std::sync::Arc::new(
|
||||
rustls::ServerConfig::builder()
|
||||
ServerConfig::builder()
|
||||
.with_safe_defaults()
|
||||
.with_no_client_auth()
|
||||
.with_single_cert(certs, private_key)?,
|
||||
@@ -193,8 +185,7 @@ where
|
||||
if config.ldaps_options.enabled {
|
||||
let tls_context = (
|
||||
context_for_tls,
|
||||
get_tls_acceptor(&config.ldaps_options)
|
||||
.context("while setting up the SSL certificate")?,
|
||||
get_tls_acceptor(config).context("while setting up the SSL certificate")?,
|
||||
);
|
||||
let tls_binder = move || {
|
||||
let tls_context = tls_context.clone();
|
||||
|
||||
@@ -19,6 +19,7 @@ use actix_service::map_config;
|
||||
use actix_web::{dev::AppConfig, guard, web, App, HttpResponse, Responder};
|
||||
use anyhow::{Context, Result};
|
||||
use hmac::Hmac;
|
||||
use secstr::SecUtf8;
|
||||
use sha2::Sha512;
|
||||
use std::collections::HashSet;
|
||||
use std::path::PathBuf;
|
||||
@@ -38,10 +39,10 @@ pub enum TcpError {
|
||||
BadRequest(String),
|
||||
#[error("Internal server error: `{0}`")]
|
||||
InternalServerError(String),
|
||||
#[error("Not found: `{0}`")]
|
||||
NotFoundError(String),
|
||||
#[error("Unauthorized: `{0}`")]
|
||||
UnauthorizedError(String),
|
||||
#[error("Not implemented: `{0}`")]
|
||||
NotImplemented(String),
|
||||
}
|
||||
|
||||
pub type TcpResult<T> = std::result::Result<T, TcpError>;
|
||||
@@ -60,9 +61,9 @@ pub(crate) fn error_to_http_response(error: TcpError) -> HttpResponse {
|
||||
| DomainError::EntityNotFound(_) => HttpResponse::BadRequest(),
|
||||
},
|
||||
TcpError::BadRequest(_) => HttpResponse::BadRequest(),
|
||||
TcpError::NotFoundError(_) => HttpResponse::NotFound(),
|
||||
TcpError::InternalServerError(_) => HttpResponse::InternalServerError(),
|
||||
TcpError::UnauthorizedError(_) => HttpResponse::Unauthorized(),
|
||||
TcpError::NotImplemented(_) => HttpResponse::NotImplemented(),
|
||||
}
|
||||
.body(error.to_string())
|
||||
}
|
||||
@@ -88,6 +89,7 @@ fn http_config<Backend>(
|
||||
jwt_blacklist: HashSet<u64>,
|
||||
server_url: String,
|
||||
mail_options: MailOptions,
|
||||
hibp_api_key: SecUtf8,
|
||||
) where
|
||||
Backend: TcpBackendHandler + BackendHandler + LoginHandler + OpaqueHandler + Clone + 'static,
|
||||
{
|
||||
@@ -98,6 +100,7 @@ fn http_config<Backend>(
|
||||
jwt_blacklist: RwLock::new(jwt_blacklist),
|
||||
server_url,
|
||||
mail_options,
|
||||
hibp_api_key,
|
||||
}))
|
||||
.route(
|
||||
"/health",
|
||||
@@ -133,6 +136,7 @@ pub(crate) struct AppState<Backend> {
|
||||
pub jwt_blacklist: RwLock<HashSet<u64>>,
|
||||
pub server_url: String,
|
||||
pub mail_options: MailOptions,
|
||||
pub hibp_api_key: SecUtf8,
|
||||
}
|
||||
|
||||
impl<Backend: BackendHandler> AppState<Backend> {
|
||||
@@ -173,6 +177,7 @@ where
|
||||
let mail_options = config.smtp_options.clone();
|
||||
let verbose = config.verbose;
|
||||
info!("Starting the API/web server on port {}", config.http_port);
|
||||
let hibp_api_key = config.hibp_api_key.clone();
|
||||
server_builder
|
||||
.bind(
|
||||
"http",
|
||||
@@ -183,6 +188,7 @@ where
|
||||
let jwt_blacklist = jwt_blacklist.clone();
|
||||
let server_url = server_url.clone();
|
||||
let mail_options = mail_options.clone();
|
||||
let hibp_api_key = hibp_api_key.clone();
|
||||
HttpServiceBuilder::default()
|
||||
.finish(map_config(
|
||||
App::new()
|
||||
@@ -198,6 +204,7 @@ where
|
||||
jwt_blacklist,
|
||||
server_url,
|
||||
mail_options,
|
||||
hibp_api_key,
|
||||
)
|
||||
}),
|
||||
|_| AppConfig::default(),
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
use crate::common::env;
|
||||
use reqwest::blocking::Client;
|
||||
|
||||
pub fn get_token(client: &Client) -> String {
|
||||
let username = env::admin_dn();
|
||||
let password = env::admin_password();
|
||||
let base_url = env::http_url();
|
||||
let response = client
|
||||
.post(format!("{base_url}/auth/simple/login"))
|
||||
.header(reqwest::header::CONTENT_TYPE, "application/json")
|
||||
.body(
|
||||
serde_json::to_string(&lldap_auth::login::ClientSimpleLoginRequest {
|
||||
username,
|
||||
password,
|
||||
})
|
||||
.expect("Failed to encode the username/password as json to log in"),
|
||||
)
|
||||
.send()
|
||||
.expect("Failed to send auth request")
|
||||
.error_for_status()
|
||||
.expect("Auth attempt failed");
|
||||
serde_json::from_str::<lldap_auth::login::ServerLoginResponse>(
|
||||
&response.text().expect("Failed to get response text"),
|
||||
)
|
||||
.expect("Failed to parse json")
|
||||
.token
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
#![allow(dead_code)]
|
||||
use std::env::var;
|
||||
|
||||
pub const DB_KEY: &str = "LLDAP_DATABASE_URL";
|
||||
|
||||
pub fn database_url() -> String {
|
||||
let url = var(DB_KEY).ok();
|
||||
url.unwrap_or("sqlite://e2e_test.db?mode=rwc".to_string())
|
||||
}
|
||||
|
||||
pub fn ldap_url() -> String {
|
||||
let port = var("LLDAP_LDAP_PORT").ok();
|
||||
let port = port.unwrap_or("3890".to_string());
|
||||
format!("ldap://localhost:{}", port)
|
||||
}
|
||||
|
||||
pub fn http_url() -> String {
|
||||
let port = var("LLDAP_HTTP_PORT").ok();
|
||||
let port = port.unwrap_or("17170".to_string());
|
||||
format!("http://localhost:{}", port)
|
||||
}
|
||||
|
||||
pub fn admin_dn() -> String {
|
||||
let user = var("LLDAP_LDAP_USER_DN").ok();
|
||||
user.unwrap_or("admin".to_string())
|
||||
}
|
||||
|
||||
pub fn admin_password() -> String {
|
||||
let pass = var("LLDAP_LDAP_USER_PASS").ok();
|
||||
pass.unwrap_or("password".to_string())
|
||||
}
|
||||
|
||||
pub fn base_dn() -> String {
|
||||
let dn = var("LLDAP_LDAP_BASE_DN").ok();
|
||||
dn.unwrap_or("dc=example,dc=com".to_string())
|
||||
}
|
||||
@@ -1,240 +0,0 @@
|
||||
use crate::common::{
|
||||
auth::get_token,
|
||||
env,
|
||||
graphql::{
|
||||
add_user_to_group, create_group, create_user, delete_group_query, delete_user_query, post,
|
||||
AddUserToGroup, CreateGroup, CreateUser, DeleteGroupQuery, DeleteUserQuery,
|
||||
},
|
||||
};
|
||||
use assert_cmd::prelude::*;
|
||||
use nix::{
|
||||
sys::signal::{self, Signal},
|
||||
unistd::Pid,
|
||||
};
|
||||
use reqwest::blocking::{Client, ClientBuilder};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::process::{Child as ChildProcess, Command};
|
||||
use std::{fs::canonicalize, thread, time::Duration};
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct User {
|
||||
pub username: String,
|
||||
pub groups: Vec<String>,
|
||||
}
|
||||
|
||||
impl User {
|
||||
pub fn new(username: &str, groups: Vec<&str>) -> Self {
|
||||
let username = username.to_owned();
|
||||
let groups = groups.iter().map(|username| username.to_string()).collect();
|
||||
Self { username, groups }
|
||||
}
|
||||
}
|
||||
|
||||
pub struct LLDAPFixture {
|
||||
token: String,
|
||||
client: Client,
|
||||
child: ChildProcess,
|
||||
users: HashSet<String>,
|
||||
groups: HashMap<String, i64>,
|
||||
}
|
||||
|
||||
const MAX_HEALTHCHECK_ATTEMPS: u8 = 10;
|
||||
|
||||
impl LLDAPFixture {
|
||||
pub fn new() -> Self {
|
||||
let mut cmd = create_lldap_command();
|
||||
cmd.arg("run");
|
||||
cmd.arg("--verbose");
|
||||
let child = cmd.spawn().expect("Unable to start server");
|
||||
let mut started = false;
|
||||
for _ in 0..MAX_HEALTHCHECK_ATTEMPS {
|
||||
let status = create_lldap_command()
|
||||
.arg("healthcheck")
|
||||
.status()
|
||||
.expect("healthcheck fail");
|
||||
if status.success() {
|
||||
started = true;
|
||||
break;
|
||||
}
|
||||
thread::sleep(Duration::from_millis(1000));
|
||||
}
|
||||
assert!(started);
|
||||
let client = ClientBuilder::new()
|
||||
.connect_timeout(std::time::Duration::from_secs(2))
|
||||
.timeout(std::time::Duration::from_secs(5))
|
||||
.redirect(reqwest::redirect::Policy::none())
|
||||
.build()
|
||||
.expect("failed to make http client");
|
||||
let token = get_token(&client);
|
||||
Self {
|
||||
client,
|
||||
token,
|
||||
child,
|
||||
users: HashSet::new(),
|
||||
groups: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load_state(&mut self, state: &Vec<User>) {
|
||||
let mut users: HashSet<String> = HashSet::new();
|
||||
let mut groups: HashSet<String> = HashSet::new();
|
||||
for user in state {
|
||||
users.insert(user.username.clone());
|
||||
groups.extend(user.groups.clone());
|
||||
}
|
||||
for user in &users {
|
||||
self.add_user(user);
|
||||
}
|
||||
for group in &groups {
|
||||
self.add_group(group);
|
||||
}
|
||||
for User { username, groups } in state {
|
||||
for group in groups {
|
||||
self.add_user_to_group(username, group);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn add_user(&mut self, user: &String) {
|
||||
post::<CreateUser>(
|
||||
&self.client,
|
||||
&self.token,
|
||||
create_user::Variables {
|
||||
user: create_user::CreateUserInput {
|
||||
id: user.clone(),
|
||||
email: format!("{}@lldap.test", user),
|
||||
avatar: None,
|
||||
display_name: None,
|
||||
first_name: None,
|
||||
last_name: None,
|
||||
},
|
||||
},
|
||||
)
|
||||
.expect("failed to add user");
|
||||
self.users.insert(user.clone());
|
||||
}
|
||||
|
||||
fn add_group(&mut self, group: &str) {
|
||||
let id = post::<CreateGroup>(
|
||||
&self.client,
|
||||
&self.token,
|
||||
create_group::Variables {
|
||||
name: group.to_owned(),
|
||||
},
|
||||
)
|
||||
.expect("failed to add group")
|
||||
.create_group
|
||||
.id;
|
||||
self.groups.insert(group.to_owned(), id);
|
||||
}
|
||||
|
||||
fn delete_user(&mut self, user: &String) {
|
||||
post::<DeleteUserQuery>(
|
||||
&self.client,
|
||||
&self.token,
|
||||
delete_user_query::Variables { user: user.clone() },
|
||||
)
|
||||
.expect("failed to delete user");
|
||||
self.users.remove(user);
|
||||
}
|
||||
|
||||
fn delete_group(&mut self, group: &String) {
|
||||
let group_id = self.groups.get(group).unwrap();
|
||||
post::<DeleteGroupQuery>(
|
||||
&self.client,
|
||||
&self.token,
|
||||
delete_group_query::Variables {
|
||||
group_id: *group_id,
|
||||
},
|
||||
)
|
||||
.expect("failed to delete group");
|
||||
self.groups.remove(group);
|
||||
}
|
||||
|
||||
fn add_user_to_group(&mut self, user: &str, group: &String) {
|
||||
let group_id = self.groups.get(group).unwrap();
|
||||
post::<AddUserToGroup>(
|
||||
&self.client,
|
||||
&self.token,
|
||||
add_user_to_group::Variables {
|
||||
user: user.to_owned(),
|
||||
group: *group_id,
|
||||
},
|
||||
)
|
||||
.expect("failed to add user to group");
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for LLDAPFixture {
|
||||
fn drop(&mut self) {
|
||||
let users = self.users.clone();
|
||||
for user in users {
|
||||
self.delete_user(&user);
|
||||
}
|
||||
let groups = self.groups.clone();
|
||||
for group in groups.keys() {
|
||||
self.delete_group(group);
|
||||
}
|
||||
let result = signal::kill(
|
||||
Pid::from_raw(self.child.id().try_into().unwrap()),
|
||||
Signal::SIGTERM,
|
||||
);
|
||||
if let Err(err) = result {
|
||||
println!("Failed to send kill signal: {:?}", err);
|
||||
let _ = self
|
||||
.child
|
||||
.kill()
|
||||
.map_err(|err| println!("Failed to kill LLDAP: {:?}", err));
|
||||
return;
|
||||
}
|
||||
|
||||
for _ in 0..10 {
|
||||
let status = self.child.try_wait();
|
||||
if status.is_err() {}
|
||||
match status {
|
||||
Err(e) => {
|
||||
println!(
|
||||
"Failed to get status while waiting for graceful exit: {}",
|
||||
e
|
||||
);
|
||||
break;
|
||||
}
|
||||
Ok(None) => {
|
||||
println!("LLDAP still running, sleeping for 1 second.");
|
||||
}
|
||||
Ok(Some(status)) => {
|
||||
if !status.success() {
|
||||
println!("LLDAP exited with status {}", status)
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
thread::sleep(Duration::from_millis(1000));
|
||||
}
|
||||
println!("LLDAP alive after 10 seconds, forcing exit.");
|
||||
let _ = self
|
||||
.child
|
||||
.kill()
|
||||
.map_err(|err| println!("Failed to kill LLDAP: {:?}", err));
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_id(prefix: Option<&str>) -> String {
|
||||
let id = Uuid::new_v4();
|
||||
let id = format!("{}-lldap-test", id.simple());
|
||||
match prefix {
|
||||
Some(prefix) => format!("{}{}", prefix, id),
|
||||
None => id,
|
||||
}
|
||||
}
|
||||
|
||||
fn create_lldap_command() -> Command {
|
||||
let mut cmd = Command::cargo_bin(env!("CARGO_PKG_NAME")).expect("cargo bin not found");
|
||||
// This gives us the absolute path of the repo base instead of running it in server/
|
||||
let path = canonicalize("..").expect("canonical path");
|
||||
let db_url = env::database_url();
|
||||
cmd.current_dir(path);
|
||||
cmd.env(env::DB_KEY, db_url);
|
||||
cmd
|
||||
}
|
||||
@@ -1,121 +0,0 @@
|
||||
use crate::common::env;
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use graphql_client::GraphQLQuery;
|
||||
use reqwest::blocking::Client;
|
||||
|
||||
pub type DateTimeUtc = chrono::DateTime<chrono::Utc>;
|
||||
|
||||
#[derive(GraphQLQuery)]
|
||||
#[graphql(
|
||||
schema_path = "../schema.graphql",
|
||||
query_path = "tests/queries/add_user_to_group.graphql",
|
||||
response_derives = "Debug",
|
||||
variables_derives = "Debug,Clone",
|
||||
custom_scalars_module = "crate::common::graphql"
|
||||
)]
|
||||
pub struct AddUserToGroup;
|
||||
|
||||
#[derive(GraphQLQuery)]
|
||||
#[graphql(
|
||||
schema_path = "../schema.graphql",
|
||||
query_path = "tests/queries/create_user.graphql",
|
||||
response_derives = "Debug",
|
||||
variables_derives = "Debug,Clone",
|
||||
custom_scalars_module = "crate::common::graphql"
|
||||
)]
|
||||
pub struct CreateUser;
|
||||
|
||||
#[derive(GraphQLQuery)]
|
||||
#[graphql(
|
||||
schema_path = "../schema.graphql",
|
||||
query_path = "tests/queries/create_group.graphql",
|
||||
response_derives = "Debug",
|
||||
variables_derives = "Debug,Clone",
|
||||
custom_scalars_module = "crate::common::graphql"
|
||||
)]
|
||||
pub struct CreateGroup;
|
||||
|
||||
#[derive(GraphQLQuery)]
|
||||
#[graphql(
|
||||
schema_path = "../schema.graphql",
|
||||
query_path = "tests/queries/list_users.graphql",
|
||||
response_derives = "Debug",
|
||||
custom_scalars_module = "crate::common::graphql"
|
||||
)]
|
||||
pub struct ListUsers;
|
||||
|
||||
#[derive(GraphQLQuery)]
|
||||
#[graphql(
|
||||
schema_path = "../schema.graphql",
|
||||
query_path = "tests/queries/get_user_details.graphql",
|
||||
response_derives = "Debug",
|
||||
variables_derives = "Debug,Clone",
|
||||
custom_scalars_module = "crate::common::graphql"
|
||||
)]
|
||||
pub struct GetUserDetails;
|
||||
|
||||
#[derive(GraphQLQuery)]
|
||||
#[graphql(
|
||||
schema_path = "../schema.graphql",
|
||||
query_path = "tests/queries/list_groups.graphql",
|
||||
response_derives = "Debug",
|
||||
custom_scalars_module = "crate::common::graphql"
|
||||
)]
|
||||
pub struct ListGroups;
|
||||
|
||||
#[derive(GraphQLQuery)]
|
||||
#[graphql(
|
||||
schema_path = "../schema.graphql",
|
||||
query_path = "tests/queries/delete_group.graphql",
|
||||
response_derives = "Debug",
|
||||
custom_scalars_module = "crate::common::graphql"
|
||||
)]
|
||||
pub struct DeleteGroupQuery;
|
||||
|
||||
#[derive(GraphQLQuery)]
|
||||
#[graphql(
|
||||
schema_path = "../schema.graphql",
|
||||
query_path = "tests/queries/delete_user.graphql",
|
||||
response_derives = "Debug",
|
||||
custom_scalars_module = "crate::common::graphql"
|
||||
)]
|
||||
pub struct DeleteUserQuery;
|
||||
|
||||
pub fn post<QueryType>(
|
||||
client: &Client,
|
||||
token: &String,
|
||||
variables: QueryType::Variables,
|
||||
) -> Result<QueryType::ResponseData>
|
||||
where
|
||||
QueryType: GraphQLQuery + 'static,
|
||||
{
|
||||
let unwrap_graphql_response = |graphql_client::Response { data, errors, .. }| {
|
||||
data.ok_or_else(|| {
|
||||
anyhow!(
|
||||
"Errors: [{}]",
|
||||
errors
|
||||
.unwrap_or_default()
|
||||
.iter()
|
||||
.map(ToString::to_string)
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ")
|
||||
)
|
||||
})
|
||||
};
|
||||
let url = env::http_url() + "/api/graphql";
|
||||
let auth_header = format!("Bearer {}", token);
|
||||
client
|
||||
.post(url)
|
||||
.header(reqwest::header::AUTHORIZATION, auth_header)
|
||||
// Request body.
|
||||
.json(&QueryType::build_query(variables))
|
||||
.send()
|
||||
.context("while sending a request to the LLDAP server")?
|
||||
.error_for_status()
|
||||
.context("error from an LLDAP response")?
|
||||
// Parse response as Json.
|
||||
.json::<graphql_client::Response<QueryType::ResponseData>>()
|
||||
.context("while parsing backend response")
|
||||
.and_then(unwrap_graphql_response)
|
||||
.context("GraphQL error from an LLDAP response")
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
pub mod auth;
|
||||
pub mod env;
|
||||
pub mod fixture;
|
||||
pub mod graphql;
|
||||
@@ -1,70 +0,0 @@
|
||||
use crate::common::{
|
||||
auth::get_token,
|
||||
env,
|
||||
fixture::{new_id, LLDAPFixture, User},
|
||||
graphql::{get_user_details, list_users, post, GetUserDetails, ListUsers},
|
||||
};
|
||||
use reqwest::blocking::ClientBuilder;
|
||||
use serial_test::file_serial;
|
||||
use std::collections::HashSet;
|
||||
mod common;
|
||||
|
||||
#[test]
|
||||
#[file_serial]
|
||||
fn list_users() {
|
||||
let mut fixture = LLDAPFixture::new();
|
||||
let prefix = "graphql-list_users-";
|
||||
let user1_name = new_id(Some(prefix));
|
||||
let user2_name = new_id(Some(prefix));
|
||||
let user3_name = new_id(Some(prefix));
|
||||
let group1_name = new_id(Some(prefix));
|
||||
let group2_name = new_id(Some(prefix));
|
||||
let initial_state = vec![
|
||||
User::new(&user1_name, vec![&group1_name]),
|
||||
User::new(&user2_name, vec![&group1_name, &group2_name]),
|
||||
User::new(&user3_name, vec![]),
|
||||
];
|
||||
fixture.load_state(&initial_state);
|
||||
|
||||
let client = ClientBuilder::new()
|
||||
.connect_timeout(std::time::Duration::from_secs(2))
|
||||
.timeout(std::time::Duration::from_secs(5))
|
||||
.redirect(reqwest::redirect::Policy::none())
|
||||
.build()
|
||||
.expect("failed to make http client");
|
||||
let token = get_token(&client);
|
||||
let result =
|
||||
post::<ListUsers>(&client, &token, list_users::Variables {}).expect("failed to list users");
|
||||
let users: HashSet<String> = result.users.iter().map(|user| user.id.clone()).collect();
|
||||
assert!(users.contains(&user1_name));
|
||||
assert!(users.contains(&user2_name));
|
||||
assert!(users.contains(&user3_name));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[file_serial]
|
||||
fn get_admin() {
|
||||
let mut _fixture = LLDAPFixture::new();
|
||||
let client = ClientBuilder::new()
|
||||
.connect_timeout(std::time::Duration::from_secs(2))
|
||||
.timeout(std::time::Duration::from_secs(5))
|
||||
.redirect(reqwest::redirect::Policy::none())
|
||||
.build()
|
||||
.expect("failed to make http client");
|
||||
let admin_name = env::admin_dn();
|
||||
let admin_group_name = "lldap_admin";
|
||||
let token = get_token(&client);
|
||||
let result = post::<GetUserDetails>(
|
||||
&client,
|
||||
&token,
|
||||
get_user_details::Variables { id: admin_name },
|
||||
)
|
||||
.expect("failed to get admin");
|
||||
let admin_groups: HashSet<String> = result
|
||||
.user
|
||||
.groups
|
||||
.iter()
|
||||
.map(|group| group.display_name.clone())
|
||||
.collect();
|
||||
assert!(admin_groups.contains(admin_group_name));
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
use std::collections::HashSet;
|
||||
|
||||
use crate::common::{
|
||||
env,
|
||||
fixture::{new_id, LLDAPFixture, User},
|
||||
};
|
||||
use ldap3::{LdapConn, Scope, SearchEntry};
|
||||
use serial_test::file_serial;
|
||||
mod common;
|
||||
|
||||
#[test]
|
||||
#[file_serial]
|
||||
fn gitea() {
|
||||
let mut fixture = LLDAPFixture::new();
|
||||
let gitea_user_group = new_id(Some("gitea_user-"));
|
||||
let gitea_admin_group = new_id(Some("gitea_admin-"));
|
||||
let gitea_user1 = new_id(Some("gitea1-"));
|
||||
let gitea_user2 = new_id(Some("gitea2-"));
|
||||
let gitea_user3 = new_id(Some("gitea3-"));
|
||||
let initial_state = vec![
|
||||
User::new(&gitea_user1, vec![&gitea_user_group, &gitea_admin_group]),
|
||||
User::new(&gitea_user2, vec![&gitea_user_group]),
|
||||
User::new(&gitea_user3, vec![]),
|
||||
];
|
||||
fixture.load_state(&initial_state);
|
||||
|
||||
let mut ldap =
|
||||
LdapConn::new(env::ldap_url().as_str()).expect("failed to create ldap connection");
|
||||
let base_dn = env::base_dn();
|
||||
let bind_dn = format!("uid={},ou=people,{}", env::admin_dn(), base_dn);
|
||||
ldap.simple_bind(bind_dn.as_str(), env::admin_password().as_str())
|
||||
.expect("failed to bind to ldap");
|
||||
|
||||
let user_base = format!("ou=people,{}", base_dn);
|
||||
let attrs = vec!["uid", "givenName", "sn", "mail", "jpegPhoto"];
|
||||
let results = ldap
|
||||
.search(
|
||||
user_base.as_str(),
|
||||
Scope::Subtree,
|
||||
format!("(memberof=cn={},ou=groups,{})", gitea_user_group, base_dn).as_str(),
|
||||
attrs,
|
||||
)
|
||||
.expect("failed to find gitea users")
|
||||
.success()
|
||||
.expect("failed to get gitea user results")
|
||||
.0;
|
||||
let mut found_users: HashSet<String> = HashSet::new();
|
||||
for result in results {
|
||||
let attrs = SearchEntry::construct(result).attrs;
|
||||
let user = attrs.get("uid").unwrap().get(0).unwrap();
|
||||
found_users.insert(user.clone());
|
||||
}
|
||||
assert!(found_users.contains(&gitea_user1));
|
||||
assert!(found_users.contains(&gitea_user2));
|
||||
assert!(!found_users.contains(&gitea_user3));
|
||||
ldap.unbind().expect("failed to unbind ldap connection");
|
||||
}
|
||||
@@ -1,112 +0,0 @@
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
use crate::common::{
|
||||
env,
|
||||
fixture::{new_id, LLDAPFixture, User},
|
||||
};
|
||||
use ldap3::{LdapConn, Scope, SearchEntry, SearchResult};
|
||||
use serial_test::file_serial;
|
||||
mod common;
|
||||
|
||||
#[test]
|
||||
#[file_serial]
|
||||
fn basic_users_search() {
|
||||
let mut fixture = LLDAPFixture::new();
|
||||
let prefix = "ldap-basic_users_search-";
|
||||
let user1_name = new_id(Some(prefix));
|
||||
let user2_name = new_id(Some(prefix));
|
||||
let user3_name = new_id(Some(prefix));
|
||||
let group1_name = new_id(Some(prefix));
|
||||
let group2_name = new_id(Some(prefix));
|
||||
let initial_state = vec![
|
||||
User::new(&user1_name, vec![&group1_name]),
|
||||
User::new(&user2_name, vec![&group1_name, &group2_name]),
|
||||
User::new(&user3_name, vec![]),
|
||||
];
|
||||
fixture.load_state(&initial_state);
|
||||
|
||||
let mut ldap =
|
||||
LdapConn::new(env::ldap_url().as_str()).expect("failed to create ldap connection");
|
||||
let base_dn = env::base_dn();
|
||||
let bind_dn = format!("uid={},ou=people,{}", env::admin_dn(), base_dn);
|
||||
ldap.simple_bind(bind_dn.as_str(), env::admin_password().as_str())
|
||||
.expect("failed to bind to ldap");
|
||||
|
||||
let attrs = vec!["uid", "memberof"];
|
||||
let found_users = get_users_and_groups(
|
||||
ldap.search(
|
||||
env::base_dn().as_str(),
|
||||
Scope::Subtree,
|
||||
"(objectclass=person)",
|
||||
attrs,
|
||||
)
|
||||
.expect("failed to find users"),
|
||||
);
|
||||
assert!(found_users.contains_key(&user1_name));
|
||||
assert!(found_users
|
||||
.get(&user1_name)
|
||||
.unwrap()
|
||||
.contains(format!("cn={},ou=groups,{}", &group1_name, base_dn).as_str()));
|
||||
assert!(found_users.contains_key(&user2_name));
|
||||
assert!(found_users
|
||||
.get(&user2_name)
|
||||
.unwrap()
|
||||
.contains(format!("cn={},ou=groups,{}", &group1_name, base_dn).as_str()));
|
||||
assert!(found_users
|
||||
.get(&user2_name)
|
||||
.unwrap()
|
||||
.contains(format!("cn={},ou=groups,{}", &group2_name, base_dn).as_str()));
|
||||
assert!(found_users.contains_key(&user3_name));
|
||||
assert!(found_users.get(&user3_name).unwrap().is_empty());
|
||||
ldap.unbind().expect("failed to unbind ldap connection");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[file_serial]
|
||||
fn admin_search() {
|
||||
let mut _fixture = LLDAPFixture::new();
|
||||
|
||||
let mut ldap =
|
||||
LdapConn::new(env::ldap_url().as_str()).expect("failed to create ldap connection");
|
||||
let base_dn = env::base_dn();
|
||||
let bind_dn = format!("uid={},ou=people,{}", env::admin_dn(), base_dn);
|
||||
ldap.simple_bind(bind_dn.as_str(), env::admin_password().as_str())
|
||||
.expect("failed to bind to ldap");
|
||||
|
||||
let attrs = vec!["uid", "memberof"];
|
||||
let admin_name = env::admin_dn();
|
||||
let admin_group_name = "lldap_admin";
|
||||
let found_users = get_users_and_groups(
|
||||
ldap.search(
|
||||
env::base_dn().as_str(),
|
||||
Scope::Subtree,
|
||||
format!("(&(objectclass=person)(uid={}))", admin_name).as_str(),
|
||||
attrs,
|
||||
)
|
||||
.expect("failed to find admin"),
|
||||
);
|
||||
|
||||
assert!(found_users.contains_key(&admin_name));
|
||||
assert!(found_users
|
||||
.get(&admin_name)
|
||||
.unwrap()
|
||||
.contains(format!("cn={},ou=groups,{}", admin_group_name, base_dn).as_str()));
|
||||
ldap.unbind().expect("failed to unbind ldap connection");
|
||||
}
|
||||
|
||||
fn get_users_and_groups(results: SearchResult) -> HashMap<String, HashSet<String>> {
|
||||
let results = results
|
||||
.success()
|
||||
.expect("failed to get successful result")
|
||||
.0;
|
||||
let mut found_users: HashMap<String, HashSet<String>> = HashMap::new();
|
||||
for result in results {
|
||||
let attrs = SearchEntry::construct(result).attrs;
|
||||
let user = attrs.get("uid").unwrap().get(0).unwrap();
|
||||
let user_groups = attrs.get("memberof").unwrap().clone();
|
||||
let mut groups: HashSet<String> = HashSet::new();
|
||||
groups.extend(user_groups.clone());
|
||||
found_users.insert(user.clone(), groups);
|
||||
}
|
||||
found_users
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
mutation AddUserToGroup($user: String!, $group: Int!) {
|
||||
addUserToGroup(userId: $user, groupId: $group) {
|
||||
ok
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
mutation CreateGroup($name: String!) {
|
||||
createGroup(name: $name) {
|
||||
id
|
||||
displayName
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
mutation CreateUser($user: CreateUserInput!) {
|
||||
createUser(user: $user) {
|
||||
id
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
mutation DeleteGroupQuery($groupId: Int!) {
|
||||
deleteGroup(groupId: $groupId) {
|
||||
ok
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
mutation DeleteUserQuery($user: String!) {
|
||||
deleteUser(userId: $user) {
|
||||
ok
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
query GetUserDetails($id: String!) {
|
||||
user(userId: $id) {
|
||||
id
|
||||
email
|
||||
displayName
|
||||
firstName
|
||||
lastName
|
||||
avatar
|
||||
creationDate
|
||||
uuid
|
||||
groups {
|
||||
id
|
||||
displayName
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
query ListGroups {
|
||||
groups {
|
||||
id
|
||||
displayName
|
||||
users {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
query ListUsers {
|
||||
users(filters: null) {
|
||||
id
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,12 @@
|
||||
[package]
|
||||
authors = ["Valentin Tolmer <valentin@tolmer.fr>"]
|
||||
description = "CLI tool to set a user password in LLDAP"
|
||||
edition = "2021"
|
||||
homepage = "https://github.com/lldap/lldap"
|
||||
license = "GPL-3.0-only"
|
||||
name = "lldap_set_password"
|
||||
repository = "https://github.com/lldap/lldap"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1"
|
||||
anyhow = "*"
|
||||
rand = "0.8"
|
||||
serde = "1"
|
||||
serde_json = "1"
|
||||
@@ -21,10 +16,10 @@ features = ["std", "color", "suggestions", "derive", "env"]
|
||||
version = "4"
|
||||
|
||||
[dependencies.lldap_auth]
|
||||
version = "0.3"
|
||||
path = "../auth"
|
||||
features = ["opaque_client"]
|
||||
|
||||
[dependencies.reqwest]
|
||||
version = "0.11"
|
||||
version = "*"
|
||||
default-features = false
|
||||
features = ["json", "blocking", "rustls-tls"]
|
||||
|
||||
Reference in New Issue
Block a user