Compare commits
65 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
32f28d664e | ||
|
|
412f4fa644 | ||
|
|
4ffa565e51 | ||
|
|
2f9ea4f10f | ||
|
|
123fdc5baf | ||
|
|
5402aa5aa2 | ||
|
|
8069516283 | ||
|
|
6c21f2ef4b | ||
|
|
516893f1f7 | ||
|
|
1660cb1fbb | ||
|
|
7e1ce10df1 | ||
|
|
b6ee918ca9 | ||
|
|
24efd61464 | ||
|
|
0b6b274cfa | ||
|
|
8b01271e94 | ||
|
|
d536addf0a | ||
|
|
2ca083541e | ||
|
|
686bdc0cb1 | ||
|
|
60c594438c | ||
|
|
b130965264 | ||
|
|
697a64991d | ||
|
|
3acc448048 | ||
|
|
0e3c5120da | ||
|
|
7707367c35 | ||
|
|
122e08790f | ||
|
|
64556fc744 | ||
|
|
134a9366f5 | ||
|
|
f69b729eb2 | ||
|
|
2ac47d5c85 | ||
|
|
26d3d84de0 | ||
|
|
b413935932 | ||
|
|
e6ae726304 | ||
|
|
520277b611 | ||
|
|
8cdfedddbd | ||
|
|
5312400a3f | ||
|
|
551f5abc4b | ||
|
|
10d826fc46 | ||
|
|
252bd6cf39 | ||
|
|
ba44dea7b6 | ||
|
|
b9c823e01a | ||
|
|
c108921dcf | ||
|
|
36eed1e091 | ||
|
|
897704fab3 | ||
|
|
9f70910283 | ||
|
|
3e3c9b97ae | ||
|
|
8c1ea11b95 | ||
|
|
cd0ab378ef | ||
|
|
5a27ae4862 | ||
|
|
05719642ca | ||
|
|
5c584536b5 | ||
|
|
4ba0db4e9e | ||
|
|
5e4ed9ee17 | ||
|
|
c399ff2bfa | ||
|
|
9e37a06514 | ||
|
|
294ce77a47 | ||
|
|
24c6b4a879 | ||
|
|
2c2696a8c3 | ||
|
|
479d1e7635 | ||
|
|
3a723460e5 | ||
|
|
8011756658 | ||
|
|
46546dac27 | ||
|
|
9a869a1474 | ||
|
|
09797695aa | ||
|
|
4f2cf45427 | ||
|
|
901eb7f469 |
106
.github/workflows/Dockerfile.ci.alpine
vendored
Normal file
106
.github/workflows/Dockerfile.ci.alpine
vendored
Normal file
@@ -0,0 +1,106 @@
|
||||
FROM debian:bullseye AS lldap
|
||||
ARG DEBIAN_FRONTEND=noninteractive
|
||||
ARG TARGETPLATFORM
|
||||
RUN apt update && apt install -y wget
|
||||
WORKDIR /dim
|
||||
COPY bin/ bin/
|
||||
COPY web/ web/
|
||||
|
||||
RUN mkdir -p target/
|
||||
RUN mkdir -p /lldap/app
|
||||
|
||||
RUN if [ "${TARGETPLATFORM}" = "linux/amd64" ]; then \
|
||||
mv bin/amd64-bin/lldap target/lldap && \
|
||||
mv bin/amd64-bin/migration-tool target/migration-tool && \
|
||||
chmod +x target/lldap && \
|
||||
chmod +x target/migration-tool && \
|
||||
ls -la target/ . && \
|
||||
pwd \
|
||||
; fi
|
||||
|
||||
RUN if [ "${TARGETPLATFORM}" = "linux/arm64" ]; then \
|
||||
mv bin/aarch64-bin/lldap target/lldap && \
|
||||
mv bin/aarch64-bin/migration-tool target/migration-tool && \
|
||||
chmod +x target/lldap && \
|
||||
chmod +x target/migration-tool && \
|
||||
ls -la target/ . && \
|
||||
pwd \
|
||||
; fi
|
||||
|
||||
RUN if [ "${TARGETPLATFORM}" = "linux/arm/v7" ]; then \
|
||||
mv bin/armhf-bin/lldap target/lldap && \
|
||||
mv bin/armhf-bin/migration-tool target/migration-tool && \
|
||||
chmod +x target/lldap && \
|
||||
chmod +x target/migration-tool && \
|
||||
ls -la target/ . && \
|
||||
pwd \
|
||||
; fi
|
||||
|
||||
# Web and App dir
|
||||
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/migration-tool /lldap/ && \
|
||||
cp -R web/index.html \
|
||||
web/pkg \
|
||||
web/static \
|
||||
/lldap/app/
|
||||
|
||||
WORKDIR /lldap
|
||||
RUN set -x \
|
||||
&& for file in $(cat /lldap/app/static/libraries.txt); do wget -P app/static "$file"; done \
|
||||
&& for file in $(cat /lldap/app/static/fonts/fonts.txt); do wget -P app/static/fonts "$file"; done \
|
||||
&& chmod a+r -R .
|
||||
|
||||
FROM alpine:3.16
|
||||
WORKDIR /app
|
||||
ENV UID=1000
|
||||
ENV GID=1000
|
||||
ENV USER=lldap
|
||||
ENV GOSU_VERSION 1.14
|
||||
# Fetch gosu from git
|
||||
RUN set -eux; \
|
||||
\
|
||||
apk add --no-cache --virtual .gosu-deps \
|
||||
ca-certificates \
|
||||
dpkg \
|
||||
gnupg \
|
||||
; \
|
||||
\
|
||||
dpkgArch="$(dpkg --print-architecture | awk -F- '{ print $NF }')"; \
|
||||
wget -O /usr/local/bin/gosu "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$dpkgArch"; \
|
||||
wget -O /usr/local/bin/gosu.asc "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$dpkgArch.asc"; \
|
||||
\
|
||||
# verify the signature
|
||||
export GNUPGHOME="$(mktemp -d)"; \
|
||||
gpg --batch --keyserver hkps://keys.openpgp.org --recv-keys B42F6819007F00F88E364FD4036A9C25BF357DD4; \
|
||||
gpg --batch --verify /usr/local/bin/gosu.asc /usr/local/bin/gosu; \
|
||||
command -v gpgconf && gpgconf --kill all || :; \
|
||||
rm -rf "$GNUPGHOME" /usr/local/bin/gosu.asc; \
|
||||
\
|
||||
# clean up fetch dependencies
|
||||
apk del --no-network .gosu-deps; \
|
||||
\
|
||||
chmod +x /usr/local/bin/gosu; \
|
||||
# verify that the binary works
|
||||
gosu --version; \
|
||||
gosu nobody true
|
||||
RUN apk add --no-cache tini ca-certificates bash && \
|
||||
addgroup -g $GID $USER && \
|
||||
adduser \
|
||||
--disabled-password \
|
||||
--gecos "" \
|
||||
--home "$(pwd)" \
|
||||
--ingroup "$USER" \
|
||||
--no-create-home \
|
||||
--uid "$UID" \
|
||||
"$USER" && \
|
||||
mkdir -p /data && \
|
||||
chown $USER:$USER /data
|
||||
COPY --from=lldap --chown=$CONTAINERUSER:$CONTAINERUSER /lldap /app
|
||||
COPY --from=lldap --chown=$CONTAINERUSER:$CONTAINERUSER /docker-entrypoint.sh /docker-entrypoint.sh
|
||||
VOLUME ["/data"]
|
||||
WORKDIR /app
|
||||
ENTRYPOINT ["tini", "--", "/docker-entrypoint.sh"]
|
||||
CMD ["run", "--config-file", "/data/lldap_config.toml"]
|
||||
@@ -39,6 +39,7 @@ RUN if [ "${TARGETPLATFORM}" = "linux/arm/v7" ]; then \
|
||||
# Web and App dir
|
||||
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/migration-tool /lldap/ && \
|
||||
cp -R web/index.html \
|
||||
@@ -46,23 +47,25 @@ RUN cp target/lldap /lldap/ && \
|
||||
web/static \
|
||||
/lldap/app/
|
||||
|
||||
WORKDIR /lldap
|
||||
RUN set -x \
|
||||
&& for file in $(cat /lldap/app/static/libraries.txt); do wget -P app/static "$file"; done \
|
||||
&& for file in $(cat /lldap/app/static/fonts/fonts.txt); do wget -P app/static/fonts "$file"; done \
|
||||
&& chmod a+r -R .
|
||||
|
||||
FROM debian:bullseye
|
||||
&& chmod a+r -R .
|
||||
|
||||
FROM debian:bullseye-slim
|
||||
ENV UID=1000
|
||||
ENV GID=1000
|
||||
ENV USER=lldap
|
||||
RUN apt update && \
|
||||
apt install -y --no-install-recommends tini ca-certificates && \
|
||||
apt install -y --no-install-recommends tini openssl ca-certificates gosu && \
|
||||
apt clean && \
|
||||
rm -rf /var/lib/apt/lists/* && \
|
||||
groupadd -g $GID $USER && useradd --system -m -g $USER --uid $UID $USER
|
||||
COPY --from=lldap --chown=$CONTAINERUSER:$CONTAINERUSER /lldap /app
|
||||
COPY --from=lldap --chown=$CONTAINERUSER:$CONTAINERUSER /docker-entrypoint.sh /docker-entrypoint.sh
|
||||
groupadd -g $GID $USER && useradd --system -m -g $USER --uid $UID $USER && \
|
||||
mkdir -p /data && chown $USER:$USER /data
|
||||
COPY --from=lldap --chown=$USER:$USER /lldap /app
|
||||
COPY --from=lldap --chown=$USER:$USER /docker-entrypoint.sh /docker-entrypoint.sh
|
||||
VOLUME ["/data"]
|
||||
WORKDIR /app
|
||||
USER $USER
|
||||
ENTRYPOINT ["tini", "--", "/docker-entrypoint.sh"]
|
||||
CMD ["run", "--config-file", "/data/lldap_config.toml"]
|
||||
34
.github/workflows/Dockerfile.dev
vendored
Normal file
34
.github/workflows/Dockerfile.dev
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
FROM rust:1.62-slim-bullseye
|
||||
|
||||
# Set needed env path
|
||||
ENV PATH="/opt/aarch64-linux-musl-cross/:/opt/aarch64-linux-musl-cross/bin/:/opt/x86_64-linux-musl-cross/:/opt/x86_64-linux-musl-cross/bin/:$PATH"
|
||||
|
||||
### Install build deps x86_64
|
||||
RUN apt update && \
|
||||
apt install -y --no-install-recommends curl git wget build-essential make perl pkg-config curl tar jq musl-tools && \
|
||||
curl -fsSL https://deb.nodesource.com/setup_lts.x | bash - && \
|
||||
apt update && \
|
||||
apt install -y --no-install-recommends nodejs && \
|
||||
apt clean && \
|
||||
rm -rf /var/lib/apt/lists/* && \
|
||||
npm install -g npm && \
|
||||
npm install -g yarn && \
|
||||
npm install -g pnpm
|
||||
|
||||
### Install build deps aarch64 build
|
||||
RUN dpkg --add-architecture arm64 && \
|
||||
apt update && \
|
||||
apt install -y gcc-aarch64-linux-gnu g++-aarch64-linux-gnu libc6-arm64-cross libc6-dev-arm64-cross && \
|
||||
apt clean && \
|
||||
rm -rf /var/lib/apt/lists/* && \
|
||||
rustup target add aarch64-unknown-linux-gnu
|
||||
|
||||
### Add musl-gcc aarch64 and x86_64
|
||||
RUN wget -c https://musl.cc/x86_64-linux-musl-cross.tgz && \
|
||||
tar zxf ./x86_64-linux-musl-cross.tgz -C /opt && \
|
||||
wget -c https://musl.cc/aarch64-linux-musl-cross.tgz && \
|
||||
tar zxf ./aarch64-linux-musl-cross.tgz -C /opt && \
|
||||
rm ./x86_64-linux-musl-cross.tgz && \
|
||||
rm ./aarch64-linux-musl-cross.tgz
|
||||
|
||||
CMD ["bash"]
|
||||
@@ -1,4 +1,4 @@
|
||||
name: Docker
|
||||
name: Docker Static
|
||||
|
||||
on:
|
||||
push:
|
||||
@@ -15,13 +15,9 @@ on:
|
||||
msg:
|
||||
description: "Set message"
|
||||
default: "Manual trigger"
|
||||
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
RUSTC_WRAPPER: sccache
|
||||
SCCACHE_DIR: $GITHUB_WORKSPACE/.sccache
|
||||
SCCACHE_VERSION: v0.3.0
|
||||
LINK: https://github.com/mozilla/sccache/releases/download
|
||||
|
||||
# In total 5 jobs, all of the jobs are containerized
|
||||
# ---
|
||||
@@ -31,7 +27,7 @@ env:
|
||||
### Install nodejs from nodesource repo
|
||||
### install wasm
|
||||
### install rollup
|
||||
### run app/build.sh
|
||||
### run app/build.sh
|
||||
### upload artifacts
|
||||
|
||||
# builds-armhf, build-aarch64, build-amd64 create binary for respective arch
|
||||
@@ -43,8 +39,6 @@ env:
|
||||
|
||||
## the CARGO_ env
|
||||
#CARGO_TARGET_ARMV7_UNKNOWN_LINUX_GNUEABIHF_LINKER: arm-linux-gnueabihf-gcc
|
||||
#OPENSSL_INCLUDE_DIR: "/usr/include/openssl/"
|
||||
#OPENSSL_LIB_DIR: "/usr/lib/arm-linux-gnueabihf/"
|
||||
# This will determine which architecture lib will be used.
|
||||
|
||||
# build-ui,builds-armhf, build-aarch64, build-amd64 will upload artifacts will be used next job
|
||||
@@ -54,50 +48,45 @@ env:
|
||||
# 1-bullseye, 1.61-bullseye, 1.61.0-bullseye, bullseye, 1, 1.61, 1.61.0, latest
|
||||
|
||||
# cache
|
||||
## .sccache
|
||||
## cargo
|
||||
## target
|
||||
|
||||
jobs:
|
||||
build-ui:
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: rust:1.61
|
||||
container:
|
||||
image: rust:1.62
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
RUSTFLAGS: -Ctarget-feature=-crt-static
|
||||
RUSTFLAGS: -Ctarget-feature=+crt-static
|
||||
steps:
|
||||
- name: install runtime
|
||||
run: apt update && apt install -y gcc-x86-64-linux-gnu g++-x86-64-linux-gnu libc6-dev libssl-dev
|
||||
run: apt update && apt install -y gcc-x86-64-linux-gnu g++-x86-64-linux-gnu libc6-dev ca-certificates
|
||||
- name: setup node repo LTS
|
||||
run: curl -fsSL https://deb.nodesource.com/setup_lts.x | bash -
|
||||
- name: install nodejs
|
||||
run: apt install -y nodejs && npm -g install npm
|
||||
- name: smoke test
|
||||
run: rustc --version
|
||||
- name: Install sccache (ubuntu-latest)
|
||||
run: |
|
||||
SCCACHE_FILE=sccache-$SCCACHE_VERSION-x86_64-unknown-linux-musl
|
||||
mkdir -p $HOME/.local/bin
|
||||
curl -L "$LINK/$SCCACHE_VERSION/$SCCACHE_FILE.tar.gz" | tar xz
|
||||
mv -f $SCCACHE_FILE/sccache $HOME/.local/bin/sccache
|
||||
chmod +x $HOME/.local/bin/sccache
|
||||
echo "$HOME/.local/bin" >> $GITHUB_PATH
|
||||
- uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
.sccache
|
||||
/usr/local/cargo
|
||||
/usr/local/cargo/bin
|
||||
/usr/local/cargo/registry/index
|
||||
/usr/local/cargo/registry/cache
|
||||
/usr/local/cargo/git/db
|
||||
target
|
||||
key: lldap-ui-${{ github.sha }}
|
||||
key: lldap-ui-${{ hashFiles('**/Cargo.lock') }}
|
||||
restore-keys: |
|
||||
lldap-ui-
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
- name: install cargo wasm
|
||||
run: cargo install wasm-pack
|
||||
uses: actions/checkout@v3.1.0
|
||||
- name: install rollup nodejs
|
||||
run: npm install -g rollup
|
||||
- name: install wasm-pack with cargo
|
||||
run: cargo install wasm-pack || true
|
||||
env:
|
||||
RUSTFLAGS: ""
|
||||
- name: build frontend
|
||||
run: ./app/build.sh
|
||||
- name: check path
|
||||
@@ -107,45 +96,39 @@ jobs:
|
||||
with:
|
||||
name: ui
|
||||
path: app/
|
||||
|
||||
|
||||
build-armhf:
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: rust:1.61
|
||||
container:
|
||||
image: rust:1.62
|
||||
env:
|
||||
CARGO_TARGET_ARMV7_UNKNOWN_LINUX_GNUEABIHF_LINKER: arm-linux-gnueabihf-gcc
|
||||
OPENSSL_INCLUDE_DIR: "/usr/include/openssl/"
|
||||
OPENSSL_LIB_DIR: "/usr/lib/arm-linux-gnueabihf/"
|
||||
CARGO_TARGET_ARMV7_UNKNOWN_LINUX_MUSLEABIHF_LINKER: arm-linux-gnueabihf-ld
|
||||
CARGO_TERM_COLOR: always
|
||||
RUSTFLAGS: -Ctarget-feature=-crt-static
|
||||
CARGO_HOME: ${GITHUB_WORKSPACE}/.cargo
|
||||
steps:
|
||||
- name: add armhf architecture
|
||||
run: dpkg --add-architecture armhf
|
||||
- name: install runtime
|
||||
run: apt update && apt install -y gcc-arm-linux-gnueabihf g++-arm-linux-gnueabihf libc6-armhf-cross libc6-dev-armhf-cross libssl-dev:armhf tar
|
||||
run: apt update && apt install -y gcc-arm-linux-gnueabihf g++-arm-linux-gnueabihf libc6-armhf-cross libc6-dev-armhf-cross tar ca-certificates
|
||||
- name: smoke test
|
||||
run: rustc --version
|
||||
- name: add armhf target
|
||||
run: rustup target add armv7-unknown-linux-gnueabihf
|
||||
- name: smoke test
|
||||
run: rustc --version
|
||||
- name: Install sccache (ubuntu-latest)
|
||||
run: |
|
||||
SCCACHE_FILE=sccache-$SCCACHE_VERSION-x86_64-unknown-linux-musl
|
||||
mkdir -p $HOME/.local/bin
|
||||
curl -L "$LINK/$SCCACHE_VERSION/$SCCACHE_FILE.tar.gz" | tar xz
|
||||
mv -f $SCCACHE_FILE/sccache $HOME/.local/bin/sccache
|
||||
chmod +x $HOME/.local/bin/sccache
|
||||
echo "$HOME/.local/bin" >> $GITHUB_PATH
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3.1.0
|
||||
- uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
.sccache
|
||||
/usr/local/cargo
|
||||
.cargo/bin
|
||||
.cargo/registry/index
|
||||
.cargo/registry/cache
|
||||
.cargo/git/db
|
||||
target
|
||||
key: lldap-bin-armhf-${{ github.sha }}
|
||||
key: lldap-bin-armhf-${{ hashFiles('**/Cargo.lock') }}
|
||||
restore-keys: |
|
||||
lldap-bin-armhf-
|
||||
- name: compile armhf
|
||||
@@ -166,113 +149,106 @@ jobs:
|
||||
|
||||
build-aarch64:
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: rust:1.61
|
||||
container:
|
||||
##################################################################################
|
||||
# Github actions currently timeout when downloading musl-gcc #
|
||||
# Using lldap dev image based on rust:1.62-slim-bullseye and musl-gcc bundled #
|
||||
# Only for Job build aarch64 and amd64 #
|
||||
###################################################################################
|
||||
#image: rust:1.62
|
||||
image: nitnelave/rust-dev:latest
|
||||
env:
|
||||
CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER: aarch64-linux-gnu-gcc
|
||||
OPENSSL_INCLUDE_DIR: "/usr/include/openssl/"
|
||||
OPENSSL_LIB_DIR: "/usr/lib/aarch64-linux-gnu/"
|
||||
CARGO_TARGET_AARCH64_UNKNOWN_LINUX_MUSL_LINKER: aarch64-linux-musl-gcc
|
||||
CARGO_TERM_COLOR: always
|
||||
RUSTFLAGS: -Ctarget-feature=-crt-static
|
||||
RUSTFLAGS: -Ctarget-feature=+crt-static
|
||||
CARGO_HOME: ${GITHUB_WORKSPACE}/.cargo
|
||||
steps:
|
||||
- name: add arm64 architecture
|
||||
run: dpkg --add-architecture arm64
|
||||
- name: install runtime
|
||||
run: apt update && apt install -y gcc-aarch64-linux-gnu g++-aarch64-linux-gnu libc6-arm64-cross libc6-dev-arm64-cross libssl-dev:arm64 tar
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3.1.0
|
||||
- name: smoke test
|
||||
run: rustc --version
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
- name: add arm64 target
|
||||
run: rustup target add aarch64-unknown-linux-gnu
|
||||
- name: smoke test
|
||||
run: rustc --version
|
||||
- name: Install sccache (ubuntu-latest)
|
||||
run: |
|
||||
SCCACHE_FILE=sccache-$SCCACHE_VERSION-x86_64-unknown-linux-musl
|
||||
mkdir -p $HOME/.local/bin
|
||||
curl -L "$LINK/$SCCACHE_VERSION/$SCCACHE_FILE.tar.gz" | tar xz
|
||||
mv -f $SCCACHE_FILE/sccache $HOME/.local/bin/sccache
|
||||
chmod +x $HOME/.local/bin/sccache
|
||||
echo "$HOME/.local/bin" >> $GITHUB_PATH
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3.1.0
|
||||
- uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
.sccache
|
||||
/usr/local/cargo
|
||||
.cargo/bin
|
||||
.cargo/registry/index
|
||||
.cargo/registry/cache
|
||||
.cargo/git/db
|
||||
target
|
||||
key: lldap-bin-aarch64-${{ github.sha }}
|
||||
key: lldap-bin-aarch64-${{ hashFiles('**/Cargo.lock') }}
|
||||
restore-keys: |
|
||||
lldap-bin-aarch64-
|
||||
- name: compile aarch64
|
||||
run: cargo build --target=aarch64-unknown-linux-gnu --release -p lldap -p migration-tool
|
||||
lldap-bin-aarch64-
|
||||
# - name: fetch musl-gcc
|
||||
# run: |
|
||||
# wget -c https://musl.cc/aarch64-linux-musl-cross.tgz
|
||||
# tar zxf ./x86_64-linux-musl-cross.tgz -C /opt
|
||||
# echo "/opt/aarch64-linux-musl-cross:/opt/aarch64-linux-musl-cross/bin" >> $GITHUB_PATH
|
||||
- name: add musl aarch64 target
|
||||
run: rustup target add aarch64-unknown-linux-musl
|
||||
- name: build lldap aarch4
|
||||
run: cargo build --target=aarch64-unknown-linux-musl --release -p lldap -p migration-tool
|
||||
- name: check path
|
||||
run: ls -al target/aarch64-unknown-linux-gnu/release/
|
||||
run: ls -al target/aarch64-unknown-linux-musl/release/
|
||||
- name: upload aarch64 lldap artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: aarch64-lldap-bin
|
||||
path: target/aarch64-unknown-linux-gnu/release/lldap
|
||||
path: target/aarch64-unknown-linux-musl/release/lldap
|
||||
- name: upload aarch64 migration-tool artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: aarch64-migration-tool-bin
|
||||
path: target/aarch64-unknown-linux-gnu/release/migration-tool
|
||||
path: target/aarch64-unknown-linux-musl/release/migration-tool
|
||||
|
||||
build-amd64:
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: rust:1.61
|
||||
container:
|
||||
# image: rust:1.62
|
||||
image: nitnelave/rust-dev:latest
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
RUSTFLAGS: -Ctarget-feature=-crt-static
|
||||
RUSTFLAGS: -Ctarget-feature=+crt-static
|
||||
CARGO_HOME: ${GITHUB_WORKSPACE}/.cargo
|
||||
CARGO_TARGET_X86_64_UNKNOWN_LINUX_MUSL_LINKER: x86_64-linux-musl-gcc
|
||||
steps:
|
||||
- name: install runtime
|
||||
run: apt update && apt install -y gcc-x86-64-linux-gnu g++-x86-64-linux-gnu libc6-dev libssl-dev tar
|
||||
- name: smoke test
|
||||
run: rustc --version
|
||||
- name: Install sccache (ubuntu-latest)
|
||||
run: |
|
||||
SCCACHE_FILE=sccache-$SCCACHE_VERSION-x86_64-unknown-linux-musl
|
||||
mkdir -p $HOME/.local/bin
|
||||
curl -L "$LINK/$SCCACHE_VERSION/$SCCACHE_FILE.tar.gz" | tar xz
|
||||
mv -f $SCCACHE_FILE/sccache $HOME/.local/bin/sccache
|
||||
chmod +x $HOME/.local/bin/sccache
|
||||
echo "$HOME/.local/bin" >> $GITHUB_PATH
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
- name: cargo & sscache cache
|
||||
uses: actions/cache@v3
|
||||
uses: actions/checkout@v3.1.0
|
||||
- uses: actions/cache@v3
|
||||
with:
|
||||
path: |
|
||||
.sccache
|
||||
/usr/local/cargo
|
||||
.cargo/bin
|
||||
.cargo/registry/index
|
||||
.cargo/registry/cache
|
||||
.cargo/git/db
|
||||
target
|
||||
key: lldap-bin-amd64-${{ github.sha }}
|
||||
key: lldap-bin-amd64-${{ hashFiles('**/Cargo.lock') }}
|
||||
restore-keys: |
|
||||
lldap-bin-amd64-
|
||||
#- name: add cargo chef
|
||||
# run: cargo install cargo-chef
|
||||
#- name: chef prepare
|
||||
# run: cargo chef prepare --recipe-path recipe.json
|
||||
#- name: cook?
|
||||
# run: cargo chef cook --release --recipe-path recipe.json
|
||||
- name: compile amd64
|
||||
run: cargo build --target=x86_64-unknown-linux-gnu --release -p lldap -p migration-tool
|
||||
- name: install musl
|
||||
run: apt update && apt install -y musl-tools tar wget
|
||||
# - name: fetch musl-gcc
|
||||
# run: |
|
||||
# wget -c https://musl.cc/x86_64-linux-musl-cross.tgz
|
||||
# tar zxf ./x86_64-linux-musl-cross.tgz -C /opt
|
||||
# echo "/opt/x86_64-linux-musl-cross:/opt/x86_64-linux-musl-cross/bin" >> $GITHUB_PATH
|
||||
- name: add x86_64 target
|
||||
run: rustup target add x86_64-unknown-linux-musl
|
||||
- name: build x86_64 lldap
|
||||
run: cargo build --target=x86_64-unknown-linux-musl --release -p lldap -p migration-tool
|
||||
- name: check path
|
||||
run: ls -al target/x86_64-unknown-linux-gnu/release/
|
||||
run: ls -al target/x86_64-unknown-linux-musl/release/
|
||||
- name: upload amd64 lldap artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: amd64-lldap-bin
|
||||
path: target/x86_64-unknown-linux-gnu/release/lldap
|
||||
path: target/x86_64-unknown-linux-musl/release/lldap
|
||||
- name: upload amd64 migration-tool artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: amd64-migration-tool-bin
|
||||
path: target/x86_64-unknown-linux-gnu/release/migration-tool
|
||||
path: target/x86_64-unknown-linux-musl/release/migration-tool
|
||||
|
||||
|
||||
build-docker-image:
|
||||
@@ -283,9 +259,11 @@ jobs:
|
||||
contents: read
|
||||
packages: write
|
||||
steps:
|
||||
- name: install rsync
|
||||
run: sudo apt update && sudo apt install -y rsync
|
||||
- name: fetch repo
|
||||
uses: actions/checkout@v2
|
||||
|
||||
uses: actions/checkout@v3.1.0
|
||||
|
||||
- name: Download armhf lldap artifacts
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
@@ -296,7 +274,7 @@ jobs:
|
||||
with:
|
||||
name: armhf-migration-tool-bin
|
||||
path: bin/armhf-bin
|
||||
|
||||
|
||||
- name: Download aarch64 lldap artifacts
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
@@ -306,8 +284,8 @@ jobs:
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: aarch64-migration-tool-bin
|
||||
path: bin/aarch64-bin
|
||||
|
||||
path: bin/aarch64-bin
|
||||
|
||||
- name: Download amd64 lldap artifacts
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
@@ -318,20 +296,20 @@ jobs:
|
||||
with:
|
||||
name: amd64-migration-tool-bin
|
||||
path: bin/amd64-bin
|
||||
|
||||
|
||||
- name: check bin path
|
||||
run: ls -al bin/
|
||||
|
||||
|
||||
- name: Download llap ui artifacts
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: ui
|
||||
path: web
|
||||
|
||||
|
||||
- name: setup qemu
|
||||
uses: docker/setup-qemu-action@v2
|
||||
- uses: docker/setup-buildx-action@v2
|
||||
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v4
|
||||
@@ -354,33 +332,62 @@ jobs:
|
||||
key: ${{ runner.os }}-buildx-${{ github.sha }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-buildx-
|
||||
|
||||
|
||||
- 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: Build and push latest
|
||||
|
||||
######################
|
||||
#### latest build ####
|
||||
######################
|
||||
- name: Build and push latest alpine
|
||||
if: github.event_name != 'release'
|
||||
uses: docker/build-push-action@v3
|
||||
with:
|
||||
context: .
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
platforms: linux/amd64,linux/arm64
|
||||
file: ./.github/workflows/Dockerfile.ci.alpine
|
||||
tags: nitnelave/lldap:latest, nitnelave/lldap:latest-alpine
|
||||
cache-from: type=local,src=/tmp/.buildx-cache
|
||||
cache-to: type=local,dest=/tmp/.buildx-cache-new
|
||||
|
||||
- name: Build and push latest debian
|
||||
if: github.event_name != 'release'
|
||||
uses: docker/build-push-action@v3
|
||||
with:
|
||||
context: .
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
platforms: linux/amd64,linux/arm64,linux/arm/v7
|
||||
file: ./.github/workflows/Dockerfile.ci
|
||||
tags: nitnelave/lldap:latest
|
||||
#cache-from: type=gha
|
||||
#cache-to: type=gha,mode=max
|
||||
file: ./.github/workflows/Dockerfile.ci.debian
|
||||
tags: nitnelave/lldap:latest-debian
|
||||
cache-from: type=local,src=/tmp/.buildx-cache
|
||||
cache-to: type=local,dest=/tmp/.buildx-cache-new
|
||||
|
||||
- name: Build and push release
|
||||
|
||||
#######################
|
||||
#### release build ####
|
||||
#######################
|
||||
- name: Build and push release alpine
|
||||
if: github.event_name == 'release'
|
||||
uses: docker/build-push-action@v3
|
||||
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=local,src=/tmp/.buildx-cache
|
||||
cache-to: type=local,dest=/tmp/.buildx-cache-new
|
||||
|
||||
- name: Build and push release debian
|
||||
if: github.event_name == 'release'
|
||||
uses: docker/build-push-action@v3
|
||||
with:
|
||||
@@ -388,18 +395,14 @@ jobs:
|
||||
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
|
||||
tags: nitnelave/lldap:stable, 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 }}
|
||||
#cache-from: type=gha
|
||||
#cache-to: type=gha,mode=max
|
||||
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=local,src=/tmp/.buildx-cache
|
||||
cache-to: type=local,dest=/tmp/.buildx-cache-new
|
||||
|
||||
|
||||
- name: Move cache
|
||||
run: |
|
||||
rm -rf /tmp/.buildx-cache
|
||||
mv /tmp/.buildx-cache-new /tmp/.buildx-cache
|
||||
|
||||
run: rsync -r /tmp/.buildx-cache-new /tmp/.buildx-cache --delete
|
||||
|
||||
- name: Update repo description
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: peter-evans/dockerhub-description@v3
|
||||
8
.github/workflows/rust.yml
vendored
8
.github/workflows/rust.yml
vendored
@@ -34,7 +34,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v3.1.0
|
||||
- uses: Swatinem/rust-cache@v1
|
||||
- name: Build
|
||||
run: cargo build --verbose --workspace
|
||||
@@ -53,7 +53,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v3.1.0
|
||||
|
||||
- uses: Swatinem/rust-cache@v1
|
||||
|
||||
@@ -70,7 +70,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v3.1.0
|
||||
|
||||
- uses: Swatinem/rust-cache@v1
|
||||
|
||||
@@ -87,7 +87,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout sources
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v3.1.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
|
||||
|
||||
15
CHANGELOG.md
15
CHANGELOG.md
@@ -7,6 +7,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [0.4.1] - 2022-10-10
|
||||
|
||||
### Added
|
||||
|
||||
- Added support for STARTTLS for SMTP.
|
||||
- Added support for user profile pictures, including importing them from OpenLDAP.
|
||||
- Added support for every config value to be specified in a file.
|
||||
- Added support for PKCS1 keys.
|
||||
|
||||
### Changed
|
||||
|
||||
- The `dn` attribute is no longer returned as an attribute (it's still part of the response).
|
||||
- Empty attributes are no longer returned.
|
||||
- The docker image now uses the locally-downloaded assets.
|
||||
|
||||
## [0.4.0] - 2022-07-08
|
||||
|
||||
### Breaking
|
||||
|
||||
597
Cargo.lock
generated
597
Cargo.lock
generated
@@ -199,7 +199,9 @@ dependencies = [
|
||||
"futures-core",
|
||||
"http",
|
||||
"log",
|
||||
"tokio-rustls 0.22.0",
|
||||
"tokio-util 0.6.10",
|
||||
"webpki-roots 0.21.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -385,6 +387,45 @@ version = "0.10.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "619743e34b5ba4e9703bba34deac3427c72507c7159f5fd030aea8cac0cfe341"
|
||||
|
||||
[[package]]
|
||||
name = "asn1-rs"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "30ff05a702273012438132f449575dbc804e27b2f3cbe3069aa237d26c98fa33"
|
||||
dependencies = [
|
||||
"asn1-rs-derive",
|
||||
"asn1-rs-impl",
|
||||
"displaydoc 0.2.3",
|
||||
"nom 7.1.1",
|
||||
"num-traits",
|
||||
"rusticata-macros",
|
||||
"thiserror",
|
||||
"time 0.3.11",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "asn1-rs-derive"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "db8b7511298d5b7784b40b092d9e9dcd3a627a5707e4b5e507931ab0d44eeebf"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"synstructure",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "asn1-rs-impl"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2777730b2039ac0f95f093556e61b6d26cebed5393ca6f152717777cec3a42ed"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-trait"
|
||||
version = "0.1.56"
|
||||
@@ -557,6 +598,12 @@ version = "3.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "37ccbd214614c6783386c1af30caf03192f17891059cecc394b4fb119e363de3"
|
||||
|
||||
[[package]]
|
||||
name = "bytemuck"
|
||||
version = "1.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a5377c8865e74a160d21f29c2d40669f53286db6eab59b88540cbb12ffc8b835"
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
version = "1.4.3"
|
||||
@@ -654,6 +701,12 @@ dependencies = [
|
||||
"os_str_bytes",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "color_quant"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
|
||||
|
||||
[[package]]
|
||||
name = "combine"
|
||||
version = "3.8.1"
|
||||
@@ -913,6 +966,12 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "data-encoding"
|
||||
version = "2.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3ee2393c4a91429dffb4bedf19f4d6abf27d8a732c8ce4980305d782e5426d57"
|
||||
|
||||
[[package]]
|
||||
name = "der"
|
||||
version = "0.4.5"
|
||||
@@ -923,6 +982,20 @@ dependencies = [
|
||||
"crypto-bigint",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "der-parser"
|
||||
version = "7.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fe398ac75057914d7d07307bf67dc7f3f574a26783b4fc7805a20ffa9f506e82"
|
||||
dependencies = [
|
||||
"asn1-rs",
|
||||
"displaydoc 0.2.3",
|
||||
"nom 7.1.1",
|
||||
"num-bigint 0.4.3",
|
||||
"num-traits",
|
||||
"rusticata-macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_builder"
|
||||
version = "0.10.2"
|
||||
@@ -1030,6 +1103,17 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "displaydoc"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3bf95dc3f046b9da4f2d51833c0d3547d8564ef6910f5c1ed130306a75b92886"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dotenv"
|
||||
version = "0.15.0"
|
||||
@@ -1118,6 +1202,15 @@ dependencies = [
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "figment_file_provider_adapter"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c33106424fdbb9b1fd89c18072ba94666496a8a468178911b832a3e406988500"
|
||||
dependencies = [
|
||||
"figment",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "firestorm"
|
||||
version = "0.5.1"
|
||||
@@ -1161,21 +1254,6 @@ version = "1.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||
|
||||
[[package]]
|
||||
name = "foreign-types"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
|
||||
dependencies = [
|
||||
"foreign-types-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "foreign-types-shared"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
|
||||
|
||||
[[package]]
|
||||
name = "form_urlencoded"
|
||||
version = "1.0.1"
|
||||
@@ -1435,13 +1513,35 @@ dependencies = [
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "graphql-parser"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2ebc8013b4426d5b81a4364c419a95ed0b404af2b82e2457de52d9348f0e474"
|
||||
dependencies = [
|
||||
"combine",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "graphql_client"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a9b58571cfc3cc42c3e8ff44fc6cfbb6c0dea17ed22d20f9d8f1efc4e8209a3f"
|
||||
dependencies = [
|
||||
"graphql_query_derive",
|
||||
"graphql_query_derive 0.10.0",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "graphql_client"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7fc16d75d169fddb720d8f1c7aed6413e329e1584079b9734ff07266a193f5bc"
|
||||
dependencies = [
|
||||
"graphql_query_derive 0.11.0",
|
||||
"reqwest",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
@@ -1463,13 +1563,41 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "graphql_client_codegen"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f290ecfa3bea3e8a157899dc8a1d96ee7dd6405c18c8ddd213fc58939d18a0e9"
|
||||
dependencies = [
|
||||
"graphql-introspection-query",
|
||||
"graphql-parser 0.4.0",
|
||||
"heck 0.4.0",
|
||||
"lazy_static",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "graphql_query_derive"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e56b093bfda71de1da99758b036f4cc811fd2511c8a76f75680e9ffbd2bb4251"
|
||||
dependencies = [
|
||||
"graphql_client_codegen",
|
||||
"graphql_client_codegen 0.10.0",
|
||||
"proc-macro2",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "graphql_query_derive"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a755cc59cda2641ea3037b4f9f7ef40471c329f55c1fa2db6fa0bb7ae6c1f7ce"
|
||||
dependencies = [
|
||||
"graphql_client_codegen 0.11.0",
|
||||
"proc-macro2",
|
||||
"syn",
|
||||
]
|
||||
@@ -1577,17 +1705,6 @@ dependencies = [
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hostname"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"match_cfg",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http"
|
||||
version = "0.2.8"
|
||||
@@ -1653,16 +1770,16 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hyper-tls"
|
||||
version = "0.5.0"
|
||||
name = "hyper-rustls"
|
||||
version = "0.23.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905"
|
||||
checksum = "d87c48c02e0dc5e3b849a2041db3029fd066650f8f717c07bf8ed78ccb895cac"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"http",
|
||||
"hyper",
|
||||
"native-tls",
|
||||
"rustls 0.20.6",
|
||||
"tokio",
|
||||
"tokio-native-tls",
|
||||
"tokio-rustls 0.23.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1688,6 +1805,20 @@ version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cb56e1aa765b4b4f3aadfab769793b7087bb03a4ea4920644a6d238e2df5b9ed"
|
||||
|
||||
[[package]]
|
||||
name = "image"
|
||||
version = "0.24.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7e30ca2ecf7666107ff827a8e481de6a132a9b687ed3bb20bb1c144a36c00964"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"byteorder",
|
||||
"color_quant",
|
||||
"jpeg-decoder",
|
||||
"num-rational",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "1.6.2"
|
||||
@@ -1750,6 +1881,12 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jpeg-decoder"
|
||||
version = "0.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9478aa10f73e7528198d75109c8be5cd7d15fb530238040148d5f9a22d4c5b3b"
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.58"
|
||||
@@ -1761,9 +1898,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "juniper"
|
||||
version = "0.15.9"
|
||||
version = "0.15.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "21ac55c9084d08a7e315d78e2b15b7cc220f5eb67413c6ebf6967ee5de3b69fc"
|
||||
checksum = "4f478f229a8ab52ff242f3250c8b3b8fe0a59b5b934f9706b7bdbc980991a7b6"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"bson",
|
||||
@@ -1866,26 +2003,29 @@ dependencies = [
|
||||
"lazy_static",
|
||||
"lber",
|
||||
"log",
|
||||
"native-tls",
|
||||
"nom 2.2.1",
|
||||
"percent-encoding",
|
||||
"ring",
|
||||
"rustls 0.20.6",
|
||||
"rustls-native-certs",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"tokio-native-tls",
|
||||
"tokio-rustls 0.23.4",
|
||||
"tokio-stream",
|
||||
"tokio-util 0.7.3",
|
||||
"url",
|
||||
"x509-parser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ldap3_server"
|
||||
version = "0.1.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7873d5bd5baabdb77aa2d8a762bf8b1136f9f90cc90f44639b4c0d4486281dcb"
|
||||
name = "ldap3_proto"
|
||||
version = "0.2.3"
|
||||
source = "git+https://github.com/nitnelave/ldap3_server/?rev=7b50b2b82c383f5f70e02e11072bb916629ed2bc#7b50b2b82c383f5f70e02e11072bb916629ed2bc"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"lber",
|
||||
"tokio-util 0.6.10",
|
||||
"tokio-util 0.7.3",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1901,18 +2041,19 @@ dependencies = [
|
||||
"fastrand",
|
||||
"futures-io",
|
||||
"futures-util",
|
||||
"hostname",
|
||||
"httpdate",
|
||||
"idna",
|
||||
"mime",
|
||||
"native-tls",
|
||||
"nom 7.1.1",
|
||||
"once_cell",
|
||||
"quoted_printable",
|
||||
"rustls 0.20.6",
|
||||
"rustls-pemfile",
|
||||
"serde",
|
||||
"socket2",
|
||||
"tokio",
|
||||
"tokio-native-tls",
|
||||
"tokio-rustls 0.23.4",
|
||||
"webpki-roots 0.22.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1979,28 +2120,31 @@ dependencies = [
|
||||
"cron",
|
||||
"derive_builder",
|
||||
"figment",
|
||||
"figment_file_provider_adapter",
|
||||
"futures",
|
||||
"futures-util",
|
||||
"hmac 0.10.1",
|
||||
"http",
|
||||
"image",
|
||||
"itertools",
|
||||
"juniper",
|
||||
"juniper_actix",
|
||||
"jwt",
|
||||
"ldap3_server",
|
||||
"ldap3_proto",
|
||||
"lettre",
|
||||
"lldap_auth",
|
||||
"log",
|
||||
"mockall",
|
||||
"native-tls",
|
||||
"opaque-ke",
|
||||
"openssl-sys",
|
||||
"orion",
|
||||
"rand 0.8.5",
|
||||
"rustls 0.20.6",
|
||||
"rustls-pemfile",
|
||||
"sea-query",
|
||||
"sea-query-binder",
|
||||
"secstr",
|
||||
"serde",
|
||||
"serde_bytes",
|
||||
"serde_json",
|
||||
"sha2",
|
||||
"sqlx",
|
||||
@@ -2008,9 +2152,9 @@ dependencies = [
|
||||
"thiserror",
|
||||
"time 0.2.27",
|
||||
"tokio",
|
||||
"tokio-native-tls",
|
||||
"tokio-rustls 0.23.4",
|
||||
"tokio-stream",
|
||||
"tokio-util 0.6.10",
|
||||
"tokio-util 0.7.3",
|
||||
"tracing",
|
||||
"tracing-actix-web",
|
||||
"tracing-attributes",
|
||||
@@ -2025,9 +2169,11 @@ name = "lldap_app"
|
||||
version = "0.4.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"base64",
|
||||
"chrono",
|
||||
"graphql_client",
|
||||
"graphql_client 0.10.0",
|
||||
"http",
|
||||
"image",
|
||||
"indexmap",
|
||||
"jwt",
|
||||
"lldap_auth",
|
||||
@@ -2099,12 +2245,6 @@ dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "match_cfg"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4"
|
||||
|
||||
[[package]]
|
||||
name = "matchers"
|
||||
version = "0.1.0"
|
||||
@@ -2148,7 +2288,8 @@ name = "migration-tool"
|
||||
version = "0.3.0-alpha.1"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"graphql_client",
|
||||
"base64",
|
||||
"graphql_client 0.11.0",
|
||||
"ldap3",
|
||||
"lldap_auth",
|
||||
"rand 0.8.5",
|
||||
@@ -2251,24 +2392,6 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "native-tls"
|
||||
version = "0.2.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fd7e2f3618557f980e0b17e8856252eee3c97fa12c54dff0ca290fb6266ca4a9"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"libc",
|
||||
"log",
|
||||
"openssl",
|
||||
"openssl-probe",
|
||||
"openssl-sys",
|
||||
"schannel",
|
||||
"security-framework",
|
||||
"security-framework-sys",
|
||||
"tempfile",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nom"
|
||||
version = "2.2.1"
|
||||
@@ -2322,6 +2445,17 @@ dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-bigint"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f"
|
||||
dependencies = [
|
||||
"autocfg 1.1.0",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-bigint-dig"
|
||||
version = "0.7.0"
|
||||
@@ -2361,6 +2495,17 @@ dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-rational"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0"
|
||||
dependencies = [
|
||||
"autocfg 1.1.0",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.15"
|
||||
@@ -2381,6 +2526,15 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_threads"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "object"
|
||||
version = "0.28.4"
|
||||
@@ -2390,6 +2544,15 @@ dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "oid-registry"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "38e20717fa0541f39bd146692035c37bedfa532b3e5071b35761082407546b2a"
|
||||
dependencies = [
|
||||
"asn1-rs",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.12.0"
|
||||
@@ -2411,7 +2574,7 @@ dependencies = [
|
||||
"base64",
|
||||
"curve25519-dalek",
|
||||
"digest",
|
||||
"displaydoc",
|
||||
"displaydoc 0.1.7",
|
||||
"generic-array",
|
||||
"generic-bytes",
|
||||
"hkdf",
|
||||
@@ -2423,61 +2586,12 @@ dependencies = [
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "openssl"
|
||||
version = "0.10.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fb81a6430ac911acb25fe5ac8f1d2af1b4ea8a4fdfda0f1ee4292af2e2d8eb0e"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cfg-if",
|
||||
"foreign-types",
|
||||
"libc",
|
||||
"once_cell",
|
||||
"openssl-macros",
|
||||
"openssl-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "openssl-macros"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "openssl-probe"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
|
||||
|
||||
[[package]]
|
||||
name = "openssl-src"
|
||||
version = "111.22.0+1.1.1q"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f31f0d509d1c1ae9cada2f9539ff8f37933831fd5098879e482aa687d659853"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "openssl-sys"
|
||||
version = "0.9.74"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "835363342df5fba8354c5b453325b110ffd54044e588c539cf2f20a8014e4cb1"
|
||||
dependencies = [
|
||||
"autocfg 1.1.0",
|
||||
"cc",
|
||||
"libc",
|
||||
"openssl-src",
|
||||
"pkg-config",
|
||||
"vcpkg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "orion"
|
||||
version = "0.16.1"
|
||||
@@ -2880,9 +2994,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "requestty"
|
||||
version = "0.4.0"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "20362058549acfb4b04b014aa182de685d7ded326eaf092db11030431c92693a"
|
||||
checksum = "8d06fb394ca73d15ad0c7bbc673459506a851a84586cd90d67d42932a280281e"
|
||||
dependencies = [
|
||||
"requestty-ui",
|
||||
"smallvec",
|
||||
@@ -2891,9 +3005,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "requestty-ui"
|
||||
version = "0.4.0"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f7b232b5ca049619df09d10909dfe32020bc0d402fd98cb5207fc1d2aa53633e"
|
||||
checksum = "31a4bce6f730d12e36993944036e2f93e88033d8a78734d8734fdb0043662cae"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"crossterm",
|
||||
@@ -2917,28 +3031,45 @@ dependencies = [
|
||||
"http",
|
||||
"http-body",
|
||||
"hyper",
|
||||
"hyper-tls",
|
||||
"hyper-rustls",
|
||||
"ipnet",
|
||||
"js-sys",
|
||||
"lazy_static",
|
||||
"log",
|
||||
"mime",
|
||||
"native-tls",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"rustls 0.20.6",
|
||||
"rustls-pemfile",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_urlencoded",
|
||||
"tokio",
|
||||
"tokio-native-tls",
|
||||
"tokio-rustls 0.23.4",
|
||||
"tower-service",
|
||||
"url",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
"web-sys",
|
||||
"webpki-roots 0.22.4",
|
||||
"winreg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ring"
|
||||
version = "0.16.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
"once_cell",
|
||||
"spin 0.5.2",
|
||||
"untrusted",
|
||||
"web-sys",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rsa"
|
||||
version = "0.5.0"
|
||||
@@ -2995,6 +3126,61 @@ dependencies = [
|
||||
"semver 1.0.12",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rusticata-macros"
|
||||
version = "4.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632"
|
||||
dependencies = [
|
||||
"nom 7.1.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls"
|
||||
version = "0.19.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "35edb675feee39aec9c99fa5ff985081995a06d594114ae14cbe797ad7b7a6d7"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"log",
|
||||
"ring",
|
||||
"sct 0.6.1",
|
||||
"webpki 0.21.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls"
|
||||
version = "0.20.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5aab8ee6c7097ed6057f43c187a62418d0c05a4bd5f18b3571db50ee0f9ce033"
|
||||
dependencies = [
|
||||
"log",
|
||||
"ring",
|
||||
"sct 0.7.0",
|
||||
"webpki 0.22.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-native-certs"
|
||||
version = "0.6.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0167bac7a9f490495f3c33013e7722b53cb087ecbe082fb0c6387c96f634ea50"
|
||||
dependencies = [
|
||||
"openssl-probe",
|
||||
"rustls-pemfile",
|
||||
"schannel",
|
||||
"security-framework",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-pemfile"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e7522c9de787ff061458fe9a829dc790a3f5b22dc571694fc5883f448b94d9a9"
|
||||
dependencies = [
|
||||
"base64",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.10"
|
||||
@@ -3017,6 +3203,26 @@ version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
|
||||
|
||||
[[package]]
|
||||
name = "sct"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b362b83898e0e69f38515b82ee15aa80636befe47c3b6d3d89a911e78fc228ce"
|
||||
dependencies = [
|
||||
"ring",
|
||||
"untrusted",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sct"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4"
|
||||
dependencies = [
|
||||
"ring",
|
||||
"untrusted",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sea-query"
|
||||
version = "0.25.2"
|
||||
@@ -3125,6 +3331,15 @@ dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_bytes"
|
||||
version = "0.11.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cfc50e8183eeeb6178dcb167ae34a8051d63535023ae38b5d8d12beae193d37b"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.137"
|
||||
@@ -3357,12 +3572,13 @@ dependencies = [
|
||||
"log",
|
||||
"md-5",
|
||||
"memchr",
|
||||
"num-bigint",
|
||||
"num-bigint 0.3.3",
|
||||
"once_cell",
|
||||
"paste",
|
||||
"percent-encoding",
|
||||
"rand 0.8.5",
|
||||
"rsa",
|
||||
"rustls 0.19.1",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha-1",
|
||||
@@ -3374,6 +3590,8 @@ dependencies = [
|
||||
"thiserror",
|
||||
"tokio-stream",
|
||||
"url",
|
||||
"webpki 0.21.4",
|
||||
"webpki-roots 0.21.1",
|
||||
"whoami",
|
||||
]
|
||||
|
||||
@@ -3403,10 +3621,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4db708cd3e459078f85f39f96a00960bd841f66ee2a669e90bf36907f5a79aae"
|
||||
dependencies = [
|
||||
"actix-rt",
|
||||
"native-tls",
|
||||
"once_cell",
|
||||
"tokio",
|
||||
"tokio-native-tls",
|
||||
"tokio-rustls 0.22.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3608,11 +3825,23 @@ dependencies = [
|
||||
"libc",
|
||||
"standback",
|
||||
"stdweb",
|
||||
"time-macros",
|
||||
"time-macros 0.1.1",
|
||||
"version_check",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.3.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72c91f41dcb2f096c05f0873d667dceec1087ce5bcf984ec8ffb19acddbb3217"
|
||||
dependencies = [
|
||||
"itoa 1.0.2",
|
||||
"libc",
|
||||
"num_threads",
|
||||
"time-macros 0.2.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time-macros"
|
||||
version = "0.1.1"
|
||||
@@ -3623,6 +3852,12 @@ dependencies = [
|
||||
"time-macros-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time-macros"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42657b1a6f4d817cda8e7a0ace261fe0cc946cf3a80314390b22cc61ae080792"
|
||||
|
||||
[[package]]
|
||||
name = "time-macros-impl"
|
||||
version = "0.1.2"
|
||||
@@ -3683,13 +3918,25 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-native-tls"
|
||||
version = "0.3.0"
|
||||
name = "tokio-rustls"
|
||||
version = "0.22.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f7d995660bd2b7f8c1568414c1126076c13fbb725c40112dc0120b78eb9b717b"
|
||||
checksum = "bc6844de72e57df1980054b38be3a9f4702aba4858be64dd700181a8a6d0e1b6"
|
||||
dependencies = [
|
||||
"native-tls",
|
||||
"rustls 0.19.1",
|
||||
"tokio",
|
||||
"webpki 0.21.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-rustls"
|
||||
version = "0.23.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59"
|
||||
dependencies = [
|
||||
"rustls 0.20.6",
|
||||
"tokio",
|
||||
"webpki 0.22.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3938,6 +4185,12 @@ dependencies = [
|
||||
"void",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "untrusted"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
|
||||
|
||||
[[package]]
|
||||
name = "url"
|
||||
version = "2.2.2"
|
||||
@@ -4132,6 +4385,44 @@ dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "webpki"
|
||||
version = "0.21.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b8e38c0608262c46d4a56202ebabdeb094cef7e560ca7a226c6bf055188aa4ea"
|
||||
dependencies = [
|
||||
"ring",
|
||||
"untrusted",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "webpki"
|
||||
version = "0.22.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd"
|
||||
dependencies = [
|
||||
"ring",
|
||||
"untrusted",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "webpki-roots"
|
||||
version = "0.21.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aabe153544e473b775453675851ecc86863d2a81d786d741f6b76778f2a48940"
|
||||
dependencies = [
|
||||
"webpki 0.21.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "webpki-roots"
|
||||
version = "0.22.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f1c760f0d366a6c24a02ed7816e23e691f5d92291f94d15e836006fd11b04daf"
|
||||
dependencies = [
|
||||
"webpki 0.22.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "whoami"
|
||||
version = "1.2.1"
|
||||
@@ -4225,6 +4516,24 @@ dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "x509-parser"
|
||||
version = "0.13.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9fb9bace5b5589ffead1afb76e43e34cff39cd0f3ce7e170ae0c29e53b88eb1c"
|
||||
dependencies = [
|
||||
"asn1-rs",
|
||||
"base64",
|
||||
"data-encoding",
|
||||
"der-parser",
|
||||
"lazy_static",
|
||||
"nom 7.1.1",
|
||||
"oid-registry",
|
||||
"rusticata-macros",
|
||||
"thiserror",
|
||||
"time 0.3.11",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "yansi"
|
||||
version = "0.5.1"
|
||||
@@ -4317,7 +4626,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "yew_form"
|
||||
version = "0.1.8"
|
||||
source = "git+https://github.com/sassman/yew_form/?rev=67050812695b7a8a90b81b0637e347fc6629daed#67050812695b7a8a90b81b0637e347fc6629daed"
|
||||
source = "git+https://github.com/jfbilodeau/yew_form?rev=67050812695b7a8a90b81b0637e347fc6629daed#67050812695b7a8a90b81b0637e347fc6629daed"
|
||||
dependencies = [
|
||||
"validator",
|
||||
"validator_derive",
|
||||
@@ -4327,7 +4636,7 @@ dependencies = [
|
||||
[[package]]
|
||||
name = "yew_form_derive"
|
||||
version = "0.1.8"
|
||||
source = "git+https://github.com/sassman/yew_form/?rev=67050812695b7a8a90b81b0637e347fc6629daed#67050812695b7a8a90b81b0637e347fc6629daed"
|
||||
source = "git+https://github.com/jfbilodeau/yew_form?rev=67050812695b7a8a90b81b0637e347fc6629daed#67050812695b7a8a90b81b0637e347fc6629daed"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
||||
12
Cargo.toml
12
Cargo.toml
@@ -8,11 +8,7 @@ members = [
|
||||
|
||||
default-members = ["server"]
|
||||
|
||||
# TODO: remove when there's a new release.
|
||||
[patch.crates-io.yew_form]
|
||||
git = 'https://github.com/sassman/yew_form/'
|
||||
rev = '67050812695b7a8a90b81b0637e347fc6629daed'
|
||||
|
||||
[patch.crates-io.yew_form_derive]
|
||||
git = 'https://github.com/sassman/yew_form/'
|
||||
rev = '67050812695b7a8a90b81b0637e347fc6629daed'
|
||||
# Remove once https://github.com/kanidm/ldap3_proto/pull/8 is merged.
|
||||
[patch.crates-io.ldap3_proto]
|
||||
git = 'https://github.com/nitnelave/ldap3_server/'
|
||||
rev = '7b50b2b82c383f5f70e02e11072bb916629ed2bc'
|
||||
|
||||
76
README.md
76
README.md
@@ -36,7 +36,7 @@
|
||||
- [Client configuration](#Client-configuration)
|
||||
- [Compatible services](#compatible-services)
|
||||
- [General configuration guide](#general-configuration-guide)
|
||||
- [Sample cient configurations](#Sample-client-configurations)
|
||||
- [Sample client configurations](#Sample-client-configurations)
|
||||
- [Comparisons with other services](#Comparisons-with-other-services)
|
||||
- [vs OpenLDAP](#vs-openldap)
|
||||
- [vs FreeIPA](#vs-freeipa)
|
||||
@@ -90,14 +90,19 @@ Configure the server by copying the `lldap_config.docker_template.toml` to
|
||||
Environment variables should be prefixed with `LLDAP_` to override the
|
||||
configuration.
|
||||
|
||||
If the `lldap_config.toml` doesn't exist when starting up, LLDAP will use default one. The default admin password is `password`, you can change the password later using the web interface.
|
||||
|
||||
Secrets can also be set through a file. The filename should be specified by the
|
||||
variables `LLDAP_JWT_SECRET_FILE` or `LLDAP_LDAP_USER_PASS_FILE`, and the file
|
||||
contents are loaded into the respective configuration parameters. Note that
|
||||
`_FILE` variables take precedence.
|
||||
|
||||
Example for docker compose:
|
||||
Example for docker compose for `:stable` tag:
|
||||
* When defined with `user: ##:##` , ensure `/data` directory had permission for the defined user, else `1000:1000` used.
|
||||
|
||||
```yaml
|
||||
version: '3'
|
||||
|
||||
volumes:
|
||||
lldap_data:
|
||||
driver: local
|
||||
@@ -122,11 +127,56 @@ services:
|
||||
- LLDAP_LDAP_BASE_DN=dc=example,dc=com
|
||||
```
|
||||
|
||||
Example for docker compose for `:latest` tag:
|
||||
* `:latest` tag image contain recent pushed codes or feature test, breaks is expected.
|
||||
* If `UID` and `GID` no defined LLDAP will use default `UID` and `GID` number `1000`
|
||||
|
||||
```yaml
|
||||
version: '3'
|
||||
|
||||
volumes:
|
||||
lldap_data:
|
||||
driver: local
|
||||
|
||||
services:
|
||||
lldap:
|
||||
image: nitnelave/lldap:latest
|
||||
ports:
|
||||
# For LDAP
|
||||
- "3890:3890"
|
||||
# For the web front-end
|
||||
- "17170:17170"
|
||||
volumes:
|
||||
- "lldap_data:/data"
|
||||
# Alternatively, you can mount a local folder
|
||||
# - "./lldap_data:/data"
|
||||
environment:
|
||||
- UID=####
|
||||
- GID=####
|
||||
- LLDAP_JWT_SECRET=REPLACE_WITH_RANDOM
|
||||
- LLDAP_LDAP_USER_PASS=REPLACE_WITH_PASSWORD
|
||||
- LLDAP_LDAP_BASE_DN=dc=example,dc=com
|
||||
```
|
||||
|
||||
Then the service will listen on two ports, one for LDAP and one for the web
|
||||
front-end.
|
||||
|
||||
### From source
|
||||
|
||||
To compile the project, you'll need:
|
||||
|
||||
* npm, curl: `sudo apt install curl npm`
|
||||
* Rust/Cargo: [rustup.rs](https://rustup.rs/)
|
||||
|
||||
Then you can compile the server (and the migration tool if you want):
|
||||
|
||||
```shell
|
||||
cargo build --release -p lldap -p migration-tool
|
||||
```
|
||||
|
||||
The resulting binaries will be in `./target/release/`. Alternatively, you can
|
||||
just run `cargo run -- run` to run the server.
|
||||
|
||||
To bring up the server, you'll need to compile the frontend. In addition to
|
||||
cargo, you'll need:
|
||||
|
||||
@@ -136,10 +186,13 @@ cargo, you'll need:
|
||||
Then you can build the frontend files with `./app/build.sh` (you'll need to run
|
||||
this after every front-end change to update the WASM package served).
|
||||
|
||||
To bring up the server, just run `cargo run`. The default config is in
|
||||
`src/infra/configuration.rs`, but you can override it by creating an
|
||||
`lldap_config.toml`, setting environment variables or passing arguments to
|
||||
`cargo run`.
|
||||
The default config is in `src/infra/configuration.rs`, but you can override it
|
||||
by creating an `lldap_config.toml`, setting environment variables or passing
|
||||
arguments to `cargo run`. Have a look at the docker template:
|
||||
`lldap_config.docker_template.toml`.
|
||||
|
||||
You can also install it as a systemd service, see
|
||||
[lldap.service](example_configs/lldap.service).
|
||||
|
||||
### Cross-compilation
|
||||
|
||||
@@ -198,23 +251,28 @@ administration access to many services.
|
||||
Some specific clients have been tested to work and come with sample
|
||||
configuration files, or guides. See the [`example_configs`](example_configs)
|
||||
folder for help with:
|
||||
- [Airsonic Advanced](example_configs/airsonic-advanced.md)
|
||||
- [Apache Guacamole](example_configs/apacheguacamole.md)
|
||||
- [Authelia](example_configs/authelia_config.yml)
|
||||
- [Bookstack](example_configs/bookstack.env.example)
|
||||
- [Calibre-Web](example_configs/calibre_web.md)
|
||||
- [Dokuwiki](example_configs/dokuwiki.md)
|
||||
- [Dolibarr](example_configs/dolibarr.md)
|
||||
- [Emby](example_configs/emby.md)
|
||||
- [Gitea](example_configs/gitea.md)
|
||||
- [Grafana](example_configs/grafana_ldap_config.toml)
|
||||
- [Hedgedoc](example_configs/hedgedoc.md)
|
||||
- [Jellyfin](example_configs/jellyfin.md)
|
||||
- [Jisti Meet](example_configs/jitsi_meet.conf)
|
||||
- [Jitsi Meet](example_configs/jitsi_meet.conf)
|
||||
- [KeyCloak](example_configs/keycloak.md)
|
||||
- [Matrix](example_configs/matrix_synapse.yml)
|
||||
- [Nextcloud](example_configs/nextcloud.md)
|
||||
- [Organizr](example_configs/Organizr.md)
|
||||
- [Portainer](example_configs/portainer.md)
|
||||
- [Seafile](example_configs/seafile.md)
|
||||
- [Syncthing](example_configs/syncthing.md)
|
||||
- [WG Portal](example_configs/wg_portal.env.example)
|
||||
- [XBackBone](example_configs/xbackbone_config.php)
|
||||
|
||||
## Comparisons with other services
|
||||
|
||||
@@ -232,7 +290,7 @@ OpenLDAP doesn't come with a UI: if you want a web interface, you'll have to
|
||||
install one (not that many that look nice) and configure it.
|
||||
|
||||
LLDAP is much simpler to setup, has a much smaller image (10x smaller, 20x if
|
||||
you add PhpLdapAdmin), and comes packed with its own purpose-built wed UI.
|
||||
you add PhpLdapAdmin), and comes packed with its own purpose-built web UI.
|
||||
|
||||
### vs FreeIPA
|
||||
|
||||
@@ -244,7 +302,7 @@ management and so on.
|
||||
If you need all of that, go for it! Keep in mind that a more complex system is
|
||||
more complex to maintain, though.
|
||||
|
||||
LLDAP is much lighter to run (<100 MB RAM including the DB), easier to
|
||||
LLDAP is much lighter to run (<10 MB RAM including the DB), easier to
|
||||
configure (no messing around with DNS or security policies) and simpler to
|
||||
use. It also comes conveniently packed in a docker container.
|
||||
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
[package]
|
||||
name = "lldap_app"
|
||||
version = "0.4.0"
|
||||
version = "0.4.1"
|
||||
authors = ["Valentin Tolmer <valentin@tolmer.fr>"]
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1"
|
||||
base64 = "0.13"
|
||||
graphql_client = "0.10"
|
||||
http = "0.2"
|
||||
jwt = "0.13"
|
||||
@@ -18,8 +19,6 @@ wasm-bindgen = "0.2"
|
||||
yew = "0.18"
|
||||
yewtil = "*"
|
||||
yew-router = "0.15"
|
||||
yew_form = "0.1.8"
|
||||
yew_form_derive = "*"
|
||||
|
||||
# Needed because of https://github.com/tkaitchuck/aHash/issues/95
|
||||
indexmap = "=1.6.2"
|
||||
@@ -29,6 +28,7 @@ version = "0.3"
|
||||
features = [
|
||||
"Document",
|
||||
"Element",
|
||||
"FileReader",
|
||||
"HtmlDocument",
|
||||
"HtmlInputElement",
|
||||
"HtmlOptionElement",
|
||||
@@ -47,5 +47,18 @@ features = [
|
||||
path = "../auth"
|
||||
features = [ "opaque_client" ]
|
||||
|
||||
[dependencies.image]
|
||||
features = ["jpeg"]
|
||||
default-features = false
|
||||
version = "0.24"
|
||||
|
||||
[dependencies.yew_form]
|
||||
git = "https://github.com/jfbilodeau/yew_form"
|
||||
rev = "67050812695b7a8a90b81b0637e347fc6629daed"
|
||||
|
||||
[dependencies.yew_form_derive]
|
||||
git = "https://github.com/jfbilodeau/yew_form"
|
||||
rev = "67050812695b7a8a90b81b0637e347fc6629daed"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
@@ -2,6 +2,8 @@ query GetGroupDetails($id: Int!) {
|
||||
group(groupId: $id) {
|
||||
id
|
||||
displayName
|
||||
creationDate
|
||||
uuid
|
||||
users {
|
||||
id
|
||||
displayName
|
||||
|
||||
@@ -2,5 +2,6 @@ query GetGroupList {
|
||||
groups {
|
||||
id
|
||||
displayName
|
||||
creationDate
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,9 @@ query GetUserDetails($id: String!) {
|
||||
displayName
|
||||
firstName
|
||||
lastName
|
||||
avatar
|
||||
creationDate
|
||||
uuid
|
||||
groups {
|
||||
id
|
||||
displayName
|
||||
|
||||
@@ -36,7 +36,7 @@ impl OpaqueData {
|
||||
}
|
||||
|
||||
/// The fields of the form, with the constraints.
|
||||
#[derive(Model, Validate, PartialEq, Clone, Default)]
|
||||
#[derive(Model, Validate, PartialEq, Eq, Clone, Default)]
|
||||
pub struct FormModel {
|
||||
#[validate(custom(
|
||||
function = "empty_or_long",
|
||||
@@ -64,7 +64,7 @@ pub struct ChangePasswordForm {
|
||||
route_dispatcher: RouteAgentDispatcher,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Properties)]
|
||||
#[derive(Clone, PartialEq, Eq, Properties)]
|
||||
pub struct Props {
|
||||
pub username: String,
|
||||
pub is_admin: bool,
|
||||
@@ -252,6 +252,7 @@ impl Component for ChangePasswordForm {
|
||||
<Field
|
||||
form=&self.form
|
||||
field_name="password"
|
||||
input_type="password"
|
||||
class="form-control"
|
||||
class_invalid="is-invalid has-error"
|
||||
class_valid="has-success"
|
||||
@@ -271,6 +272,7 @@ impl Component for ChangePasswordForm {
|
||||
<Field
|
||||
form=&self.form
|
||||
field_name="confirm_password"
|
||||
input_type="password"
|
||||
class="form-control"
|
||||
class_invalid="is-invalid has-error"
|
||||
class_valid="has-success"
|
||||
|
||||
@@ -28,7 +28,7 @@ pub struct CreateGroupForm {
|
||||
form: yew_form::Form<CreateGroupModel>,
|
||||
}
|
||||
|
||||
#[derive(Model, Validate, PartialEq, Clone, Default)]
|
||||
#[derive(Model, Validate, PartialEq, Eq, Clone, Default)]
|
||||
pub struct CreateGroupModel {
|
||||
#[validate(length(min = 1, message = "Groupname is required"))]
|
||||
groupname: String,
|
||||
|
||||
@@ -32,7 +32,7 @@ pub struct CreateUserForm {
|
||||
form: yew_form::Form<CreateUserModel>,
|
||||
}
|
||||
|
||||
#[derive(Model, Validate, PartialEq, Clone, Default)]
|
||||
#[derive(Model, Validate, PartialEq, Eq, Clone, Default)]
|
||||
pub struct CreateUserModel {
|
||||
#[validate(length(min = 1, message = "Username is required"))]
|
||||
username: String,
|
||||
@@ -90,6 +90,7 @@ impl CommonComponent<CreateUserForm> for CreateUserForm {
|
||||
displayName: to_option(model.display_name),
|
||||
firstName: to_option(model.first_name),
|
||||
lastName: to_option(model.last_name),
|
||||
avatar: None,
|
||||
},
|
||||
};
|
||||
self.common.call_graphql::<CreateUser, _>(
|
||||
|
||||
@@ -40,7 +40,7 @@ pub enum Msg {
|
||||
OnUserRemovedFromGroup((String, i64)),
|
||||
}
|
||||
|
||||
#[derive(yew::Properties, Clone, PartialEq)]
|
||||
#[derive(yew::Properties, Clone, PartialEq, Eq)]
|
||||
pub struct Props {
|
||||
pub group_id: i64,
|
||||
}
|
||||
@@ -68,6 +68,45 @@ impl GroupDetails {
|
||||
}
|
||||
}
|
||||
|
||||
fn view_details(&self, g: &Group) -> Html {
|
||||
html! {
|
||||
<>
|
||||
<h3>{g.display_name.to_string()}</h3>
|
||||
<div class="py-3">
|
||||
<form class="form">
|
||||
<div class="form-group row mb-3">
|
||||
<label for="displayName"
|
||||
class="form-label col-4 col-form-label">
|
||||
{"Group: "}
|
||||
</label>
|
||||
<div class="col-8">
|
||||
<span id="groupId" class="form-constrol-static">{g.display_name.to_string()}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row mb-3">
|
||||
<label for="creationDate"
|
||||
class="form-label col-4 col-form-label">
|
||||
{"Creation date: "}
|
||||
</label>
|
||||
<div class="col-8">
|
||||
<span id="creationDate" class="form-constrol-static">{g.creation_date.date().naive_local()}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row mb-3">
|
||||
<label for="uuid"
|
||||
class="form-label col-4 col-form-label">
|
||||
{"UUID: "}
|
||||
</label>
|
||||
<div class="col-8">
|
||||
<span id="uuid" class="form-constrol-static">{g.uuid.to_string()}</span>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
}
|
||||
|
||||
fn view_user_list(&self, g: &Group) -> Html {
|
||||
let make_user_row = |user: &User| {
|
||||
let user_id = user.id.clone();
|
||||
@@ -92,7 +131,6 @@ impl GroupDetails {
|
||||
};
|
||||
html! {
|
||||
<>
|
||||
<h3>{g.display_name.to_string()}</h3>
|
||||
<h5 class="fw-bold">{"Members"}</h5>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped">
|
||||
@@ -201,6 +239,7 @@ impl Component for GroupDetails {
|
||||
(Some(u), error) => {
|
||||
html! {
|
||||
<div>
|
||||
{self.view_details(u)}
|
||||
{self.view_user_list(u)}
|
||||
{self.view_add_user_button(u)}
|
||||
{self.view_messages(error)}
|
||||
|
||||
@@ -13,7 +13,7 @@ use yew::prelude::*;
|
||||
#[graphql(
|
||||
schema_path = "../schema.graphql",
|
||||
query_path = "queries/get_group_list.graphql",
|
||||
response_derives = "Debug,Clone,PartialEq",
|
||||
response_derives = "Debug,Clone,PartialEq,Eq",
|
||||
custom_scalars_module = "crate::infra::graphql"
|
||||
)]
|
||||
pub struct GetGroupList;
|
||||
@@ -97,7 +97,8 @@ impl GroupTable {
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{"Groups"}</th>
|
||||
<th>{"Group name"}</th>
|
||||
<th>{"Creation date"}</th>
|
||||
<th>{"Delete"}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -122,6 +123,9 @@ impl GroupTable {
|
||||
{&group.display_name}
|
||||
</Link>
|
||||
</td>
|
||||
<td>
|
||||
{&group.creation_date.date().naive_local()}
|
||||
</td>
|
||||
<td>
|
||||
<DeleteGroup
|
||||
group=group.clone()
|
||||
|
||||
@@ -19,7 +19,7 @@ pub struct LoginForm {
|
||||
}
|
||||
|
||||
/// The fields of the form, with the constraints.
|
||||
#[derive(Model, Validate, PartialEq, Clone, Default)]
|
||||
#[derive(Model, Validate, PartialEq, Eq, Clone, Default)]
|
||||
pub struct FormModel {
|
||||
#[validate(length(min = 1, message = "Missing username"))]
|
||||
username: String,
|
||||
|
||||
@@ -18,7 +18,7 @@ pub struct ResetPasswordStep1Form {
|
||||
}
|
||||
|
||||
/// The fields of the form, with the constraints.
|
||||
#[derive(Model, Validate, PartialEq, Clone, Default)]
|
||||
#[derive(Model, Validate, PartialEq, Eq, Clone, Default)]
|
||||
pub struct FormModel {
|
||||
#[validate(length(min = 1, message = "Missing username"))]
|
||||
username: String,
|
||||
|
||||
@@ -20,7 +20,7 @@ use yew_router::{
|
||||
};
|
||||
|
||||
/// The fields of the form, with the constraints.
|
||||
#[derive(Model, Validate, PartialEq, Clone, Default)]
|
||||
#[derive(Model, Validate, PartialEq, Eq, Clone, Default)]
|
||||
pub struct FormModel {
|
||||
#[validate(length(min = 8, message = "Invalid password. Min length: 8"))]
|
||||
password: String,
|
||||
@@ -36,7 +36,7 @@ pub struct ResetPasswordStep2Form {
|
||||
route_dispatcher: RouteAgentDispatcher,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Properties)]
|
||||
#[derive(Clone, PartialEq, Eq, Properties)]
|
||||
pub struct Props {
|
||||
pub token: String,
|
||||
}
|
||||
|
||||
@@ -81,7 +81,7 @@ pub struct SelectOption {
|
||||
props: SelectOptionProps,
|
||||
}
|
||||
|
||||
#[derive(yew::Properties, Clone, PartialEq, Debug)]
|
||||
#[derive(yew::Properties, Clone, PartialEq, Eq, Debug)]
|
||||
pub struct SelectOptionProps {
|
||||
pub value: String,
|
||||
pub text: String,
|
||||
|
||||
@@ -40,7 +40,7 @@ pub enum Msg {
|
||||
OnUserRemovedFromGroup((String, i64)),
|
||||
}
|
||||
|
||||
#[derive(yew::Properties, Clone, PartialEq)]
|
||||
#[derive(yew::Properties, Clone, PartialEq, Eq)]
|
||||
pub struct Props {
|
||||
pub username: String,
|
||||
pub is_admin: bool,
|
||||
@@ -198,8 +198,7 @@ impl Component for UserDetails {
|
||||
<>
|
||||
<h3>{u.id.to_string()}</h3>
|
||||
<UserDetailsForm
|
||||
user=u.clone()
|
||||
on_error=self.common.callback(Msg::OnError)/>
|
||||
user=u.clone() />
|
||||
<div class="row justify-content-center">
|
||||
<NavButton
|
||||
route=AppRoute::ChangePassword(u.id.clone())
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
use std::str::FromStr;
|
||||
|
||||
use crate::{
|
||||
components::user_details::User,
|
||||
infra::common_component::{CommonComponent, CommonComponentParts},
|
||||
@@ -5,11 +7,39 @@ use crate::{
|
||||
use anyhow::{bail, Error, Result};
|
||||
use graphql_client::GraphQLQuery;
|
||||
use validator_derive::Validate;
|
||||
use yew::prelude::*;
|
||||
use wasm_bindgen::JsCast;
|
||||
use yew::{prelude::*, services::ConsoleService};
|
||||
use yew_form_derive::Model;
|
||||
|
||||
#[derive(PartialEq, Eq, Clone, Default)]
|
||||
struct JsFile {
|
||||
file: Option<web_sys::File>,
|
||||
contents: Option<Vec<u8>>,
|
||||
}
|
||||
|
||||
impl ToString for JsFile {
|
||||
fn to_string(&self) -> String {
|
||||
self.file
|
||||
.as_ref()
|
||||
.map(web_sys::File::name)
|
||||
.unwrap_or_else(String::new)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for JsFile {
|
||||
type Err = Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self> {
|
||||
if s.is_empty() {
|
||||
Ok(JsFile::default())
|
||||
} else {
|
||||
bail!("Building file from non-empty string")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The fields of the form, with the editable details and the constraints.
|
||||
#[derive(Model, Validate, PartialEq, Clone)]
|
||||
#[derive(Model, Validate, PartialEq, Eq, Clone)]
|
||||
pub struct UserModel {
|
||||
#[validate(email)]
|
||||
email: String,
|
||||
@@ -25,7 +55,7 @@ pub struct UserModel {
|
||||
schema_path = "../schema.graphql",
|
||||
query_path = "queries/update_user.graphql",
|
||||
response_derives = "Debug",
|
||||
variables_derives = "Clone,PartialEq",
|
||||
variables_derives = "Clone,PartialEq,Eq",
|
||||
custom_scalars_module = "crate::infra::graphql"
|
||||
)]
|
||||
pub struct UpdateUser;
|
||||
@@ -34,6 +64,7 @@ pub struct UpdateUser;
|
||||
pub struct UserDetailsForm {
|
||||
common: CommonComponentParts<Self>,
|
||||
form: yew_form::Form<UserModel>,
|
||||
avatar: JsFile,
|
||||
/// True if we just successfully updated the user, to display a success message.
|
||||
just_updated: bool,
|
||||
}
|
||||
@@ -43,24 +74,68 @@ pub enum Msg {
|
||||
Update,
|
||||
/// The "Submit" button was clicked.
|
||||
SubmitClicked,
|
||||
/// A picked file finished loading.
|
||||
FileLoaded(yew::services::reader::FileData),
|
||||
/// We got the response from the server about our update message.
|
||||
UserUpdated(Result<update_user::ResponseData>),
|
||||
}
|
||||
|
||||
#[derive(yew::Properties, Clone, PartialEq)]
|
||||
#[derive(yew::Properties, Clone, PartialEq, Eq)]
|
||||
pub struct Props {
|
||||
/// The current user details.
|
||||
pub user: User,
|
||||
/// Callback to report errors (e.g. server error).
|
||||
pub on_error: Callback<Error>,
|
||||
}
|
||||
|
||||
impl CommonComponent<UserDetailsForm> for UserDetailsForm {
|
||||
fn handle_msg(&mut self, msg: <Self as Component>::Message) -> Result<bool> {
|
||||
match msg {
|
||||
Msg::Update => Ok(true),
|
||||
Msg::Update => {
|
||||
let window = web_sys::window().expect("no global `window` exists");
|
||||
let document = window.document().expect("should have a document on window");
|
||||
let input = document
|
||||
.get_element_by_id("avatarInput")
|
||||
.expect("Form field avatarInput should be present")
|
||||
.dyn_into::<web_sys::HtmlInputElement>()
|
||||
.expect("Should be an HtmlInputElement");
|
||||
ConsoleService::log("Form update");
|
||||
if let Some(files) = input.files() {
|
||||
ConsoleService::log("Got file list");
|
||||
if files.length() > 0 {
|
||||
ConsoleService::log("Got a file");
|
||||
let new_avatar = JsFile {
|
||||
file: files.item(0),
|
||||
contents: None,
|
||||
};
|
||||
if self.avatar.file.as_ref().map(|f| f.name())
|
||||
!= new_avatar.file.as_ref().map(|f| f.name())
|
||||
{
|
||||
if let Some(ref file) = new_avatar.file {
|
||||
self.mut_common().read_file(file.clone(), Msg::FileLoaded)?;
|
||||
}
|
||||
self.avatar = new_avatar;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(true)
|
||||
}
|
||||
Msg::SubmitClicked => self.submit_user_update_form(),
|
||||
Msg::UserUpdated(response) => self.user_update_finished(response),
|
||||
Msg::FileLoaded(data) => {
|
||||
self.common.cancel_task();
|
||||
if let Some(file) = &self.avatar.file {
|
||||
if file.name() == data.name {
|
||||
if !is_valid_jpeg(data.content.as_slice()) {
|
||||
// Clear the selection.
|
||||
self.avatar = JsFile::default();
|
||||
bail!("Chosen image is not a valid JPEG");
|
||||
} else {
|
||||
self.avatar.contents = Some(data.content);
|
||||
return Ok(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,17 +158,14 @@ impl Component for UserDetailsForm {
|
||||
Self {
|
||||
common: CommonComponentParts::<Self>::create(props, link),
|
||||
form: yew_form::Form::new(model),
|
||||
avatar: JsFile::default(),
|
||||
just_updated: false,
|
||||
}
|
||||
}
|
||||
|
||||
fn update(&mut self, msg: Self::Message) -> ShouldRender {
|
||||
self.just_updated = false;
|
||||
CommonComponentParts::<Self>::update_and_report_error(
|
||||
self,
|
||||
msg,
|
||||
self.common.on_error.clone(),
|
||||
)
|
||||
CommonComponentParts::<Self>::update(self, msg)
|
||||
}
|
||||
|
||||
fn change(&mut self, props: Self::Properties) -> ShouldRender {
|
||||
@@ -102,6 +174,9 @@ impl Component for UserDetailsForm {
|
||||
|
||||
fn view(&self) -> Html {
|
||||
type Field = yew_form::Field<UserModel>;
|
||||
|
||||
let avatar_base64 = maybe_to_base64(&self.avatar).unwrap_or_default();
|
||||
let avatar_string = avatar_base64.as_ref().unwrap_or(&self.common.user.avatar);
|
||||
html! {
|
||||
<div class="py-3">
|
||||
<form class="form">
|
||||
@@ -111,7 +186,24 @@ impl Component for UserDetailsForm {
|
||||
{"User ID: "}
|
||||
</label>
|
||||
<div class="col-8">
|
||||
<span id="userId" class="form-constrol-static">{&self.common.user.id}</span>
|
||||
<span id="userId" class="form-constrol-static"><b>{&self.common.user.id}</b></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row mb-3">
|
||||
<div class="col-4 col-form-label">
|
||||
<img
|
||||
id="avatarDisplay"
|
||||
src={format!("data:image/jpeg;base64, {}", avatar_string)}
|
||||
style="max-height:128px;max-width:128px;height:auto;width:auto;"
|
||||
alt="Avatar" />
|
||||
</div>
|
||||
<div class="col-8">
|
||||
<input
|
||||
class="form-control"
|
||||
id="avatarInput"
|
||||
type="file"
|
||||
accept="image/jpeg"
|
||||
oninput=self.common.callback(|_| Msg::Update) />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row mb-3">
|
||||
@@ -195,6 +287,15 @@ impl Component for UserDetailsForm {
|
||||
<span id="creationDate" class="form-constrol-static">{&self.common.user.creation_date.date().naive_local()}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row mb-3">
|
||||
<label for="uuid"
|
||||
class="form-label col-4 col-form-label">
|
||||
{"UUID: "}
|
||||
</label>
|
||||
<div class="col-8">
|
||||
<span id="creationDate" class="form-constrol-static">{&self.common.user.uuid}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row justify-content-center">
|
||||
<button
|
||||
type="submit"
|
||||
@@ -205,6 +306,14 @@ impl Component for UserDetailsForm {
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
{ if let Some(e) = &self.common.error {
|
||||
html! {
|
||||
<div class="alert alert-danger">
|
||||
{e.to_string() }
|
||||
</div>
|
||||
}
|
||||
} else { html! {} }
|
||||
}
|
||||
<div hidden=!self.just_updated>
|
||||
<span>{"User successfully updated!"}</span>
|
||||
</div>
|
||||
@@ -215,9 +324,19 @@ impl Component for UserDetailsForm {
|
||||
|
||||
impl UserDetailsForm {
|
||||
fn submit_user_update_form(&mut self) -> Result<bool> {
|
||||
ConsoleService::log("Submit");
|
||||
if !self.form.validate() {
|
||||
bail!("Invalid inputs");
|
||||
}
|
||||
ConsoleService::log("Valid inputs");
|
||||
if let JsFile {
|
||||
file: Some(_),
|
||||
contents: None,
|
||||
} = &self.avatar
|
||||
{
|
||||
bail!("Image file hasn't finished loading, try again");
|
||||
}
|
||||
ConsoleService::log("File is correctly loaded");
|
||||
let base_user = &self.common.user;
|
||||
let mut user_input = update_user::UpdateUserInput {
|
||||
id: self.common.user.id.clone(),
|
||||
@@ -225,6 +344,7 @@ impl UserDetailsForm {
|
||||
displayName: None,
|
||||
firstName: None,
|
||||
lastName: None,
|
||||
avatar: None,
|
||||
};
|
||||
let default_user_input = user_input.clone();
|
||||
let model = self.form.model();
|
||||
@@ -241,11 +361,14 @@ impl UserDetailsForm {
|
||||
if base_user.last_name != model.last_name {
|
||||
user_input.lastName = Some(model.last_name);
|
||||
}
|
||||
user_input.avatar = maybe_to_base64(&self.avatar)?;
|
||||
// Nothing changed.
|
||||
if user_input == default_user_input {
|
||||
ConsoleService::log("No changes");
|
||||
return Ok(false);
|
||||
}
|
||||
let req = update_user::Variables { user: user_input };
|
||||
ConsoleService::log("Querying");
|
||||
self.common.call_graphql::<UpdateUser, _>(
|
||||
req,
|
||||
Msg::UserUpdated,
|
||||
@@ -260,18 +383,44 @@ impl UserDetailsForm {
|
||||
Err(e) => return Err(e),
|
||||
Ok(_) => {
|
||||
let model = self.form.model();
|
||||
self.common.user = User {
|
||||
id: self.common.user.id.clone(),
|
||||
email: model.email,
|
||||
display_name: model.display_name,
|
||||
first_name: model.first_name,
|
||||
last_name: model.last_name,
|
||||
creation_date: self.common.user.creation_date,
|
||||
groups: self.common.user.groups.clone(),
|
||||
};
|
||||
self.common.user.email = model.email;
|
||||
self.common.user.display_name = model.display_name;
|
||||
self.common.user.first_name = model.first_name;
|
||||
self.common.user.last_name = model.last_name;
|
||||
if let Some(avatar) = maybe_to_base64(&self.avatar)? {
|
||||
self.common.user.avatar = avatar;
|
||||
}
|
||||
self.just_updated = true;
|
||||
}
|
||||
};
|
||||
Ok(true)
|
||||
}
|
||||
}
|
||||
|
||||
fn is_valid_jpeg(bytes: &[u8]) -> bool {
|
||||
image::io::Reader::with_format(std::io::Cursor::new(bytes), image::ImageFormat::Jpeg)
|
||||
.decode()
|
||||
.is_ok()
|
||||
}
|
||||
|
||||
fn maybe_to_base64(file: &JsFile) -> Result<Option<String>> {
|
||||
match file {
|
||||
JsFile {
|
||||
file: None,
|
||||
contents: _,
|
||||
} => Ok(None),
|
||||
JsFile {
|
||||
file: Some(_),
|
||||
contents: None,
|
||||
} => bail!("Image file hasn't finished loading, try again"),
|
||||
JsFile {
|
||||
file: Some(_),
|
||||
contents: Some(data),
|
||||
} => {
|
||||
if !is_valid_jpeg(data.as_slice()) {
|
||||
bail!("Chosen image is not a valid JPEG");
|
||||
}
|
||||
Ok(Some(base64::encode(data)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,11 @@ use anyhow::{Error, Result};
|
||||
use graphql_client::GraphQLQuery;
|
||||
use yew::{
|
||||
prelude::*,
|
||||
services::{fetch::FetchTask, ConsoleService},
|
||||
services::{
|
||||
fetch::FetchTask,
|
||||
reader::{FileData, ReaderService, ReaderTask},
|
||||
ConsoleService,
|
||||
},
|
||||
};
|
||||
use yewtil::NeqAssign;
|
||||
|
||||
@@ -40,13 +44,34 @@ pub trait CommonComponent<C: Component + CommonComponent<C>>: Component {
|
||||
fn mut_common(&mut self) -> &mut CommonComponentParts<C>;
|
||||
}
|
||||
|
||||
enum AnyTask {
|
||||
None,
|
||||
FetchTask(FetchTask),
|
||||
ReaderTask(ReaderTask),
|
||||
}
|
||||
|
||||
impl AnyTask {
|
||||
fn is_some(&self) -> bool {
|
||||
!matches!(self, AnyTask::None)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Option<FetchTask>> for AnyTask {
|
||||
fn from(task: Option<FetchTask>) -> Self {
|
||||
match task {
|
||||
Some(t) => AnyTask::FetchTask(t),
|
||||
None => AnyTask::None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Structure that contains the common parts needed by most components.
|
||||
/// The fields of [`props`] are directly accessible through a `Deref` implementation.
|
||||
pub struct CommonComponentParts<C: CommonComponent<C>> {
|
||||
link: ComponentLink<C>,
|
||||
pub props: <C as Component>::Properties,
|
||||
pub error: Option<Error>,
|
||||
task: Option<FetchTask>,
|
||||
task: AnyTask,
|
||||
}
|
||||
|
||||
impl<C: CommonComponent<C>> CommonComponentParts<C> {
|
||||
@@ -57,7 +82,7 @@ impl<C: CommonComponent<C>> CommonComponentParts<C> {
|
||||
|
||||
/// Cancel any background task.
|
||||
pub fn cancel_task(&mut self) {
|
||||
self.task = None;
|
||||
self.task = AnyTask::None;
|
||||
}
|
||||
|
||||
pub fn create(props: <C as Component>::Properties, link: ComponentLink<C>) -> Self {
|
||||
@@ -65,7 +90,7 @@ impl<C: CommonComponent<C>> CommonComponentParts<C> {
|
||||
link,
|
||||
props,
|
||||
error: None,
|
||||
task: None,
|
||||
task: AnyTask::None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -131,7 +156,7 @@ impl<C: CommonComponent<C>> CommonComponentParts<C> {
|
||||
M: Fn(Req, Callback<Resp>) -> Result<FetchTask>,
|
||||
Cb: FnOnce(Resp) -> <C as Component>::Message + 'static,
|
||||
{
|
||||
self.task = Some(method(req, self.link.callback_once(callback))?);
|
||||
self.task = AnyTask::FetchTask(method(req, self.link.callback_once(callback))?);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -156,7 +181,19 @@ impl<C: CommonComponent<C>> CommonComponentParts<C> {
|
||||
ConsoleService::log(&e.to_string());
|
||||
self.error = Some(e);
|
||||
})
|
||||
.ok();
|
||||
.ok()
|
||||
.into();
|
||||
}
|
||||
|
||||
pub(crate) fn read_file<Cb>(&mut self, file: web_sys::File, callback: Cb) -> Result<()>
|
||||
where
|
||||
Cb: FnOnce(FileData) -> <C as Component>::Message + 'static,
|
||||
{
|
||||
self.task = AnyTask::ReaderTask(ReaderService::read_file(
|
||||
file,
|
||||
self.link.callback_once(callback),
|
||||
)?);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,20 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
for SECRET in LLDAP_JWT_SECRET LLDAP_LDAP_USER_PASS; do
|
||||
FILE_VAR="${SECRET}_FILE"
|
||||
SECRET_FILE="${!FILE_VAR:-}"
|
||||
if [[ -n "$SECRET_FILE" ]]; then
|
||||
if [[ -f "$SECRET_FILE" ]]; then
|
||||
declare "$SECRET=$(cat $SECRET_FILE)"
|
||||
export "$SECRET"
|
||||
echo "[entrypoint] Set $SECRET from $SECRET_FILE"
|
||||
else
|
||||
echo "[entrypoint] Could not read contents of $SECRET_FILE (specified in $FILE_VAR)" >&2
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
CONFIG_FILE=/data/lldap_config.toml
|
||||
|
||||
if [[ ( ! -w "/data" ) ]] || [[ ( ! -d "/data" ) ]]; then
|
||||
@@ -35,4 +21,13 @@ if [[ ! -r "$CONFIG_FILE" ]]; then
|
||||
exit 1;
|
||||
fi
|
||||
|
||||
exec /app/lldap "$@"
|
||||
echo "> Setup permissions.."
|
||||
find /app \! -user "$UID" -exec chown "$UID:$GID" '{}' +
|
||||
find /data \! -user "$UID" -exec chown "$UID:$GID" '{}' +
|
||||
|
||||
|
||||
echo "> Starting lldap.."
|
||||
echo ""
|
||||
exec gosu "$UID:$GID" /app/lldap "$@"
|
||||
|
||||
exec "$@"
|
||||
|
||||
26
example_configs/airsonic-advanced.md
Normal file
26
example_configs/airsonic-advanced.md
Normal file
@@ -0,0 +1,26 @@
|
||||
# Configuration for Airsonic Advanced
|
||||
|
||||
Replace `dc=example,dc=com` with your LLDAP configured domain.
|
||||
|
||||
### LDAP URL
|
||||
```
|
||||
ldap://lldap:3890/ou=people,dc=example,dc=com
|
||||
```
|
||||
### LDAP search filter
|
||||
```
|
||||
(&(uid={0})(memberof=cn=airsonic,ou=groups,dc=example,dc=com))
|
||||
```
|
||||
|
||||
### LDAP manager DN
|
||||
```
|
||||
cn=admin,ou=people,dc=example,dc=com
|
||||
```
|
||||
|
||||
### Password
|
||||
```
|
||||
admin-password
|
||||
```
|
||||
|
||||
Make sure the box `Automatically create users in Airsonic` is checked.
|
||||
|
||||
Restart airsonic-advanced
|
||||
25
example_configs/dokuwiki.md
Normal file
25
example_configs/dokuwiki.md
Normal file
@@ -0,0 +1,25 @@
|
||||
# Configuration for dokuwiki
|
||||
|
||||
LDAP configuration is in ```/dokuwiki/conf/local.protected.php```:
|
||||
|
||||
```
|
||||
<?php
|
||||
$conf['useacl'] = 1; //enable ACL
|
||||
$conf['authtype'] = 'authldap'; //enable this Auth plugin
|
||||
$conf['plugin']['authldap']['server'] = 'ldap://lldap_server:3890'; #IP of your lldap
|
||||
$conf['plugin']['authldap']['usertree'] = 'ou=people,dc=example,dc=com';
|
||||
$conf['plugin']['authldap']['grouptree'] = 'ou=groups, dc=example, dc=com';
|
||||
$conf['plugin']['authldap']['userfilter'] = '(&(uid=%{user})(objectClass=person))';
|
||||
$conf['plugin']['authldap']['groupfilter'] = '(&(objectClass=group)(memberUID=member))';
|
||||
$conf['plugin']['authldap']['attributes'] = array('cn', 'displayname', 'mail', 'givenname', 'objectclass', 'sn', 'uid', 'memberof');
|
||||
$conf['plugin']['authldap']['version'] = 3;
|
||||
$conf['plugin']['authldap']['binddn'] = 'cn=admin,ou=people,dc=example,dc=com';
|
||||
$conf['plugin']['authldap']['bindpw'] = 'ENTER_YOUR_LLDAP_PASSWORD';
|
||||
```
|
||||
|
||||
DokuWiki by default, ships with an LDAP Authentication Plugin called ```authLDAP``` that allows authentication against an LDAP directory.
|
||||
All you need to do is to activate the plugin. This can be done on the DokuWiki Extensions Manager.
|
||||
|
||||
Once the LDAP settings are defined, proceed to define the default authentication method.
|
||||
Navigate to Table of Contents > DokuWiki > Authentication.
|
||||
On the Authentication backend, select ```authldap``` and save the changes.
|
||||
@@ -20,7 +20,7 @@ ssl_skip_verify = false
|
||||
# client_key = "/path/to/client.key"
|
||||
|
||||
# Search user bind dn
|
||||
bind_dn = "cn=<your grafana user>,ou=people,dc=example,dc=org"
|
||||
bind_dn = "uid=<your grafana user>,ou=people,dc=example,dc=org"
|
||||
# Search user bind password
|
||||
# If the password contains # or ; you have to wrap it with triple quotes. Ex """#password;"""
|
||||
bind_password = "<grafana user password>"
|
||||
@@ -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,c=example,dc=org"
|
||||
# group_dn = "uid=lldap_admin,ou=groups,dc=example,dc=org"
|
||||
# org_role = "Admin"
|
||||
# grafana_admin = true
|
||||
|
||||
16
example_configs/hedgedoc.md
Normal file
16
example_configs/hedgedoc.md
Normal file
@@ -0,0 +1,16 @@
|
||||
# Configuration for hedgedoc
|
||||
|
||||
[Hedgedoc](https://hedgedoc.org/) is a platform to write and share markdown.
|
||||
|
||||
### Using docker variables
|
||||
|
||||
Any member of the group ```hedgedoc``` can log into hedgedoc.
|
||||
```
|
||||
- CMD_LDAP_URL=ldap://lldap:3890
|
||||
- CMD_LDAP_BINDDN=uid=admin,ou=people,dc=example,dc=com
|
||||
- CMD_LDAP_BINDCREDENTIALS=insert_your_password
|
||||
- CMD_LDAP_SEARCHBASE=ou=people,dc=example,dc=com
|
||||
- CMD_LDAP_SEARCHFILTER=(&(memberOf=cn=hedgedoc,ou=groups,dc=example,dc=com)(uid={{username}}))
|
||||
- CMD_LDAP_USERIDFIELD=uid
|
||||
```
|
||||
Replace `dc=example,dc=com` with your LLDAP configured domain for all occurances
|
||||
BIN
example_configs/images/nextcloud_groups.png
Normal file
BIN
example_configs/images/nextcloud_groups.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 89 KiB |
BIN
example_configs/images/nextcloud_ldap_srv.png
Normal file
BIN
example_configs/images/nextcloud_ldap_srv.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 82 KiB |
BIN
example_configs/images/nextcloud_login_attributes.png
Normal file
BIN
example_configs/images/nextcloud_login_attributes.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 115 KiB |
BIN
example_configs/images/nextcloud_loginfilter.png
Normal file
BIN
example_configs/images/nextcloud_loginfilter.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 168 KiB |
BIN
example_configs/images/nextcloud_sharing_options.png
Normal file
BIN
example_configs/images/nextcloud_sharing_options.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 110 KiB |
22
example_configs/lldap.service
Normal file
22
example_configs/lldap.service
Normal file
@@ -0,0 +1,22 @@
|
||||
[Unit]
|
||||
Description=Nitnelave LLDAP
|
||||
Documentation=https://github.com/nitnelave/lldap
|
||||
|
||||
# Only sqlite
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
# The user/group LLDAP is run under. The working directory (see below) should allow write and read access to this user/group.
|
||||
User=root
|
||||
Group=root
|
||||
|
||||
# The location of the compiled binary
|
||||
ExecStart=/opt/nitnelave/lldap \
|
||||
run
|
||||
|
||||
# Only allow writes to the following directory and set it to the working directory (user and password data are stored here).
|
||||
WorkingDirectory=/opt/nitnelave/
|
||||
ReadWriteDirectories=/opt/nitnelave/
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
111
example_configs/nextcloud.md
Normal file
111
example_configs/nextcloud.md
Normal file
@@ -0,0 +1,111 @@
|
||||
# Nextcloud LLDAP example config
|
||||
|
||||
## lldap users & groups
|
||||
|
||||
This example is using following users & groups in lldap :
|
||||
|
||||
* A technical user (ex: `ro_admin`), member of `lldap_strict_readonly` or `lldap_password_manager`
|
||||
* Several accounts, members of `users` group will be authorized to log in Nextcloud (eg neither `admin` nor `ro_admin`)
|
||||
* Some "application" groups, let's say `friends` and `family`: users in Nextcloud will be able to share files and view people in dynamic lists only to members of their own group(s)
|
||||
|
||||
## Nextcloud config : the cli way
|
||||
|
||||
TL;DR let's script it. The "user_ldap" application is shipped with default Nextcloud installation (at least using Docker official stable images), you just have to install & enable it :
|
||||
|
||||
```sh
|
||||
occ app:install user_ldap
|
||||
occ app:enable user_ldap
|
||||
occ ldap:create-empty-config
|
||||
# EDIT: domain
|
||||
occ ldap:set-config s01 ldapHost "ldap://lldap.example.net."
|
||||
occ ldap:set-config s01 ldapPort 3890
|
||||
# EDIT: admin user
|
||||
occ ldap:set-config s01 ldapAgentName "uid=ro_admin,ou=people,dc=example,dc=com"
|
||||
# EDIT: password
|
||||
occ ldap:set-config s01 ldapAgentPassword "password"
|
||||
# EDIT: Base DN
|
||||
occ ldap:set-config s01 ldapBase "dc=example,dc=com"
|
||||
occ ldap:set-config s01 ldapBaseUsers "dc=example,dc=com"
|
||||
occ ldap:set-config s01 ldapBaseGroups "dc=example,dc=com"
|
||||
occ ldap:set-config s01 ldapConfigurationActive 1
|
||||
occ ldap:set-config s01 ldapLoginFilter "(&(objectclass=person)(uid=%uid))"
|
||||
# EDIT: users group, contains the users who can login to Nextcloud
|
||||
occ ldap:set-config s01 ldapUserFilter "(&(objectclass=person)(memberOf=cn=users,ou=groups,dc=example,dc=com))"
|
||||
occ ldap:set-config s01 ldapUserFilterMode 0
|
||||
occ ldap:set-config s01 ldapUserFilterObjectclass person
|
||||
occ ldap:set-config s01 turnOnPasswordChange 0
|
||||
occ ldap:set-config s01 ldapCacheTTL 600
|
||||
occ ldap:set-config s01 ldapExperiencedAdmin 0
|
||||
occ ldap:set-config s01 ldapGidNumber gidNumber
|
||||
# EDIT: list of application groups
|
||||
occ ldap:set-config s01 ldapGroupFilter "(&(objectclass=groupOfUniqueNames)(|(cn=friends)(cn=family)))"
|
||||
# EDIT: list of application groups
|
||||
occ ldap:set-config s01 ldapGroupFilterGroups "friends;family"
|
||||
occ ldap:set-config s01 ldapGroupFilterMode 0
|
||||
occ ldap:set-config s01 ldapGroupDisplayName cn
|
||||
occ ldap:set-config s01 ldapGroupFilterObjectclass groupOfUniqueNames
|
||||
occ ldap:set-config s01 ldapGroupMemberAssocAttr uniqueMember
|
||||
occ ldap:set-config s01 ldapLoginFilterEmail 0
|
||||
occ ldap:set-config s01 ldapLoginFilterUsername 1
|
||||
occ ldap:set-config s01 ldapMatchingRuleInChainState unknown
|
||||
occ ldap:set-config s01 ldapNestedGroups 0
|
||||
occ ldap:set-config s01 ldapPagingSize 500
|
||||
occ ldap:set-config s01 ldapTLS 0
|
||||
occ ldap:set-config s01 ldapUserAvatarRule default
|
||||
occ ldap:set-config s01 ldapUserDisplayName displayname
|
||||
occ ldap:set-config s01 ldapUserFilterMode 1
|
||||
occ ldap:set-config s01 ldapUuidGroupAttribute auto
|
||||
occ ldap:set-config s01 ldapUuidUserAttribute auto
|
||||
```
|
||||
With small amount of luck, you should be able to log in your nextcloud instance with LLDAP accounts in the `users` group.
|
||||
|
||||
## Nextcloud config : the GUI way
|
||||
|
||||
1. enable LDAP application (installed but not enabled by default)
|
||||
2. setup your ldap server in Settings > Administration > LDAP / AD integration
|
||||
3. setup Group limitations
|
||||
|
||||
### LDAP server config
|
||||
|
||||
Fill the LLDAP domain and port, DN + password of your technical account and base DN (as usual : change `example.com` by your own domain) :
|
||||
|
||||

|
||||
|
||||
### Users tab
|
||||
|
||||
Select `person` as object class and then choose `Edit LDAP Query` : the `only from these groups` option is not functional.
|
||||
We want only users from the `users` group to be allowed to log in Nextcloud :
|
||||
```
|
||||
(&(objectclass=person)(memberOf=cn=users,ou=groups,dc=example,dc=com))
|
||||
```
|
||||
|
||||

|
||||
|
||||
You can check with `Verify settings and count users` that your filter is working properly (here your accounts `admin` and `ro_admin` will not be counted as users).
|
||||
|
||||
### Login attributes
|
||||
Select `Edit LDAP Query` and enter :
|
||||
```
|
||||
(&(objectclass=person)(uid=%uid))
|
||||
```
|
||||
|
||||

|
||||
|
||||
Enter a valid username in lldap and check if your filter is working.
|
||||
|
||||
### Groups
|
||||
|
||||
You can use the menus for this part : select `groupOfUniqueNames` in the first menu and check every group you want members to be allowed to view their group member / share files with.
|
||||
|
||||

|
||||
|
||||
The resulting LDAP filter could be simplified removing the first 'OR' condition (I think).
|
||||
|
||||
## Sharing restrictions
|
||||
|
||||
Go to Settings > Administration > Sharing and check following boxes :
|
||||
|
||||
* "Allow username autocompletion to users within the same groups"
|
||||
* "Restrict users to only share with users in their groups"
|
||||
|
||||

|
||||
21
example_configs/xbackbone_config.php
Normal file
21
example_configs/xbackbone_config.php
Normal file
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
return array (
|
||||
'ldap' =>
|
||||
array (
|
||||
'enabled' => true,
|
||||
'schema' => 'ldap',
|
||||
// If using same docker network, use 'lldap', otherwise put ip/hostname
|
||||
'host' => 'lldap',
|
||||
// Normal ldap port is 389, standard in LLDAP is 3890
|
||||
'port' => 3890,
|
||||
'base_domain' => 'ou=people,dc=example,dc=com',
|
||||
// ???? is replaced with user-provided username, authenticates users in an lldap group called "xbackbone"
|
||||
// Remove the "(memberof=...)" if you want to allow all users.
|
||||
'search_filter' => '(&(uid=????)(objectClass=person)(memberof=cn=xbackbone,ou=groups,dc=example,dc=com))',
|
||||
// the attribute to use as username
|
||||
'rdn_attribute' => 'uid',
|
||||
// LDAP admin/service account info below
|
||||
'service_account_dn' => 'cn=admin,ou=people,dc=example,dc=com',
|
||||
'service_account_password' => 'REPLACE_ME',
|
||||
),
|
||||
);
|
||||
@@ -45,6 +45,11 @@
|
||||
## For the administration interface, this is the username.
|
||||
#ldap_user_dn = "admin"
|
||||
|
||||
## Admin email.
|
||||
## Email for the admin account. It is only used when initially creating
|
||||
## the admin user, and can safely be omitted.
|
||||
#ldap_user_email = "admin@example.com"
|
||||
|
||||
## Admin password.
|
||||
## Password for the admin account, both for the LDAP bind and for the
|
||||
## administration interface. It is only used when initially creating
|
||||
@@ -96,8 +101,8 @@ key_file = "/data/private_key"
|
||||
#server="smtp.gmail.com"
|
||||
## The SMTP port.
|
||||
#port=587
|
||||
## Whether to connect with TLS.
|
||||
#tls_required=true
|
||||
## How the connection is encrypted, either "TLS" or "STARTTLS".
|
||||
#smtp_encryption = "TLS"
|
||||
## The SMTP user, usually your email address.
|
||||
#user="sender@gmail.com"
|
||||
## The SMTP password.
|
||||
|
||||
@@ -1,23 +1,33 @@
|
||||
[package]
|
||||
name = "migration-tool"
|
||||
version = "0.3.0-alpha.1"
|
||||
version = "0.4.1"
|
||||
edition = "2021"
|
||||
authors = ["Valentin Tolmer <valentin@tolmer.fr>"]
|
||||
|
||||
[dependencies]
|
||||
anyhow = "*"
|
||||
graphql_client = "0.10"
|
||||
ldap3 = "*"
|
||||
base64 = "0.13"
|
||||
rand = "0.8"
|
||||
requestty = "*"
|
||||
requestty = "0.4.1"
|
||||
serde = "1"
|
||||
serde_json = "1"
|
||||
smallvec = "*"
|
||||
|
||||
[dependencies.lldap_auth]
|
||||
path = "../auth"
|
||||
features = [ "opaque_client" ]
|
||||
features = ["opaque_client"]
|
||||
|
||||
[dependencies.graphql_client]
|
||||
features = ["graphql_query_derive", "reqwest-rustls"]
|
||||
default-features = false
|
||||
version = "0.11"
|
||||
|
||||
[dependencies.reqwest]
|
||||
version = "*"
|
||||
features = [ "json", "blocking" ]
|
||||
default-features = false
|
||||
features = ["json", "blocking", "rustls-tls"]
|
||||
|
||||
[dependencies.ldap3]
|
||||
version = "*"
|
||||
default-features = false
|
||||
features = ["sync", "tls-rustls"]
|
||||
|
||||
@@ -131,7 +131,7 @@ fn bind_ldap(
|
||||
};
|
||||
if let Err(e) = ldap_connection
|
||||
.simple_bind(&binddn, &password)
|
||||
.and_then(|r| r.success())
|
||||
.and_then(ldap3::LdapResult::success)
|
||||
{
|
||||
println!("Error connecting as '{}': {}", binddn, e);
|
||||
bind_ldap(ldap_connection, Some(binddn))
|
||||
@@ -150,12 +150,11 @@ impl TryFrom<ResultEntry> for User {
|
||||
.attrs
|
||||
.get(attr)
|
||||
.ok_or_else(|| anyhow!("Missing {} for user", attr))
|
||||
.and_then(|u| {
|
||||
if u.len() > 1 {
|
||||
Err(anyhow!("Too many {}s", attr))
|
||||
} else {
|
||||
Ok(u.first().unwrap().to_owned())
|
||||
}
|
||||
.and_then(|u| -> Result<String> {
|
||||
u.iter()
|
||||
.next()
|
||||
.map(String::to_owned)
|
||||
.ok_or_else(|| anyhow!("Too many {}s", attr))
|
||||
})
|
||||
};
|
||||
let id = get_required_attribute("uid")
|
||||
@@ -170,13 +169,8 @@ impl TryFrom<ResultEntry> for User {
|
||||
.attrs
|
||||
.get(attr)
|
||||
.and_then(|v| v.first().map(|s| s.as_str()))
|
||||
.and_then(|s| {
|
||||
if s.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(s.to_owned())
|
||||
}
|
||||
})
|
||||
.filter(|s| !s.is_empty())
|
||||
.map(str::to_owned)
|
||||
};
|
||||
let last_name = get_optional_attribute("sn").or_else(|| get_optional_attribute("surname"));
|
||||
let display_name = get_optional_attribute("cn")
|
||||
@@ -184,14 +178,23 @@ impl TryFrom<ResultEntry> for User {
|
||||
.or_else(|| get_optional_attribute("name"))
|
||||
.or_else(|| get_optional_attribute("displayName"));
|
||||
let first_name = get_optional_attribute("givenName");
|
||||
let avatar = entry
|
||||
.attrs
|
||||
.get("jpegPhoto")
|
||||
.map(|v| v.iter().map(|s| s.as_bytes().to_vec()).collect::<Vec<_>>())
|
||||
.or_else(|| entry.bin_attrs.get("jpegPhoto").map(Clone::clone))
|
||||
.and_then(|v| v.into_iter().next().filter(|s| !s.is_empty()));
|
||||
let password =
|
||||
get_optional_attribute("userPassword").or_else(|| get_optional_attribute("password"));
|
||||
Ok(User::new(
|
||||
id,
|
||||
email,
|
||||
display_name,
|
||||
first_name,
|
||||
last_name,
|
||||
crate::lldap::CreateUserInput {
|
||||
id,
|
||||
email,
|
||||
display_name,
|
||||
first_name,
|
||||
last_name,
|
||||
avatar: avatar.map(base64::encode),
|
||||
},
|
||||
password,
|
||||
entry.dn,
|
||||
))
|
||||
|
||||
@@ -30,7 +30,7 @@ impl GraphQLClient {
|
||||
where
|
||||
QueryType: GraphQLQuery + 'static,
|
||||
{
|
||||
let unwrap_graphql_response = |graphql_client::Response { data, errors }| {
|
||||
let unwrap_graphql_response = |graphql_client::Response { data, errors, .. }| {
|
||||
data.ok_or_else(|| {
|
||||
anyhow!(
|
||||
"Errors: [{}]",
|
||||
@@ -69,24 +69,13 @@ pub struct User {
|
||||
|
||||
impl User {
|
||||
// https://github.com/graphql-rust/graphql-client/issues/386
|
||||
#[allow(non_snake_case)]
|
||||
pub fn new(
|
||||
id: String,
|
||||
email: String,
|
||||
displayName: Option<String>,
|
||||
firstName: Option<String>,
|
||||
lastName: Option<String>,
|
||||
user_input: create_user::CreateUserInput,
|
||||
password: Option<String>,
|
||||
dn: String,
|
||||
) -> User {
|
||||
User {
|
||||
user_input: create_user::CreateUserInput {
|
||||
id,
|
||||
email,
|
||||
displayName,
|
||||
firstName,
|
||||
lastName,
|
||||
},
|
||||
user_input,
|
||||
password,
|
||||
dn,
|
||||
}
|
||||
@@ -103,6 +92,8 @@ impl User {
|
||||
)]
|
||||
struct CreateUser;
|
||||
|
||||
pub type CreateUserInput = create_user::CreateUserInput;
|
||||
|
||||
#[derive(GraphQLQuery)]
|
||||
#[graphql(
|
||||
schema_path = "../schema.graphql",
|
||||
@@ -175,7 +166,9 @@ fn try_login(
|
||||
response.status().as_str()
|
||||
);
|
||||
}
|
||||
Ok(response.text()?)
|
||||
let json = serde_json::from_str::<lldap_auth::login::ServerLoginResponse>(&response.text()?)
|
||||
.context("Could not parse response")?;
|
||||
Ok(json.token)
|
||||
}
|
||||
|
||||
pub fn get_lldap_user_and_password(
|
||||
|
||||
@@ -17,6 +17,8 @@ type Mutation {
|
||||
type Group {
|
||||
id: Int!
|
||||
displayName: String!
|
||||
creationDate: DateTimeUtc!
|
||||
uuid: String!
|
||||
"The groups to which this user belongs."
|
||||
users: [User!]!
|
||||
}
|
||||
@@ -58,6 +60,7 @@ input CreateUserInput {
|
||||
displayName: String
|
||||
firstName: String
|
||||
lastName: String
|
||||
avatar: String
|
||||
}
|
||||
|
||||
type User {
|
||||
@@ -66,7 +69,9 @@ type User {
|
||||
displayName: String!
|
||||
firstName: String!
|
||||
lastName: String!
|
||||
avatar: String!
|
||||
creationDate: DateTimeUtc!
|
||||
uuid: String!
|
||||
"The groups to which this user belongs."
|
||||
groups: [Group!]!
|
||||
}
|
||||
@@ -82,6 +87,7 @@ input UpdateUserInput {
|
||||
displayName: String
|
||||
firstName: String
|
||||
lastName: String
|
||||
avatar: String
|
||||
}
|
||||
|
||||
schema {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
authors = ["Valentin Tolmer <valentin@tolmer.fr>"]
|
||||
edition = "2021"
|
||||
name = "lldap"
|
||||
version = "0.4.0"
|
||||
version = "0.4.1"
|
||||
|
||||
[dependencies]
|
||||
actix = "0.12"
|
||||
@@ -11,7 +11,6 @@ actix-http = "=3.0.0-beta.9"
|
||||
actix-rt = "2.2.0"
|
||||
actix-server = "=2.0.0-beta.5"
|
||||
actix-service = "2.0.0"
|
||||
actix-tls = "=3.0.0-beta.5"
|
||||
actix-web = "=4.0.0-beta.8"
|
||||
actix-web-httpauth = "0.6.0-beta.2"
|
||||
anyhow = "*"
|
||||
@@ -20,31 +19,34 @@ base64 = "0.13"
|
||||
bincode = "1.3"
|
||||
cron = "*"
|
||||
derive_builder = "0.10.2"
|
||||
figment_file_provider_adapter = "0.1"
|
||||
futures = "*"
|
||||
futures-util = "*"
|
||||
hmac = "0.10"
|
||||
http = "*"
|
||||
itertools = "0.10.1"
|
||||
juniper = "0.15.6"
|
||||
juniper = "0.15.10"
|
||||
juniper_actix = "0.4.0"
|
||||
jwt = "0.13"
|
||||
ldap3_server = "=0.1.11"
|
||||
ldap3_proto = "*"
|
||||
log = "*"
|
||||
native-tls = "0.2.10"
|
||||
orion = "0.16"
|
||||
rustls = "0.20"
|
||||
serde = "*"
|
||||
serde_json = "1"
|
||||
sha2 = "0.9"
|
||||
sqlx-core = "0.5.11"
|
||||
thiserror = "*"
|
||||
time = "0.2"
|
||||
tokio-native-tls = "0.3"
|
||||
tokio-rustls = "0.23"
|
||||
tokio-stream = "*"
|
||||
tokio-util = "0.6.3"
|
||||
tokio-util = "0.7.3"
|
||||
tracing = "*"
|
||||
tracing-actix-web = "0.4.0-beta.7"
|
||||
tracing-attributes = "^0.1.21"
|
||||
tracing-log = "*"
|
||||
rustls-pemfile = "1.0.0"
|
||||
serde_bytes = "0.11.7"
|
||||
|
||||
[dependencies.chrono]
|
||||
features = ["serde"]
|
||||
@@ -63,7 +65,8 @@ version = "0.3"
|
||||
features = ["env-filter", "tracing-log"]
|
||||
|
||||
[dependencies.lettre]
|
||||
features = ["builder", "serde", "smtp-transport", "tokio1-native-tls", "tokio1"]
|
||||
features = ["builder", "serde", "smtp-transport", "tokio1-rustls-tls"]
|
||||
default-features = false
|
||||
version = "0.10.0-rc.3"
|
||||
|
||||
[dependencies.sqlx]
|
||||
@@ -74,7 +77,7 @@ features = [
|
||||
"macros",
|
||||
"mysql",
|
||||
"postgres",
|
||||
"runtime-actix-native-tls",
|
||||
"runtime-actix-rustls",
|
||||
"sqlite",
|
||||
]
|
||||
|
||||
@@ -92,10 +95,6 @@ features = ["with-chrono", "sqlx-sqlite", "sqlx-any"]
|
||||
[dependencies.opaque-ke]
|
||||
version = "0.6"
|
||||
|
||||
[dependencies.openssl-sys]
|
||||
features = ["vendored"]
|
||||
version = "*"
|
||||
|
||||
[dependencies.rand]
|
||||
features = ["small_rng", "getrandom"]
|
||||
version = "0.8"
|
||||
@@ -106,7 +105,7 @@ version = "*"
|
||||
|
||||
[dependencies.tokio]
|
||||
features = ["full"]
|
||||
version = "1.13.1"
|
||||
version = "1.17"
|
||||
|
||||
[dependencies.uuid]
|
||||
features = ["v3"]
|
||||
@@ -116,5 +115,14 @@ version = "*"
|
||||
features = ["smallvec", "chrono", "tokio"]
|
||||
version = "^0.1.4"
|
||||
|
||||
[dependencies.actix-tls]
|
||||
features = ["default", "rustls"]
|
||||
version = "=3.0.0-beta.5"
|
||||
|
||||
[dependencies.image]
|
||||
features = ["jpeg"]
|
||||
default-features = false
|
||||
version = "0.24"
|
||||
|
||||
[dev-dependencies]
|
||||
mockall = "0.9.1"
|
||||
|
||||
@@ -47,7 +47,7 @@ impl std::string::ToString for Uuid {
|
||||
#[macro_export]
|
||||
macro_rules! uuid {
|
||||
($s:literal) => {
|
||||
crate::domain::handler::Uuid::try_from($s).unwrap()
|
||||
$crate::domain::handler::Uuid::try_from($s).unwrap()
|
||||
};
|
||||
}
|
||||
|
||||
@@ -82,6 +82,74 @@ impl From<String> for UserId {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Clone, Debug, Default, Serialize, Deserialize, sqlx::Type)]
|
||||
#[sqlx(transparent)]
|
||||
pub struct JpegPhoto(#[serde(with = "serde_bytes")] Vec<u8>);
|
||||
|
||||
impl From<JpegPhoto> for sea_query::Value {
|
||||
fn from(photo: JpegPhoto) -> Self {
|
||||
photo.0.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&JpegPhoto> for sea_query::Value {
|
||||
fn from(photo: &JpegPhoto) -> Self {
|
||||
photo.0.as_slice().into()
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Vec<u8>> for JpegPhoto {
|
||||
type Error = anyhow::Error;
|
||||
fn try_from(bytes: Vec<u8>) -> anyhow::Result<Self> {
|
||||
// Confirm that it's a valid Jpeg, then store only the bytes.
|
||||
image::io::Reader::with_format(
|
||||
std::io::Cursor::new(bytes.as_slice()),
|
||||
image::ImageFormat::Jpeg,
|
||||
)
|
||||
.decode()?;
|
||||
Ok(JpegPhoto(bytes))
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<String> for JpegPhoto {
|
||||
type Error = anyhow::Error;
|
||||
fn try_from(string: String) -> anyhow::Result<Self> {
|
||||
// The String format is in base64.
|
||||
Self::try_from(base64::decode(string.as_str())?)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&JpegPhoto> for String {
|
||||
fn from(val: &JpegPhoto) -> Self {
|
||||
base64::encode(&val.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl JpegPhoto {
|
||||
pub fn into_bytes(self) -> Vec<u8> {
|
||||
self.0
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn for_tests() -> Self {
|
||||
use image::{ImageOutputFormat, Rgb, RgbImage};
|
||||
let img = RgbImage::from_fn(32, 32, |x, y| {
|
||||
if (x + y) % 2 == 0 {
|
||||
Rgb([0, 0, 0])
|
||||
} else {
|
||||
Rgb([255, 255, 255])
|
||||
}
|
||||
});
|
||||
let mut bytes: Vec<u8> = Vec::new();
|
||||
img.write_to(
|
||||
&mut std::io::Cursor::new(&mut bytes),
|
||||
ImageOutputFormat::Jpeg(0),
|
||||
)
|
||||
.unwrap();
|
||||
Self(bytes)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Debug, Clone, Serialize, Deserialize, sqlx::FromRow)]
|
||||
pub struct User {
|
||||
pub user_id: UserId,
|
||||
@@ -89,7 +157,7 @@ pub struct User {
|
||||
pub display_name: String,
|
||||
pub first_name: String,
|
||||
pub last_name: String,
|
||||
// pub avatar: ?,
|
||||
pub avatar: JpegPhoto,
|
||||
pub creation_date: chrono::DateTime<chrono::Utc>,
|
||||
pub uuid: Uuid,
|
||||
}
|
||||
@@ -105,6 +173,7 @@ impl Default for User {
|
||||
display_name: String::new(),
|
||||
first_name: String::new(),
|
||||
last_name: String::new(),
|
||||
avatar: JpegPhoto::default(),
|
||||
creation_date: epoch,
|
||||
uuid: Uuid::from_name_and_date("", &epoch),
|
||||
}
|
||||
@@ -159,6 +228,7 @@ pub struct CreateUserRequest {
|
||||
pub display_name: Option<String>,
|
||||
pub first_name: Option<String>,
|
||||
pub last_name: Option<String>,
|
||||
pub avatar: Option<JpegPhoto>,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Debug, Serialize, Deserialize, Clone, Default)]
|
||||
@@ -169,6 +239,7 @@ pub struct UpdateUserRequest {
|
||||
pub display_name: Option<String>,
|
||||
pub first_name: Option<String>,
|
||||
pub last_name: Option<String>,
|
||||
pub avatar: Option<JpegPhoto>,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Debug, Serialize, Deserialize, Clone)]
|
||||
@@ -194,7 +265,7 @@ pub struct GroupDetails {
|
||||
pub uuid: Uuid,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct UserAndGroups {
|
||||
pub user: User,
|
||||
pub groups: Option<Vec<GroupDetails>>,
|
||||
@@ -263,4 +334,11 @@ mod tests {
|
||||
Uuid::from_name_and_date(user_id, &date2)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_jpeg_try_from_bytes() {
|
||||
let base64_raw = "/9j/4AAQSkZJRgABAQEASABIAAD/2wBDAP//////////////////////////////////////////////////////////////////////////////////////2wBDAf//////////////////////////////////////////////////////////////////////////////////////wAARCADqATkDASIAAhEBAxEB/8QAFwABAQEBAAAAAAAAAAAAAAAAAAECA//EACQQAQEBAAIBBAMBAQEBAAAAAAABESExQQISUXFhgZGxocHw/8QAFQEBAQAAAAAAAAAAAAAAAAAAAAH/xAAWEQEBAQAAAAAAAAAAAAAAAAAAEQH/2gAMAwEAAhEDEQA/AMriLyCKgg1gQwCgs4FTMOdutepjQak+FzMSVqgxZdRdPPIIvH5WzzGdBriphtTeAXg2ZjKA1pqKDUGZca3foBek8gFv8Ie3fKdA1qb8s7hoL6eLVt51FsAnql3Ut1M7AWbflLMDkEMX/F6/YjK/pADFQAUNA6alYagKk72m/j9p4Bq2fDDSYKLNXPNLoHE/NT6RYC31cJxZ3yWVM+aBYi/S2ZgiAsnYJx5D21vPmqrm3PTfpQQwyAC8JZvSKDni41ZrMuUVVl+Uz9w9v/1QWrZsZ5nFPHYH+JZyureQSF5M+fJ0CAfwRAVRBQA1DAWVUayoJUWoDpsxntPsueBV4+VxhdyAtv8AjOLGpIDMLbeGvbF4iozJfr/WukAVABAXAQXEAAASzVAZdO2WNordm+emFl7XcQSNZiFtv0C9w90nhJf4mA1u+GcJFwIyAqL/AOovwgGNfSRqdIrNa29M0gKCAojU9PAMjWXpckEJFNFEAAXEUBABYz6rZ0ureQc9vyt9XxDF2QAXtABcQAs0AZywkvluJbyipifas52DcyxjlZweAO0xri/hc+wZOEKIu6nSyeToVZyWXwvCg53gW81QQ7aTNAn5dGZJPs1UXURQAUEMCXQLZE93PRZ5hPTgNMrbIzKCm52LZwCs+2M8w2g3sjPuZAXb4IsMAUACzVUGM4/K+md6vEXUUyM5PDR0IxYe6ramih0VNBrS4xoqN8Q1BFQk3yqyAsioioAAKgDSJL4/jQIn5igLrPqtOuf6oOaxbMoAltUAhhIoJiiggrPu+AaOIxtAX3JbaAIaLwi4t9X4T3fg2AFtqcrUUarP20zUDAmqoE0WRBZPNVUVEAAAAVAC8kvih2DSKxOdBqs7Z0l0gI0mKAC4AuHE7ZtBriM+744QAAAAABAFsveIttBICyaikvy1+r/Cen5rWQHIBQa4rIDRqSl5qDWqziqgAAAATA7BpGdqXb2C2+J/UgAtRQBSQtkBWb6vhLbQAAAAAEBRAAAAAUbm+GZNdPxAP+ql2Tjwx7/wIgZ8iKvBk+CJoCXii9gaqZ/qqihAAAEVABGkBFUwBftNkZ3QW34QAAABFAQAVAAAAAARVkl8gs/43sk1jL45LvHArepk+E9XTG35oLqsmIKmLAEygKg0y1AFQBUXwgAAAoBC34S3UAAABAVAAAAAABAUQAVABdRQa1PcYyit2z58M8C4ouM2NXpOEGeWtNZUatiAIoAKIoCoAoG4C9MW6dgIoAIAAAAAAACKWAgL0CAAAALiANCKioNLgM1CrLihmTafkt1EF3SZ5ZVUW4mnIKvAi5fhEURVDWVQBRAAAAAAAAQFRVyAyulgAqCKlF8IqLsEgC9mGoC+IusqCrv5ZEUVOk1RuJfwSLOOkGFi4XPCoYYrNiKauosBGi9ICstM1UAAAAAAFQ0VcTBAXUGgIqGoKhKAzRRUQUAwxoSrGRpkQA/qiosOL9oJptMRRVZa0VUqSiChE6BqMgCwqKqIogAIAqKCKgKoogg0lBFuIKgAAAKNRlf2gqsftsEtZWoAAqAACKoMqAAeSoqp39kL2AqLOlE8rEBFQARYALhigrNC9gGmooLp4TweEQFFBFAECgIoAu0ifIAqAAA//9k=";
|
||||
let base64_jpeg = base64::decode(base64_raw).unwrap();
|
||||
JpegPhoto::try_from(base64_jpeg).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ use super::{error::*, handler::*, sql_tables::*};
|
||||
use crate::infra::configuration::Configuration;
|
||||
use async_trait::async_trait;
|
||||
use futures_util::StreamExt;
|
||||
use sea_query::{Alias, Cond, Expr, Iden, Order, Query, SimpleExpr};
|
||||
use sea_query::{Alias, Cond, Expr, Iden, Order, Query};
|
||||
use sea_query_binder::SqlxBinder;
|
||||
use sqlx::{query_as_with, query_with, FromRow, Row};
|
||||
use std::collections::HashSet;
|
||||
@@ -23,90 +23,89 @@ impl SqlBackendHandler {
|
||||
struct RequiresGroup(bool);
|
||||
|
||||
// Returns the condition for the SQL query, and whether it requires joining with the groups table.
|
||||
fn get_user_filter_expr(filter: UserRequestFilter) -> (RequiresGroup, SimpleExpr) {
|
||||
fn get_user_filter_expr(filter: UserRequestFilter) -> (RequiresGroup, Cond) {
|
||||
use sea_query::IntoCondition;
|
||||
use UserRequestFilter::*;
|
||||
fn get_repeated_filter(
|
||||
fs: Vec<UserRequestFilter>,
|
||||
field: &dyn Fn(SimpleExpr, SimpleExpr) -> SimpleExpr,
|
||||
) -> (RequiresGroup, SimpleExpr) {
|
||||
fn get_repeated_filter(fs: Vec<UserRequestFilter>, condition: Cond) -> (RequiresGroup, Cond) {
|
||||
let mut requires_group = false;
|
||||
let mut it = fs.into_iter();
|
||||
let first_expr = match it.next() {
|
||||
None => return (RequiresGroup(false), Expr::value(true)),
|
||||
Some(f) => {
|
||||
let (group, filter) = get_user_filter_expr(f);
|
||||
requires_group |= group.0;
|
||||
filter
|
||||
}
|
||||
};
|
||||
let filter = it.fold(first_expr, |e, f| {
|
||||
let filter = fs.into_iter().fold(condition, |c, f| {
|
||||
let (group, filters) = get_user_filter_expr(f);
|
||||
requires_group |= group.0;
|
||||
field(e, filters)
|
||||
c.add(filters)
|
||||
});
|
||||
(RequiresGroup(requires_group), filter)
|
||||
}
|
||||
match filter {
|
||||
And(fs) => get_repeated_filter(fs, &SimpleExpr::and),
|
||||
Or(fs) => get_repeated_filter(fs, &SimpleExpr::or),
|
||||
And(fs) => get_repeated_filter(fs, Cond::all()),
|
||||
Or(fs) => get_repeated_filter(fs, Cond::any()),
|
||||
Not(f) => {
|
||||
let (requires_group, filters) = get_user_filter_expr(*f);
|
||||
(requires_group, Expr::not(Expr::expr(filters)))
|
||||
(requires_group, filters.not())
|
||||
}
|
||||
UserId(user_id) => (
|
||||
RequiresGroup(false),
|
||||
Expr::col((Users::Table, Users::UserId)).eq(user_id),
|
||||
Expr::col((Users::Table, Users::UserId))
|
||||
.eq(user_id)
|
||||
.into_condition(),
|
||||
),
|
||||
Equality(s1, s2) => (
|
||||
RequiresGroup(false),
|
||||
if s1 == Users::DisplayName.to_string() {
|
||||
Expr::col((Users::Table, Users::DisplayName)).eq(s2)
|
||||
Expr::col((Users::Table, Users::DisplayName))
|
||||
.eq(s2)
|
||||
.into_condition()
|
||||
} else if s1 == Users::UserId.to_string() {
|
||||
panic!("User id should be wrapped")
|
||||
} else {
|
||||
Expr::expr(Expr::cust(&s1)).eq(s2)
|
||||
Expr::expr(Expr::cust(&s1)).eq(s2).into_condition()
|
||||
},
|
||||
),
|
||||
MemberOf(group) => (
|
||||
RequiresGroup(true),
|
||||
Expr::col((Groups::Table, Groups::DisplayName)).eq(group),
|
||||
Expr::col((Groups::Table, Groups::DisplayName))
|
||||
.eq(group)
|
||||
.into_condition(),
|
||||
),
|
||||
MemberOfId(group_id) => (
|
||||
RequiresGroup(true),
|
||||
Expr::col((Groups::Table, Groups::GroupId)).eq(group_id),
|
||||
Expr::col((Groups::Table, Groups::GroupId))
|
||||
.eq(group_id)
|
||||
.into_condition(),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
// Returns the condition for the SQL query, and whether it requires joining with the groups table.
|
||||
fn get_group_filter_expr(filter: GroupRequestFilter) -> SimpleExpr {
|
||||
fn get_group_filter_expr(filter: GroupRequestFilter) -> Cond {
|
||||
use sea_query::IntoCondition;
|
||||
use GroupRequestFilter::*;
|
||||
fn get_repeated_filter(
|
||||
fs: Vec<GroupRequestFilter>,
|
||||
field: &dyn Fn(SimpleExpr, SimpleExpr) -> SimpleExpr,
|
||||
) -> SimpleExpr {
|
||||
let mut it = fs.into_iter();
|
||||
let first_expr = match it.next() {
|
||||
None => return Expr::value(true),
|
||||
Some(f) => get_group_filter_expr(f),
|
||||
};
|
||||
it.fold(first_expr, |e, f| field(e, get_group_filter_expr(f)))
|
||||
}
|
||||
match filter {
|
||||
And(fs) => get_repeated_filter(fs, &SimpleExpr::and),
|
||||
Or(fs) => get_repeated_filter(fs, &SimpleExpr::or),
|
||||
Not(f) => Expr::not(Expr::expr(get_group_filter_expr(*f))),
|
||||
DisplayName(name) => Expr::col((Groups::Table, Groups::DisplayName)).eq(name),
|
||||
GroupId(id) => Expr::col((Groups::Table, Groups::GroupId)).eq(id.0),
|
||||
Uuid(uuid) => Expr::col((Groups::Table, Groups::Uuid)).eq(uuid.to_string()),
|
||||
And(fs) => fs
|
||||
.into_iter()
|
||||
.fold(Cond::all(), |c, f| c.add(get_group_filter_expr(f))),
|
||||
Or(fs) => fs
|
||||
.into_iter()
|
||||
.fold(Cond::any(), |c, f| c.add(get_group_filter_expr(f))),
|
||||
Not(f) => get_group_filter_expr(*f).not(),
|
||||
DisplayName(name) => Expr::col((Groups::Table, Groups::DisplayName))
|
||||
.eq(name)
|
||||
.into_condition(),
|
||||
GroupId(id) => Expr::col((Groups::Table, Groups::GroupId))
|
||||
.eq(id.0)
|
||||
.into_condition(),
|
||||
Uuid(uuid) => Expr::col((Groups::Table, Groups::Uuid))
|
||||
.eq(uuid.to_string())
|
||||
.into_condition(),
|
||||
// WHERE (group_id in (SELECT group_id FROM memberships WHERE user_id = user))
|
||||
Member(user) => Expr::col((Memberships::Table, Memberships::GroupId)).in_subquery(
|
||||
Query::select()
|
||||
.column(Memberships::GroupId)
|
||||
.from(Memberships::Table)
|
||||
.cond_where(Expr::col(Memberships::UserId).eq(user))
|
||||
.take(),
|
||||
),
|
||||
Member(user) => Expr::col((Memberships::Table, Memberships::GroupId))
|
||||
.in_subquery(
|
||||
Query::select()
|
||||
.column(Memberships::GroupId)
|
||||
.from(Memberships::Table)
|
||||
.cond_where(Expr::col(Memberships::UserId).eq(user))
|
||||
.take(),
|
||||
)
|
||||
.into_condition(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -287,7 +286,7 @@ impl BackendHandler for SqlBackendHandler {
|
||||
Ok(groups)
|
||||
}
|
||||
|
||||
#[instrument(skip_all, level = "debug", ret, err)]
|
||||
#[instrument(skip_all, level = "debug", ret)]
|
||||
async fn get_user_details(&self, user_id: &UserId) -> Result<User> {
|
||||
debug!(?user_id);
|
||||
let (query, values) = Query::select()
|
||||
@@ -367,6 +366,7 @@ impl BackendHandler for SqlBackendHandler {
|
||||
Users::DisplayName,
|
||||
Users::FirstName,
|
||||
Users::LastName,
|
||||
Users::Avatar,
|
||||
Users::CreationDate,
|
||||
Users::Uuid,
|
||||
];
|
||||
@@ -378,6 +378,7 @@ impl BackendHandler for SqlBackendHandler {
|
||||
request.display_name.unwrap_or_default().into(),
|
||||
request.first_name.unwrap_or_default().into(),
|
||||
request.last_name.unwrap_or_default().into(),
|
||||
request.avatar.unwrap_or_default().into(),
|
||||
now.naive_utc().into(),
|
||||
uuid.into(),
|
||||
];
|
||||
@@ -409,6 +410,9 @@ impl BackendHandler for SqlBackendHandler {
|
||||
if let Some(last_name) = request.last_name {
|
||||
values.push((Users::LastName, last_name.into()));
|
||||
}
|
||||
if let Some(avatar) = request.avatar {
|
||||
values.push((Users::Avatar, avatar.into()));
|
||||
}
|
||||
if values.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
@@ -532,10 +536,7 @@ mod tests {
|
||||
use lldap_auth::{opaque, registration};
|
||||
|
||||
fn get_default_config() -> Configuration {
|
||||
ConfigurationBuilder::default()
|
||||
.verbose(true)
|
||||
.build()
|
||||
.unwrap()
|
||||
ConfigurationBuilder::for_tests()
|
||||
}
|
||||
|
||||
async fn get_in_memory_db() -> Pool {
|
||||
@@ -695,6 +696,21 @@ mod tests {
|
||||
.await;
|
||||
assert_eq!(users, vec!["bob", "john"]);
|
||||
}
|
||||
{
|
||||
let users = get_user_names(
|
||||
&handler,
|
||||
Some(UserRequestFilter::And(vec![
|
||||
UserRequestFilter::Or(vec![]),
|
||||
UserRequestFilter::Or(vec![
|
||||
UserRequestFilter::UserId(UserId::new("bob")),
|
||||
UserRequestFilter::UserId(UserId::new("John")),
|
||||
UserRequestFilter::UserId(UserId::new("random")),
|
||||
]),
|
||||
])),
|
||||
)
|
||||
.await;
|
||||
assert_eq!(users, vec!["bob", "john"]);
|
||||
}
|
||||
{
|
||||
let users = get_user_names(
|
||||
&handler,
|
||||
@@ -879,14 +895,14 @@ mod tests {
|
||||
insert_user(&handler, "Jennz", "boupBoup").await;
|
||||
|
||||
// Remove a user
|
||||
let _request_result = handler.delete_user(&UserId::new("Jennz")).await.unwrap();
|
||||
handler.delete_user(&UserId::new("Jennz")).await.unwrap();
|
||||
|
||||
assert_eq!(get_user_names(&handler, None).await, vec!["hector", "val"]);
|
||||
|
||||
// Insert new user and remove two
|
||||
insert_user(&handler, "NewBoi", "Joni").await;
|
||||
let _request_result = handler.delete_user(&UserId::new("Hector")).await.unwrap();
|
||||
let _request_result = handler.delete_user(&UserId::new("NewBoi")).await.unwrap();
|
||||
handler.delete_user(&UserId::new("Hector")).await.unwrap();
|
||||
handler.delete_user(&UserId::new("NewBoi")).await.unwrap();
|
||||
|
||||
assert_eq!(get_user_names(&handler, None).await, vec!["val"]);
|
||||
}
|
||||
|
||||
@@ -77,11 +77,10 @@ async fn column_exists(pool: &Pool, table_name: &str, column_name: &str) -> sqlx
|
||||
"SELECT COUNT(*) AS col_count FROM pragma_table_info('{}') WHERE name = '{}'",
|
||||
table_name, column_name
|
||||
);
|
||||
Ok(sqlx::query(&query)
|
||||
.fetch_one(pool)
|
||||
.await?
|
||||
.get::<i32, _>("col_count")
|
||||
> 0)
|
||||
match sqlx::query(&query).fetch_one(pool).await {
|
||||
Err(_) => Ok(false),
|
||||
Ok(row) => Ok(row.get::<i32, _>("col_count") > 0),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn create_group(group_name: &str, pool: &Pool) -> sqlx::Result<()> {
|
||||
|
||||
@@ -562,7 +562,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Debug)]
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||
pub enum Permission {
|
||||
Admin,
|
||||
PasswordManager,
|
||||
@@ -570,7 +570,7 @@ pub enum Permission {
|
||||
Regular,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct ValidationResults {
|
||||
pub user: UserId,
|
||||
pub permission: Permission,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use clap::Parser;
|
||||
use lettre::message::Mailbox;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// lldap is a lightweight LDAP server
|
||||
#[derive(Debug, Parser, Clone)]
|
||||
@@ -102,6 +103,14 @@ pub struct LdapsOpts {
|
||||
pub ldaps_key_file: Option<String>,
|
||||
}
|
||||
|
||||
clap::arg_enum! {
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
pub enum SmtpEncryption {
|
||||
TLS,
|
||||
STARTTLS,
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Parser, Clone)]
|
||||
#[clap(next_help_heading = Some("SMTP"), setting = clap::AppSettings::DeriveDisplayOrder)]
|
||||
pub struct SmtpOpts {
|
||||
@@ -130,8 +139,11 @@ pub struct SmtpOpts {
|
||||
pub smtp_password: Option<String>,
|
||||
|
||||
/// Whether TLS should be used to connect to SMTP.
|
||||
#[clap(long, env = "LLDAP_SMTP_OPTIONS__TLS_REQUIRED")]
|
||||
#[clap(long, env = "LLDAP_SMTP_OPTIONS__TLS_REQUIRED", setting=clap::ArgSettings::Hidden)]
|
||||
pub smtp_tls_required: Option<bool>,
|
||||
|
||||
#[clap(long, env = "LLDAP_SMTP_OPTIONS__ENCRYPTION", possible_values = SmtpEncryption::variants(), case_insensitive = true)]
|
||||
pub smtp_encryption: Option<SmtpEncryption>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Parser, Clone)]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::{
|
||||
domain::handler::UserId,
|
||||
infra::cli::{GeneralConfigOpts, LdapsOpts, RunOpts, SmtpOpts, TestEmailOpts},
|
||||
infra::cli::{GeneralConfigOpts, LdapsOpts, RunOpts, SmtpEncryption, SmtpOpts, TestEmailOpts},
|
||||
};
|
||||
use anyhow::{Context, Result};
|
||||
use figment::{
|
||||
@@ -29,8 +29,11 @@ pub struct MailOptions {
|
||||
pub user: String,
|
||||
#[builder(default = r#"SecUtf8::from("")"#)]
|
||||
pub password: SecUtf8,
|
||||
#[builder(default = "true")]
|
||||
pub tls_required: bool,
|
||||
#[builder(default = "SmtpEncryption::TLS")]
|
||||
pub smtp_encryption: SmtpEncryption,
|
||||
/// Deprecated.
|
||||
#[builder(default = "None")]
|
||||
pub tls_required: Option<bool>,
|
||||
}
|
||||
|
||||
impl std::default::Default for MailOptions {
|
||||
@@ -71,6 +74,8 @@ pub struct Configuration {
|
||||
pub ldap_base_dn: String,
|
||||
#[builder(default = r#"UserId::new("admin")"#)]
|
||||
pub ldap_user_dn: UserId,
|
||||
#[builder(default = r#"String::default()"#)]
|
||||
pub ldap_user_email: String,
|
||||
#[builder(default = r#"SecUtf8::from("password")"#)]
|
||||
pub ldap_user_pass: SecUtf8,
|
||||
#[builder(default = r#"String::from("sqlite://users.db?mode=rwc")"#)]
|
||||
@@ -105,6 +110,15 @@ impl ConfigurationBuilder {
|
||||
let server_setup = get_server_setup(self.key_file.as_deref().unwrap_or("server_key"))?;
|
||||
Ok(self.server_setup(Some(server_setup)).private_build()?)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn for_tests() -> Configuration {
|
||||
ConfigurationBuilder::default()
|
||||
.verbose(true)
|
||||
.server_setup(Some(generate_random_private_key()))
|
||||
.private_build()
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl Configuration {
|
||||
@@ -117,17 +131,34 @@ impl Configuration {
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_random_private_key() -> ServerSetup {
|
||||
let mut rng = rand::rngs::OsRng;
|
||||
ServerSetup::new(&mut rng)
|
||||
}
|
||||
|
||||
fn write_to_readonly_file(path: &std::path::Path, buffer: &[u8]) -> Result<()> {
|
||||
use std::{fs::File, io::Write};
|
||||
assert!(!path.exists());
|
||||
let mut file = File::create(path)?;
|
||||
let mut permissions = file.metadata()?.permissions();
|
||||
permissions.set_readonly(true);
|
||||
if cfg!(unix) {
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
permissions.set_mode(0o400);
|
||||
}
|
||||
file.set_permissions(permissions)?;
|
||||
Ok(file.write_all(buffer)?)
|
||||
}
|
||||
|
||||
fn get_server_setup(file_path: &str) -> Result<ServerSetup> {
|
||||
use std::path::Path;
|
||||
let path = Path::new(file_path);
|
||||
use std::fs::read;
|
||||
let path = std::path::Path::new(file_path);
|
||||
if path.exists() {
|
||||
let bytes =
|
||||
std::fs::read(file_path).context(format!("Could not read key file `{}`", file_path))?;
|
||||
let bytes = read(file_path).context(format!("Could not read key file `{}`", file_path))?;
|
||||
Ok(ServerSetup::deserialize(&bytes)?)
|
||||
} else {
|
||||
let mut rng = rand::rngs::OsRng;
|
||||
let server_setup = ServerSetup::new(&mut rng);
|
||||
std::fs::write(path, server_setup.serialize()).context(format!(
|
||||
let server_setup = generate_random_private_key();
|
||||
write_to_readonly_file(path, &server_setup.serialize()).context(format!(
|
||||
"Could not write the generated server setup to file `{}`",
|
||||
file_path,
|
||||
))?;
|
||||
@@ -232,7 +263,7 @@ impl ConfigOverrider for SmtpOpts {
|
||||
config.smtp_options.password = SecUtf8::from(password.clone());
|
||||
}
|
||||
if let Some(tls_required) = self.smtp_tls_required {
|
||||
config.smtp_options.tls_required = tls_required;
|
||||
config.smtp_options.tls_required = Some(tls_required);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -248,11 +279,13 @@ where
|
||||
overrides.general_config().config_file
|
||||
);
|
||||
|
||||
use figment_file_provider_adapter::FileAdapter;
|
||||
let ignore_keys = ["key_file", "cert_file"];
|
||||
let mut config: Configuration = Figment::from(Serialized::defaults(
|
||||
ConfigurationBuilder::default().private_build().unwrap(),
|
||||
))
|
||||
.merge(Toml::file(config_file))
|
||||
.merge(Env::prefixed("LLDAP_").split("__"))
|
||||
.merge(FileAdapter::wrap(Toml::file(config_file)).ignore(&ignore_keys))
|
||||
.merge(FileAdapter::wrap(Env::prefixed("LLDAP_").split("__")).ignore(&ignore_keys))
|
||||
.extract()?;
|
||||
|
||||
overrides.override_config(&mut config);
|
||||
@@ -266,5 +299,8 @@ where
|
||||
if config.ldap_user_pass == SecUtf8::from("password") {
|
||||
println!("WARNING: Unsecure default admin password is used.");
|
||||
}
|
||||
if config.smtp_options.tls_required.is_some() {
|
||||
println!("DEPRECATED: smtp_options.tls_required field is deprecated, it never did anything. You can replace it with smtp_options.smtp_encryption.");
|
||||
}
|
||||
Ok(config)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
use crate::domain::handler::{
|
||||
BackendHandler, CreateUserRequest, GroupId, UpdateGroupRequest, UpdateUserRequest, UserId,
|
||||
BackendHandler, CreateUserRequest, GroupId, JpegPhoto, UpdateGroupRequest, UpdateUserRequest,
|
||||
UserId,
|
||||
};
|
||||
use anyhow::Context as AnyhowContext;
|
||||
use juniper::{graphql_object, FieldResult, GraphQLInputObject, GraphQLObject};
|
||||
use tracing::{debug, debug_span, Instrument};
|
||||
|
||||
@@ -28,6 +30,8 @@ pub struct CreateUserInput {
|
||||
display_name: Option<String>,
|
||||
first_name: Option<String>,
|
||||
last_name: Option<String>,
|
||||
// Base64 encoded JpegPhoto.
|
||||
avatar: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Debug, GraphQLInputObject)]
|
||||
@@ -38,6 +42,8 @@ pub struct UpdateUserInput {
|
||||
display_name: Option<String>,
|
||||
first_name: Option<String>,
|
||||
last_name: Option<String>,
|
||||
// Base64 encoded JpegPhoto.
|
||||
avatar: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Debug, GraphQLInputObject)]
|
||||
@@ -73,6 +79,14 @@ impl<Handler: BackendHandler + Sync> Mutation<Handler> {
|
||||
return Err("Unauthorized user creation".into());
|
||||
}
|
||||
let user_id = UserId::new(&user.id);
|
||||
let avatar = user
|
||||
.avatar
|
||||
.map(base64::decode)
|
||||
.transpose()
|
||||
.context("Invalid base64 image")?
|
||||
.map(JpegPhoto::try_from)
|
||||
.transpose()
|
||||
.context("Provided image is not a valid JPEG")?;
|
||||
context
|
||||
.handler
|
||||
.create_user(CreateUserRequest {
|
||||
@@ -81,6 +95,7 @@ impl<Handler: BackendHandler + Sync> Mutation<Handler> {
|
||||
display_name: user.display_name,
|
||||
first_name: user.first_name,
|
||||
last_name: user.last_name,
|
||||
avatar,
|
||||
})
|
||||
.instrument(span.clone())
|
||||
.await?;
|
||||
@@ -126,6 +141,14 @@ impl<Handler: BackendHandler + Sync> Mutation<Handler> {
|
||||
span.in_scope(|| debug!("Unauthorized"));
|
||||
return Err("Unauthorized user update".into());
|
||||
}
|
||||
let avatar = user
|
||||
.avatar
|
||||
.map(base64::decode)
|
||||
.transpose()
|
||||
.context("Invalid base64 image")?
|
||||
.map(JpegPhoto::try_from)
|
||||
.transpose()
|
||||
.context("Provided image is not a valid JPEG")?;
|
||||
context
|
||||
.handler
|
||||
.update_user(UpdateUserRequest {
|
||||
@@ -134,6 +157,7 @@ impl<Handler: BackendHandler + Sync> Mutation<Handler> {
|
||||
display_name: user.display_name,
|
||||
first_name: user.first_name,
|
||||
last_name: user.last_name,
|
||||
avatar,
|
||||
})
|
||||
.instrument(span)
|
||||
.await?;
|
||||
|
||||
@@ -217,10 +217,18 @@ impl<Handler: BackendHandler + Sync> User<Handler> {
|
||||
&self.user.last_name
|
||||
}
|
||||
|
||||
fn avatar(&self) -> String {
|
||||
(&self.user.avatar).into()
|
||||
}
|
||||
|
||||
fn creation_date(&self) -> chrono::DateTime<chrono::Utc> {
|
||||
self.user.creation_date
|
||||
}
|
||||
|
||||
fn uuid(&self) -> &str {
|
||||
self.user.uuid.as_str()
|
||||
}
|
||||
|
||||
/// The groups to which this user belongs.
|
||||
async fn groups(&self, context: &Context<Handler>) -> FieldResult<Vec<Group<Handler>>> {
|
||||
let span = debug_span!("[GraphQL query] user::groups");
|
||||
@@ -260,6 +268,7 @@ pub struct Group<Handler: BackendHandler> {
|
||||
group_id: i32,
|
||||
display_name: String,
|
||||
creation_date: chrono::DateTime<chrono::Utc>,
|
||||
uuid: String,
|
||||
members: Option<Vec<String>>,
|
||||
_phantom: std::marker::PhantomData<Box<Handler>>,
|
||||
}
|
||||
@@ -272,6 +281,12 @@ impl<Handler: BackendHandler + Sync> Group<Handler> {
|
||||
fn display_name(&self) -> String {
|
||||
self.display_name.clone()
|
||||
}
|
||||
fn creation_date(&self) -> chrono::DateTime<chrono::Utc> {
|
||||
self.creation_date
|
||||
}
|
||||
fn uuid(&self) -> String {
|
||||
self.uuid.clone()
|
||||
}
|
||||
/// The groups to which this user belongs.
|
||||
async fn users(&self, context: &Context<Handler>) -> FieldResult<Vec<User<Handler>>> {
|
||||
let span = debug_span!("[GraphQL query] group::users");
|
||||
@@ -300,6 +315,7 @@ impl<Handler: BackendHandler> From<GroupDetails> for Group<Handler> {
|
||||
group_id: group_details.group_id.0,
|
||||
display_name: group_details.display_name,
|
||||
creation_date: group_details.creation_date,
|
||||
uuid: group_details.uuid.into_string(),
|
||||
members: None,
|
||||
_phantom: std::marker::PhantomData,
|
||||
}
|
||||
@@ -312,6 +328,7 @@ impl<Handler: BackendHandler> From<DomainGroup> for Group<Handler> {
|
||||
group_id: group.id.0,
|
||||
display_name: group.display_name,
|
||||
creation_date: group.creation_date,
|
||||
uuid: group.uuid.into_string(),
|
||||
members: Some(group.users.into_iter().map(UserId::into_string).collect()),
|
||||
_phantom: std::marker::PhantomData,
|
||||
}
|
||||
@@ -350,8 +367,12 @@ mod tests {
|
||||
user(userId: "bob") {
|
||||
id
|
||||
email
|
||||
creationDate
|
||||
uuid
|
||||
groups {
|
||||
id
|
||||
creationDate
|
||||
uuid
|
||||
}
|
||||
}
|
||||
}"#;
|
||||
@@ -363,6 +384,8 @@ mod tests {
|
||||
Ok(DomainUser {
|
||||
user_id: UserId::new("bob"),
|
||||
email: "bob@bobbers.on".to_string(),
|
||||
creation_date: chrono::Utc.timestamp_millis(42),
|
||||
uuid: crate::uuid!("b1a2a3a4b1b2c1c2d1d2d3d4d5d6d7d8"),
|
||||
..Default::default()
|
||||
})
|
||||
});
|
||||
@@ -391,7 +414,13 @@ mod tests {
|
||||
"user": {
|
||||
"id": "bob",
|
||||
"email": "bob@bobbers.on",
|
||||
"groups": [{"id": 3}]
|
||||
"creationDate": "1970-01-01T00:00:00.042+00:00",
|
||||
"uuid": "b1a2a3a4-b1b2-c1c2-d1d2-d3d4d5d6d7d8",
|
||||
"groups": [{
|
||||
"id": 3,
|
||||
"creationDate": "1970-01-01T00:00:00.000000042+00:00",
|
||||
"uuid": "a1a2a3a4-b1b2-c1c2-d1d2-d3d4d5d6d7d8"
|
||||
}]
|
||||
}
|
||||
}),
|
||||
vec![]
|
||||
|
||||
@@ -10,12 +10,12 @@ use crate::{
|
||||
};
|
||||
use anyhow::{bail, Context, Result};
|
||||
use itertools::Itertools;
|
||||
use ldap3_server::proto::{
|
||||
use ldap3_proto::proto::{
|
||||
LdapBindCred, LdapBindRequest, LdapBindResponse, LdapExtendedRequest, LdapExtendedResponse,
|
||||
LdapFilter, LdapOp, LdapPartialAttribute, LdapPasswordModifyRequest, LdapResult,
|
||||
LdapResultCode, LdapSearchRequest, LdapSearchResultEntry, LdapSearchScope,
|
||||
};
|
||||
use tracing::{debug, instrument, warn};
|
||||
use tracing::{debug, info, instrument, warn};
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
struct LdapDn(String);
|
||||
@@ -151,25 +151,26 @@ fn get_user_id_from_distinguished_name(
|
||||
fn get_user_attribute(
|
||||
user: &User,
|
||||
attribute: &str,
|
||||
dn: &str,
|
||||
base_dn_str: &str,
|
||||
groups: Option<&[GroupDetails]>,
|
||||
ignored_user_attributes: &[String],
|
||||
) -> Result<Option<Vec<String>>> {
|
||||
) -> Result<Option<Vec<Vec<u8>>>> {
|
||||
let attribute = attribute.to_ascii_lowercase();
|
||||
Ok(Some(match attribute.as_str() {
|
||||
let attribute_values = match attribute.as_str() {
|
||||
"objectclass" => vec![
|
||||
"inetOrgPerson".to_string(),
|
||||
"posixAccount".to_string(),
|
||||
"mailAccount".to_string(),
|
||||
"person".to_string(),
|
||||
b"inetOrgPerson".to_vec(),
|
||||
b"posixAccount".to_vec(),
|
||||
b"mailAccount".to_vec(),
|
||||
b"person".to_vec(),
|
||||
],
|
||||
"dn" | "distinguishedname" => vec![dn.to_string()],
|
||||
"uid" => vec![user.user_id.to_string()],
|
||||
"entryuuid" => vec![user.uuid.to_string()],
|
||||
"mail" => vec![user.email.clone()],
|
||||
"givenname" => vec![user.first_name.clone()],
|
||||
"sn" => vec![user.last_name.clone()],
|
||||
// dn is always returned as part of the base response.
|
||||
"dn" | "distinguishedname" => return Ok(None),
|
||||
"uid" => vec![user.user_id.to_string().into_bytes()],
|
||||
"entryuuid" => vec![user.uuid.to_string().into_bytes()],
|
||||
"mail" => vec![user.email.clone().into_bytes()],
|
||||
"givenname" => vec![user.first_name.clone().into_bytes()],
|
||||
"sn" => vec![user.last_name.clone().into_bytes()],
|
||||
"jpegphoto" => vec![user.avatar.clone().into_bytes()],
|
||||
"memberof" => groups
|
||||
.into_iter()
|
||||
.flatten()
|
||||
@@ -178,10 +179,11 @@ fn get_user_attribute(
|
||||
"uid={},ou=groups,{}",
|
||||
&id_and_name.display_name, base_dn_str
|
||||
)
|
||||
.into_bytes()
|
||||
})
|
||||
.collect(),
|
||||
"cn" | "displayname" => vec![user.display_name.clone()],
|
||||
"createtimestamp" | "modifytimestamp" => vec![user.creation_date.to_rfc3339()],
|
||||
"cn" | "displayname" => vec![user.display_name.clone().into_bytes()],
|
||||
"createtimestamp" | "modifytimestamp" => vec![user.creation_date.to_rfc3339().into_bytes()],
|
||||
"1.1" => return Ok(None),
|
||||
// We ignore the operational attribute wildcard.
|
||||
"+" => return Ok(None),
|
||||
@@ -201,7 +203,12 @@ fn get_user_attribute(
|
||||
}
|
||||
return Ok(None);
|
||||
}
|
||||
}))
|
||||
};
|
||||
if attribute_values.len() == 1 && attribute_values[0].is_empty() {
|
||||
Ok(None)
|
||||
} else {
|
||||
Ok(Some(attribute_values))
|
||||
}
|
||||
}
|
||||
|
||||
#[instrument(skip_all, level = "debug")]
|
||||
@@ -232,12 +239,12 @@ fn expand_attribute_wildcards<'a>(
|
||||
|
||||
const ALL_USER_ATTRIBUTE_KEYS: &[&str] = &[
|
||||
"objectclass",
|
||||
"dn",
|
||||
"uid",
|
||||
"mail",
|
||||
"givenname",
|
||||
"sn",
|
||||
"cn",
|
||||
"jpegPhoto",
|
||||
"createtimestamp",
|
||||
];
|
||||
|
||||
@@ -251,14 +258,13 @@ fn make_ldap_search_user_result_entry(
|
||||
let dn = format!("uid={},ou=people,{}", user.user_id.as_str(), base_dn_str);
|
||||
|
||||
Ok(LdapSearchResultEntry {
|
||||
dn: dn.clone(),
|
||||
dn,
|
||||
attributes: attributes
|
||||
.iter()
|
||||
.filter_map(|a| {
|
||||
let values = match get_user_attribute(
|
||||
&user,
|
||||
a,
|
||||
&dn,
|
||||
base_dn_str,
|
||||
groups,
|
||||
ignored_user_attributes,
|
||||
@@ -281,21 +287,19 @@ fn get_group_attribute(
|
||||
attribute: &str,
|
||||
user_filter: &Option<&UserId>,
|
||||
ignored_group_attributes: &[String],
|
||||
) -> Result<Option<Vec<String>>> {
|
||||
) -> Result<Option<Vec<Vec<u8>>>> {
|
||||
let attribute = attribute.to_ascii_lowercase();
|
||||
Ok(Some(match attribute.as_str() {
|
||||
"objectclass" => vec!["groupOfUniqueNames".to_string()],
|
||||
"dn" | "distinguishedname" => vec![format!(
|
||||
"cn={},ou=groups,{}",
|
||||
group.display_name, base_dn_str
|
||||
)],
|
||||
"cn" | "uid" => vec![group.display_name.clone()],
|
||||
"entryuuid" => vec![group.uuid.to_string()],
|
||||
let attribute_values = match attribute.as_str() {
|
||||
"objectclass" => vec![b"groupOfUniqueNames".to_vec()],
|
||||
// Always returned as part of the base response.
|
||||
"dn" | "distinguishedname" => return Ok(None),
|
||||
"cn" | "uid" => vec![group.display_name.clone().into_bytes()],
|
||||
"entryuuid" => vec![group.uuid.to_string().into_bytes()],
|
||||
"member" | "uniquemember" => group
|
||||
.users
|
||||
.iter()
|
||||
.filter(|u| user_filter.map(|f| *u == f).unwrap_or(true))
|
||||
.map(|u| format!("uid={},ou=people,{}", u, base_dn_str))
|
||||
.map(|u| format!("uid={},ou=people,{}", u, base_dn_str).into_bytes())
|
||||
.collect(),
|
||||
"1.1" => return Ok(None),
|
||||
// We ignore the operational attribute wildcard
|
||||
@@ -316,11 +320,15 @@ fn get_group_attribute(
|
||||
}
|
||||
return Ok(None);
|
||||
}
|
||||
}))
|
||||
};
|
||||
if attribute_values.len() == 1 && attribute_values[0].is_empty() {
|
||||
Ok(None)
|
||||
} else {
|
||||
Ok(Some(attribute_values))
|
||||
}
|
||||
}
|
||||
|
||||
const ALL_GROUP_ATTRIBUTE_KEYS: &[&str] =
|
||||
&["objectclass", "dn", "uid", "cn", "member", "uniquemember"];
|
||||
const ALL_GROUP_ATTRIBUTE_KEYS: &[&str] = &["objectclass", "uid", "cn", "member", "uniquemember"];
|
||||
|
||||
fn make_ldap_search_group_result_entry(
|
||||
group: Group,
|
||||
@@ -423,27 +431,27 @@ fn root_dse_response(base_dn: &str) -> LdapOp {
|
||||
attributes: vec![
|
||||
LdapPartialAttribute {
|
||||
atype: "objectClass".to_string(),
|
||||
vals: vec!["top".to_string()],
|
||||
vals: vec![b"top".to_vec()],
|
||||
},
|
||||
LdapPartialAttribute {
|
||||
atype: "vendorName".to_string(),
|
||||
vals: vec!["LLDAP".to_string()],
|
||||
vals: vec![b"LLDAP".to_vec()],
|
||||
},
|
||||
LdapPartialAttribute {
|
||||
atype: "vendorVersion".to_string(),
|
||||
vals: vec!["lldap_0.2.0".to_string()],
|
||||
vals: vec![b"lldap_0.2.0".to_vec()],
|
||||
},
|
||||
LdapPartialAttribute {
|
||||
atype: "supportedLDAPVersion".to_string(),
|
||||
vals: vec!["3".to_string()],
|
||||
vals: vec![b"3".to_vec()],
|
||||
},
|
||||
LdapPartialAttribute {
|
||||
atype: "supportedExtension".to_string(),
|
||||
vals: vec!["1.3.6.1.4.1.4203.1.11.1".to_string()],
|
||||
vals: vec![b"1.3.6.1.4.1.4203.1.11.1".to_vec()],
|
||||
},
|
||||
LdapPartialAttribute {
|
||||
atype: "defaultnamingcontext".to_string(),
|
||||
vals: vec![base_dn.to_string()],
|
||||
vals: vec![base_dn.to_string().into_bytes()],
|
||||
},
|
||||
],
|
||||
})
|
||||
@@ -738,6 +746,7 @@ impl<Backend: BackendHandler + LoginHandler + OpaqueHandler> LdapHandler<Backend
|
||||
let parsed_filters = match user_filter {
|
||||
None => filters,
|
||||
Some(u) => {
|
||||
info!("Unpriviledged search, limiting results");
|
||||
UserRequestFilter::And(vec![filters, UserRequestFilter::UserId((*u).clone())])
|
||||
}
|
||||
};
|
||||
@@ -802,6 +811,7 @@ impl<Backend: BackendHandler + LoginHandler + OpaqueHandler> LdapHandler<Backend
|
||||
let parsed_filters = match user_filter {
|
||||
None => filter,
|
||||
Some(u) => {
|
||||
info!("Unpriviledged search, limiting results");
|
||||
GroupRequestFilter::And(vec![filter, GroupRequestFilter::Member((*u).clone())])
|
||||
}
|
||||
};
|
||||
@@ -928,7 +938,11 @@ impl<Backend: BackendHandler + LoginHandler + OpaqueHandler> LdapHandler<Backend
|
||||
self.convert_group_filter(filter)?,
|
||||
))),
|
||||
LdapFilter::Present(field) => {
|
||||
if ALL_GROUP_ATTRIBUTE_KEYS.contains(&field.to_ascii_lowercase().as_str()) {
|
||||
let field = &field.to_ascii_lowercase();
|
||||
if field == "dn"
|
||||
|| field == "distinguishedname"
|
||||
|| ALL_GROUP_ATTRIBUTE_KEYS.contains(&field.as_str())
|
||||
{
|
||||
Ok(GroupRequestFilter::And(vec![]))
|
||||
} else {
|
||||
Ok(GroupRequestFilter::Not(Box::new(GroupRequestFilter::And(
|
||||
@@ -1005,7 +1019,11 @@ impl<Backend: BackendHandler + LoginHandler + OpaqueHandler> LdapHandler<Backend
|
||||
LdapFilter::Present(field) => {
|
||||
let field = &field.to_ascii_lowercase();
|
||||
// Check that it's a field we support.
|
||||
if field == "objectclass" || map_field(field).is_ok() {
|
||||
if field == "objectclass"
|
||||
|| field == "dn"
|
||||
|| field == "distinguishedname"
|
||||
|| map_field(field).is_ok()
|
||||
{
|
||||
Ok(UserRequestFilter::And(vec![]))
|
||||
} else {
|
||||
Ok(UserRequestFilter::Not(Box::new(UserRequestFilter::And(
|
||||
@@ -1027,7 +1045,7 @@ mod tests {
|
||||
};
|
||||
use async_trait::async_trait;
|
||||
use chrono::TimeZone;
|
||||
use ldap3_server::proto::{LdapDerefAliases, LdapSearchScope};
|
||||
use ldap3_proto::proto::{LdapDerefAliases, LdapSearchScope};
|
||||
use mockall::predicate::eq;
|
||||
use std::collections::HashSet;
|
||||
use tokio;
|
||||
@@ -1309,7 +1327,7 @@ mod tests {
|
||||
dn: "uid=bob,ou=people,dc=example,dc=com".to_string(),
|
||||
attributes: vec![LdapPartialAttribute {
|
||||
atype: "memberOf".to_string(),
|
||||
vals: vec!["uid=rockstars,ou=groups,dc=example,dc=com".to_string()]
|
||||
vals: vec![b"uid=rockstars,ou=groups,dc=example,dc=com".to_vec()]
|
||||
}],
|
||||
}),
|
||||
make_search_success(),
|
||||
@@ -1454,6 +1472,7 @@ mod tests {
|
||||
display_name: "Jimminy Cricket".to_string(),
|
||||
first_name: "Jim".to_string(),
|
||||
last_name: "Cricket".to_string(),
|
||||
avatar: JpegPhoto::for_tests(),
|
||||
uuid: uuid!("04ac75e0-2900-3e21-926c-2f732c26b3fc"),
|
||||
creation_date: Utc.ymd(2014, 7, 8).and_hms(9, 10, 11),
|
||||
},
|
||||
@@ -1474,6 +1493,7 @@ mod tests {
|
||||
"cn",
|
||||
"createTimestamp",
|
||||
"entryUuid",
|
||||
"jpegPhoto",
|
||||
],
|
||||
);
|
||||
assert_eq!(
|
||||
@@ -1485,43 +1505,39 @@ mod tests {
|
||||
LdapPartialAttribute {
|
||||
atype: "objectClass".to_string(),
|
||||
vals: vec![
|
||||
"inetOrgPerson".to_string(),
|
||||
"posixAccount".to_string(),
|
||||
"mailAccount".to_string(),
|
||||
"person".to_string()
|
||||
b"inetOrgPerson".to_vec(),
|
||||
b"posixAccount".to_vec(),
|
||||
b"mailAccount".to_vec(),
|
||||
b"person".to_vec()
|
||||
]
|
||||
},
|
||||
LdapPartialAttribute {
|
||||
atype: "dn".to_string(),
|
||||
vals: vec!["uid=bob_1,ou=people,dc=example,dc=com".to_string()]
|
||||
},
|
||||
LdapPartialAttribute {
|
||||
atype: "uid".to_string(),
|
||||
vals: vec!["bob_1".to_string()]
|
||||
vals: vec![b"bob_1".to_vec()]
|
||||
},
|
||||
LdapPartialAttribute {
|
||||
atype: "mail".to_string(),
|
||||
vals: vec!["bob@bobmail.bob".to_string()]
|
||||
vals: vec![b"bob@bobmail.bob".to_vec()]
|
||||
},
|
||||
LdapPartialAttribute {
|
||||
atype: "givenName".to_string(),
|
||||
vals: vec!["Bôb".to_string()]
|
||||
vals: vec!["Bôb".to_string().into_bytes()]
|
||||
},
|
||||
LdapPartialAttribute {
|
||||
atype: "sn".to_string(),
|
||||
vals: vec!["Böbberson".to_string()]
|
||||
vals: vec!["Böbberson".to_string().into_bytes()]
|
||||
},
|
||||
LdapPartialAttribute {
|
||||
atype: "cn".to_string(),
|
||||
vals: vec!["Bôb Böbberson".to_string()]
|
||||
vals: vec!["Bôb Böbberson".to_string().into_bytes()]
|
||||
},
|
||||
LdapPartialAttribute {
|
||||
atype: "createTimestamp".to_string(),
|
||||
vals: vec!["1970-01-01T00:00:00+00:00".to_string()]
|
||||
vals: vec![b"1970-01-01T00:00:00+00:00".to_vec()]
|
||||
},
|
||||
LdapPartialAttribute {
|
||||
atype: "entryUuid".to_string(),
|
||||
vals: vec!["698e1d5f-7a40-3151-8745-b9b8a37839da".to_string()]
|
||||
vals: vec![b"698e1d5f-7a40-3151-8745-b9b8a37839da".to_vec()]
|
||||
},
|
||||
],
|
||||
}),
|
||||
@@ -1531,43 +1547,43 @@ mod tests {
|
||||
LdapPartialAttribute {
|
||||
atype: "objectClass".to_string(),
|
||||
vals: vec![
|
||||
"inetOrgPerson".to_string(),
|
||||
"posixAccount".to_string(),
|
||||
"mailAccount".to_string(),
|
||||
"person".to_string()
|
||||
b"inetOrgPerson".to_vec(),
|
||||
b"posixAccount".to_vec(),
|
||||
b"mailAccount".to_vec(),
|
||||
b"person".to_vec()
|
||||
]
|
||||
},
|
||||
LdapPartialAttribute {
|
||||
atype: "dn".to_string(),
|
||||
vals: vec!["uid=jim,ou=people,dc=example,dc=com".to_string()]
|
||||
},
|
||||
LdapPartialAttribute {
|
||||
atype: "uid".to_string(),
|
||||
vals: vec!["jim".to_string()]
|
||||
vals: vec![b"jim".to_vec()]
|
||||
},
|
||||
LdapPartialAttribute {
|
||||
atype: "mail".to_string(),
|
||||
vals: vec!["jim@cricket.jim".to_string()]
|
||||
vals: vec![b"jim@cricket.jim".to_vec()]
|
||||
},
|
||||
LdapPartialAttribute {
|
||||
atype: "givenName".to_string(),
|
||||
vals: vec!["Jim".to_string()]
|
||||
vals: vec![b"Jim".to_vec()]
|
||||
},
|
||||
LdapPartialAttribute {
|
||||
atype: "sn".to_string(),
|
||||
vals: vec!["Cricket".to_string()]
|
||||
vals: vec![b"Cricket".to_vec()]
|
||||
},
|
||||
LdapPartialAttribute {
|
||||
atype: "cn".to_string(),
|
||||
vals: vec!["Jimminy Cricket".to_string()]
|
||||
vals: vec![b"Jimminy Cricket".to_vec()]
|
||||
},
|
||||
LdapPartialAttribute {
|
||||
atype: "createTimestamp".to_string(),
|
||||
vals: vec!["2014-07-08T09:10:11+00:00".to_string()]
|
||||
vals: vec![b"2014-07-08T09:10:11+00:00".to_vec()]
|
||||
},
|
||||
LdapPartialAttribute {
|
||||
atype: "entryUuid".to_string(),
|
||||
vals: vec!["04ac75e0-2900-3e21-926c-2f732c26b3fc".to_string()]
|
||||
vals: vec![b"04ac75e0-2900-3e21-926c-2f732c26b3fc".to_vec()]
|
||||
},
|
||||
LdapPartialAttribute {
|
||||
atype: "jpegPhoto".to_string(),
|
||||
vals: vec![JpegPhoto::for_tests().into_bytes()]
|
||||
},
|
||||
],
|
||||
}),
|
||||
@@ -1614,26 +1630,22 @@ mod tests {
|
||||
attributes: vec![
|
||||
LdapPartialAttribute {
|
||||
atype: "objectClass".to_string(),
|
||||
vals: vec!["groupOfUniqueNames".to_string(),]
|
||||
},
|
||||
LdapPartialAttribute {
|
||||
atype: "dn".to_string(),
|
||||
vals: vec!["cn=group_1,ou=groups,dc=example,dc=com".to_string()]
|
||||
vals: vec![b"groupOfUniqueNames".to_vec(),]
|
||||
},
|
||||
LdapPartialAttribute {
|
||||
atype: "cn".to_string(),
|
||||
vals: vec!["group_1".to_string()]
|
||||
vals: vec![b"group_1".to_vec()]
|
||||
},
|
||||
LdapPartialAttribute {
|
||||
atype: "uniqueMember".to_string(),
|
||||
vals: vec![
|
||||
"uid=bob,ou=people,dc=example,dc=com".to_string(),
|
||||
"uid=john,ou=people,dc=example,dc=com".to_string(),
|
||||
b"uid=bob,ou=people,dc=example,dc=com".to_vec(),
|
||||
b"uid=john,ou=people,dc=example,dc=com".to_vec(),
|
||||
]
|
||||
},
|
||||
LdapPartialAttribute {
|
||||
atype: "entryUuid".to_string(),
|
||||
vals: vec!["04ac75e0-2900-3e21-926c-2f732c26b3fc".to_string()],
|
||||
vals: vec![b"04ac75e0-2900-3e21-926c-2f732c26b3fc".to_vec()],
|
||||
},
|
||||
],
|
||||
}),
|
||||
@@ -1642,23 +1654,19 @@ mod tests {
|
||||
attributes: vec![
|
||||
LdapPartialAttribute {
|
||||
atype: "objectClass".to_string(),
|
||||
vals: vec!["groupOfUniqueNames".to_string(),]
|
||||
},
|
||||
LdapPartialAttribute {
|
||||
atype: "dn".to_string(),
|
||||
vals: vec!["cn=BestGroup,ou=groups,dc=example,dc=com".to_string()]
|
||||
vals: vec![b"groupOfUniqueNames".to_vec(),]
|
||||
},
|
||||
LdapPartialAttribute {
|
||||
atype: "cn".to_string(),
|
||||
vals: vec!["BestGroup".to_string()]
|
||||
vals: vec![b"BestGroup".to_vec()]
|
||||
},
|
||||
LdapPartialAttribute {
|
||||
atype: "uniqueMember".to_string(),
|
||||
vals: vec!["uid=john,ou=people,dc=example,dc=com".to_string()]
|
||||
vals: vec![b"uid=john,ou=people,dc=example,dc=com".to_vec()]
|
||||
},
|
||||
LdapPartialAttribute {
|
||||
atype: "entryUuid".to_string(),
|
||||
vals: vec!["04ac75e0-2900-3e21-926c-2f732c26b3fc".to_string()],
|
||||
vals: vec![b"04ac75e0-2900-3e21-926c-2f732c26b3fc".to_vec()],
|
||||
},
|
||||
],
|
||||
}),
|
||||
@@ -1760,7 +1768,7 @@ mod tests {
|
||||
dn: "cn=group_1,ou=groups,dc=example,dc=com".to_string(),
|
||||
attributes: vec![LdapPartialAttribute {
|
||||
atype: "cn".to_string(),
|
||||
vals: vec!["group_1".to_string()]
|
||||
vals: vec![b"group_1".to_vec()]
|
||||
},],
|
||||
}),
|
||||
make_search_success(),
|
||||
@@ -1836,7 +1844,7 @@ mod tests {
|
||||
"ou=groups,dc=example,dc=com",
|
||||
LdapFilter::And(vec![LdapFilter::Substring(
|
||||
"whatever".to_string(),
|
||||
ldap3_server::proto::LdapSubstringFilter::default(),
|
||||
ldap3_proto::proto::LdapSubstringFilter::default(),
|
||||
)]),
|
||||
vec!["cn"],
|
||||
);
|
||||
@@ -1980,10 +1988,10 @@ mod tests {
|
||||
attributes: vec![LdapPartialAttribute {
|
||||
atype: "objectclass".to_string(),
|
||||
vals: vec![
|
||||
"inetOrgPerson".to_string(),
|
||||
"posixAccount".to_string(),
|
||||
"mailAccount".to_string(),
|
||||
"person".to_string()
|
||||
b"inetOrgPerson".to_vec(),
|
||||
b"posixAccount".to_vec(),
|
||||
b"mailAccount".to_vec(),
|
||||
b"person".to_vec()
|
||||
]
|
||||
},]
|
||||
}),
|
||||
@@ -2035,19 +2043,15 @@ mod tests {
|
||||
LdapPartialAttribute {
|
||||
atype: "objectClass".to_string(),
|
||||
vals: vec![
|
||||
"inetOrgPerson".to_string(),
|
||||
"posixAccount".to_string(),
|
||||
"mailAccount".to_string(),
|
||||
"person".to_string()
|
||||
b"inetOrgPerson".to_vec(),
|
||||
b"posixAccount".to_vec(),
|
||||
b"mailAccount".to_vec(),
|
||||
b"person".to_vec()
|
||||
]
|
||||
},
|
||||
LdapPartialAttribute {
|
||||
atype: "dn".to_string(),
|
||||
vals: vec!["uid=bob_1,ou=people,dc=example,dc=com".to_string()]
|
||||
},
|
||||
LdapPartialAttribute {
|
||||
atype: "cn".to_string(),
|
||||
vals: vec!["Bôb Böbberson".to_string()]
|
||||
vals: vec!["Bôb Böbberson".to_string().into_bytes()]
|
||||
},
|
||||
],
|
||||
}),
|
||||
@@ -2056,15 +2060,11 @@ mod tests {
|
||||
attributes: vec![
|
||||
LdapPartialAttribute {
|
||||
atype: "objectClass".to_string(),
|
||||
vals: vec!["groupOfUniqueNames".to_string(),]
|
||||
},
|
||||
LdapPartialAttribute {
|
||||
atype: "dn".to_string(),
|
||||
vals: vec!["cn=group_1,ou=groups,dc=example,dc=com".to_string()]
|
||||
vals: vec![b"groupOfUniqueNames".to_vec(),]
|
||||
},
|
||||
LdapPartialAttribute {
|
||||
atype: "cn".to_string(),
|
||||
vals: vec!["group_1".to_string()]
|
||||
vals: vec![b"group_1".to_vec()]
|
||||
},
|
||||
],
|
||||
}),
|
||||
@@ -2082,8 +2082,8 @@ mod tests {
|
||||
user_id: UserId::new("bob_1"),
|
||||
email: "bob@bobmail.bob".to_string(),
|
||||
display_name: "Bôb Böbberson".to_string(),
|
||||
first_name: "Bôb".to_string(),
|
||||
last_name: "Böbberson".to_string(),
|
||||
avatar: JpegPhoto::for_tests(),
|
||||
..Default::default()
|
||||
},
|
||||
groups: None,
|
||||
@@ -2116,39 +2116,35 @@ mod tests {
|
||||
LdapPartialAttribute {
|
||||
atype: "objectclass".to_string(),
|
||||
vals: vec![
|
||||
"inetOrgPerson".to_string(),
|
||||
"posixAccount".to_string(),
|
||||
"mailAccount".to_string(),
|
||||
"person".to_string(),
|
||||
b"inetOrgPerson".to_vec(),
|
||||
b"posixAccount".to_vec(),
|
||||
b"mailAccount".to_vec(),
|
||||
b"person".to_vec(),
|
||||
],
|
||||
},
|
||||
LdapPartialAttribute {
|
||||
atype: "dn".to_string(),
|
||||
vals: vec!["uid=bob_1,ou=people,dc=example,dc=com".to_string()],
|
||||
},
|
||||
LdapPartialAttribute {
|
||||
atype: "uid".to_string(),
|
||||
vals: vec!["bob_1".to_string()],
|
||||
vals: vec![b"bob_1".to_vec()],
|
||||
},
|
||||
LdapPartialAttribute {
|
||||
atype: "mail".to_string(),
|
||||
vals: vec!["bob@bobmail.bob".to_string()],
|
||||
},
|
||||
LdapPartialAttribute {
|
||||
atype: "givenname".to_string(),
|
||||
vals: vec!["Bôb".to_string()],
|
||||
vals: vec![b"bob@bobmail.bob".to_vec()],
|
||||
},
|
||||
LdapPartialAttribute {
|
||||
atype: "sn".to_string(),
|
||||
vals: vec!["Böbberson".to_string()],
|
||||
vals: vec!["Böbberson".to_string().into_bytes()],
|
||||
},
|
||||
LdapPartialAttribute {
|
||||
atype: "cn".to_string(),
|
||||
vals: vec!["Bôb Böbberson".to_string()],
|
||||
vals: vec!["Bôb Böbberson".to_string().into_bytes()],
|
||||
},
|
||||
LdapPartialAttribute {
|
||||
atype: "jpegPhoto".to_string(),
|
||||
vals: vec![JpegPhoto::for_tests().into_bytes()],
|
||||
},
|
||||
LdapPartialAttribute {
|
||||
atype: "createtimestamp".to_string(),
|
||||
vals: vec![chrono::Utc.timestamp(0, 0).to_rfc3339()],
|
||||
vals: vec![chrono::Utc.timestamp(0, 0).to_rfc3339().into_bytes()],
|
||||
},
|
||||
],
|
||||
}),
|
||||
@@ -2158,34 +2154,30 @@ mod tests {
|
||||
attributes: vec![
|
||||
LdapPartialAttribute {
|
||||
atype: "objectclass".to_string(),
|
||||
vals: vec!["groupOfUniqueNames".to_string()],
|
||||
},
|
||||
LdapPartialAttribute {
|
||||
atype: "dn".to_string(),
|
||||
vals: vec!["cn=group_1,ou=groups,dc=example,dc=com".to_string()],
|
||||
vals: vec![b"groupOfUniqueNames".to_vec()],
|
||||
},
|
||||
// UID
|
||||
LdapPartialAttribute {
|
||||
atype: "uid".to_string(),
|
||||
vals: vec!["group_1".to_string()],
|
||||
vals: vec![b"group_1".to_vec()],
|
||||
},
|
||||
LdapPartialAttribute {
|
||||
atype: "cn".to_string(),
|
||||
vals: vec!["group_1".to_string()],
|
||||
vals: vec![b"group_1".to_vec()],
|
||||
},
|
||||
//member / uniquemember : "uid={},ou=people,{}"
|
||||
LdapPartialAttribute {
|
||||
atype: "member".to_string(),
|
||||
vals: vec![
|
||||
"uid=bob,ou=people,dc=example,dc=com".to_string(),
|
||||
"uid=john,ou=people,dc=example,dc=com".to_string(),
|
||||
b"uid=bob,ou=people,dc=example,dc=com".to_vec(),
|
||||
b"uid=john,ou=people,dc=example,dc=com".to_vec(),
|
||||
],
|
||||
},
|
||||
LdapPartialAttribute {
|
||||
atype: "uniquemember".to_string(),
|
||||
vals: vec![
|
||||
"uid=bob,ou=people,dc=example,dc=com".to_string(),
|
||||
"uid=john,ou=people,dc=example,dc=com".to_string(),
|
||||
b"uid=bob,ou=people,dc=example,dc=com".to_vec(),
|
||||
b"uid=john,ou=people,dc=example,dc=com".to_vec(),
|
||||
],
|
||||
},
|
||||
],
|
||||
@@ -2260,7 +2252,7 @@ mod tests {
|
||||
let request = make_user_search_request(
|
||||
LdapFilter::Substring(
|
||||
"uid".to_string(),
|
||||
ldap3_server::proto::LdapSubstringFilter::default(),
|
||||
ldap3_proto::proto::LdapSubstringFilter::default(),
|
||||
),
|
||||
vec!["objectClass"],
|
||||
);
|
||||
|
||||
@@ -8,10 +8,10 @@ use crate::{
|
||||
use actix_rt::net::TcpStream;
|
||||
use actix_server::ServerBuilder;
|
||||
use actix_service::{fn_service, ServiceFactoryExt};
|
||||
use anyhow::{Context, Result};
|
||||
use ldap3_server::{proto::LdapMsg, LdapCodec};
|
||||
use native_tls::{Identity, TlsAcceptor};
|
||||
use tokio_native_tls::TlsAcceptor as NativeTlsAcceptor;
|
||||
use anyhow::{anyhow, Context, Result};
|
||||
use ldap3_proto::{proto::LdapMsg, LdapCodec};
|
||||
use rustls::PrivateKey;
|
||||
use tokio_rustls::TlsAcceptor as RustlsTlsAcceptor;
|
||||
use tokio_util::codec::{FramedRead, FramedWrite};
|
||||
use tracing::{debug, error, info, instrument};
|
||||
|
||||
@@ -54,19 +54,6 @@ where
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
fn get_file_as_byte_vec(filename: &str) -> Result<Vec<u8>> {
|
||||
(|| -> Result<Vec<u8>> {
|
||||
use std::fs::{metadata, File};
|
||||
use std::io::Read;
|
||||
let mut f = File::open(&filename).context("file not found")?;
|
||||
let metadata = metadata(&filename).context("unable to read metadata")?;
|
||||
let mut buffer = vec![0; metadata.len() as usize];
|
||||
f.read(&mut buffer).context("buffer overflow")?;
|
||||
Ok(buffer)
|
||||
})()
|
||||
.context(format!("while reading file {}", filename))
|
||||
}
|
||||
|
||||
#[instrument(skip_all, level = "info", name = "LDAP session")]
|
||||
async fn handle_ldap_stream<Stream, Backend>(
|
||||
stream: Stream,
|
||||
@@ -103,12 +90,53 @@ where
|
||||
Ok(requests.into_inner().unsplit(resp.into_inner()))
|
||||
}
|
||||
|
||||
fn get_tls_acceptor(config: &Configuration) -> Result<NativeTlsAcceptor> {
|
||||
fn read_private_key(key_file: &str) -> Result<PrivateKey> {
|
||||
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)
|
||||
.and_then(|keys| {
|
||||
keys.into_iter()
|
||||
.next()
|
||||
.ok_or_else(|| anyhow!("No PKCS8 key"))
|
||||
})
|
||||
.or_else(|_| {
|
||||
rsa_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 PKCS1 key"))
|
||||
})
|
||||
})
|
||||
.with_context(|| {
|
||||
format!(
|
||||
"Cannot read either PKCS1 or PKCS8 private key from {}",
|
||||
key_file
|
||||
)
|
||||
})
|
||||
.map(rustls::PrivateKey)
|
||||
}
|
||||
|
||||
fn get_tls_acceptor(config: &Configuration) -> Result<RustlsTlsAcceptor> {
|
||||
use rustls::{Certificate, ServerConfig};
|
||||
use rustls_pemfile::certs;
|
||||
use std::{fs::File, io::BufReader};
|
||||
// Load TLS key and cert files
|
||||
let cert_file = get_file_as_byte_vec(&config.ldaps_options.cert_file)?;
|
||||
let key_file = get_file_as_byte_vec(&config.ldaps_options.key_file)?;
|
||||
let identity = Identity::from_pkcs8(&cert_file, &key_file)?;
|
||||
Ok(TlsAcceptor::new(identity)?.into())
|
||||
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(
|
||||
ServerConfig::builder()
|
||||
.with_safe_defaults()
|
||||
.with_no_client_auth()
|
||||
.with_single_cert(certs, private_key)?,
|
||||
);
|
||||
Ok(server_config.into())
|
||||
}
|
||||
|
||||
pub fn build_ldap_server<Backend>(
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::infra::configuration::MailOptions;
|
||||
use crate::infra::{cli::SmtpEncryption, configuration::MailOptions};
|
||||
use anyhow::Result;
|
||||
use lettre::{
|
||||
message::Mailbox, transport::smtp::authentication::Credentials, Message, SmtpTransport,
|
||||
@@ -26,9 +26,11 @@ fn send_email(to: Mailbox, subject: &str, body: String, options: &MailOptions) -
|
||||
options.user.clone(),
|
||||
options.password.unsecure().to_string(),
|
||||
);
|
||||
let mailer = SmtpTransport::relay(&options.server)?
|
||||
.credentials(creds)
|
||||
.build();
|
||||
let relay_factory = match options.smtp_encryption {
|
||||
SmtpEncryption::TLS => SmtpTransport::relay,
|
||||
SmtpEncryption::STARTTLS => SmtpTransport::starttls_relay,
|
||||
};
|
||||
let mailer = relay_factory(&options.server)?.credentials(creds).build();
|
||||
mailer.send(&email)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ async fn create_admin_user(handler: &SqlBackendHandler, config: &Configuration)
|
||||
handler
|
||||
.create_user(CreateUserRequest {
|
||||
user_id: config.ldap_user_dn.clone(),
|
||||
email: config.ldap_user_email.clone(),
|
||||
display_name: Some("Administrator".to_string()),
|
||||
..Default::default()
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user