| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634 |
- diff --git a/.gitignore b/.gitignore
- index 7e57cb3..8994b5d 100644
- --- a/.gitignore
- +++ b/.gitignore
- @@ -1,2 +1,3 @@
- data/
- -config/nextcloud/
- \ No newline at end of file
- +config/credentials
- +config/nextcloud/
- diff --git a/ansible/site.yml b/ansible/site.yml
- index 22ea670..ded0d95 100644
- --- a/ansible/site.yml
- +++ b/ansible/site.yml
- @@ -1,11 +1,22 @@
- -# automated-office/ansible/site.yml
- ---
- - name: Deploy Automated Office
- - hosts: all
- + hosts: localhost
- become: true
-
- + pre_tasks:
- + - debug:
- + msg:
- + - "Client secret defined: {{ client_secret is defined }}"
- + - "Client secret length: {{ client_secret | default('') | length }}"
- + verbosity: 1
- +
- + - name: "Verify required variables"
- + fail:
- + msg: "Client secret is not set or empty"
- + when: client_secret is not defined or client_secret | default('') | trim == ''
- +
- roles:
- - - common
- - - docker
- - - nginx
- - - services
- \ No newline at end of file
- + - role: common
- + - role: docker
- + - role: nginx
- + - role: services
- \ No newline at end of file
- diff --git a/config/credentials/credentials_2024-12-11_18-28-21.txt.gpg b/config/credentials/credentials_2024-12-11_18-28-21.txt.gpg
- deleted file mode 100644
- index b43471e..0000000
- Binary files a/config/credentials/credentials_2024-12-11_18-28-21.txt.gpg and /dev/null differ
- diff --git a/config/nginx/sites-available/keycloak b/config/nginx/sites-available/keycloak
- index 2c2181a..cf437a2 100644
- --- a/config/nginx/sites-available/keycloak
- +++ b/config/nginx/sites-available/keycloak
- @@ -5,7 +5,7 @@ upstream keycloak_upstream {
- server {
- listen 80;
- server_name auth.mrx8086.com;
- -
- +
- # Redirect HTTP to HTTPS
- return 301 https://$host$request_uri;
- }
- @@ -30,35 +30,49 @@ server {
- add_header X-XSS-Protection "1; mode=block" always;
- add_header X-Frame-Options SAMEORIGIN always;
- add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
- -
- +
- # Content Security Policy
- add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:; frame-src 'self'; frame-ancestors 'self'; connect-src 'self'" always;
-
- - # Proxy settings for all locations
- + # Proxy settings - Added X-Forwarded headers here to apply to all proxied locations
- proxy_set_header X-Real-IP $remote_addr;
- proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
- proxy_set_header X-Forwarded-Proto $scheme;
- + proxy_set_header X-Forwarded-Ssl on; # Optional, but explicit
- + proxy_set_header X-Forwarded-Port $server_port;
- proxy_set_header X-Forwarded-Host $host;
- - proxy_set_header X-Forwarded-Port 443;
- proxy_set_header Host $host;
- proxy_http_version 1.1;
- -
- +
- # Root location for the main application
- location / {
- proxy_pass http://keycloak_upstream;
- }
-
- + # Specific location for the token endpoint
- + location ~ ^/auth/realms/[^/]+/protocol/openid-connect/token$ {
- + proxy_pass http://keycloak_upstream;
- + proxy_buffer_size 128k;
- + proxy_buffers 4 256k;
- + proxy_busy_buffers_size 256k;
- + # WebSocket support (likely not needed for token endpoint, but keeping for consistency)
- + proxy_set_header Upgrade $http_upgrade;
- + proxy_set_header Connection "upgrade";
- + # Timeouts
- + proxy_connect_timeout 60s;
- + proxy_send_timeout 60s;
- + proxy_read_timeout 60s;
- + }
- +
- # Keycloak required paths
- location /realms/ {
- proxy_pass http://keycloak_upstream;
- proxy_buffer_size 128k;
- proxy_buffers 4 256k;
- proxy_busy_buffers_size 256k;
- -
- # WebSocket support
- proxy_set_header Upgrade $http_upgrade;
- proxy_set_header Connection "upgrade";
- -
- # Timeouts
- proxy_connect_timeout 60s;
- proxy_send_timeout 60s;
- @@ -67,7 +81,7 @@ server {
-
- location /resources/ {
- proxy_pass http://keycloak_upstream;
- -
- +
- # Cache settings for static resources
- proxy_cache_use_stale error timeout http_500 http_502 http_503 http_504;
- proxy_cache_valid 200 1d;
- diff --git a/config/nginx/sites-available/nextcloud b/config/nginx/sites-available/nextcloud
- index c384e10..8357df3 100644
- --- a/config/nginx/sites-available/nextcloud
- +++ b/config/nginx/sites-available/nextcloud
- @@ -1,12 +1,10 @@
- upstream nextcloud_upstream {
- - server 172.19.0.3:80; # Die IP wird später durch die tatsächliche Container-IP ersetzt
- + server 172.19.0.3:80; # SICHERSTELLEN, DASS DIES DIE KORREKTE IP IST
- }
-
- server {
- listen 80;
- server_name cloud.mrx8086.com;
- -
- - # Redirect HTTP to HTTPS
- return 301 https://$host$request_uri;
- }
-
- @@ -14,27 +12,19 @@ server {
- listen 443 ssl;
- server_name cloud.mrx8086.com;
-
- - # SSL Configuration
- + # SSL Configuration (wie zuvor)
- ssl_certificate /etc/nginx/ssl/mrx8086.com/fullchain.pem;
- ssl_certificate_key /etc/nginx/ssl/mrx8086.com/privkey.pem;
- - ssl_session_timeout 1d;
- - ssl_session_tickets off;
- + # ... weitere SSL-Einstellungen ...
-
- - # Modern SSL configuration
- - ssl_protocols TLSv1.2 TLSv1.3;
- - ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
- - ssl_prefer_server_ciphers off;
- -
- - # Security headers
- + # Security headers (wie zuvor)
- add_header X-Content-Type-Options nosniff always;
- add_header X-XSS-Protection "1; mode=block" always;
- add_header X-Frame-Options SAMEORIGIN always;
- add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
- -
- - # Content Security Policy für Nextcloud
- add_header Content-Security-Policy "frame-ancestors 'self'; default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; font-src 'self' data:; connect-src 'self'; media-src 'self';" always;
-
- - # Proxy settings
- + # Proxy settings (wie zuvor)
- proxy_set_header X-Real-IP $remote_addr;
- proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
- proxy_set_header X-Forwarded-Proto $scheme;
- @@ -43,31 +33,36 @@ server {
- proxy_set_header Host $host;
- proxy_http_version 1.1;
-
- - # Nextcloud specific settings
- + # Nextcloud specific settings (wie zuvor)
- client_max_body_size 512M;
- fastcgi_buffers 64 4K;
- -
- +
- + # Expliziter location-Block für den OpenID Connect Callback
- + location /apps/sociallogin/custom_oidc/keycloak {
- + proxy_pass http://nextcloud_upstream;
- + proxy_set_header Host $host;
- + proxy_set_header X-Real-IP $remote_addr;
- + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
- + proxy_set_header X-Forwarded-Proto $scheme;
- + }
- +
- # Root location
- location / {
- proxy_pass http://nextcloud_upstream;
- -
- - # WebSocket support
- proxy_set_header Upgrade $http_upgrade;
- proxy_set_header Connection "upgrade";
- -
- - # Timeouts
- proxy_connect_timeout 60s;
- proxy_send_timeout 60s;
- proxy_read_timeout 60s;
- }
-
- - # Block sensitive paths
- + # Block sensitive paths (wie zuvor)
- location ~ ^/(?:build|tests|config|lib|3rdparty|templates|data)(?:$|/) {
- deny all;
- return 404;
- }
-
- - # Deny access to hidden files
- + # Deny access to hidden files (wie zuvor)
- location ~ /\. {
- deny all;
- return 404;
- diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml
- index 0163334..ccbcd3b 100644
- --- a/docker/docker-compose.yml
- +++ b/docker/docker-compose.yml
- @@ -34,8 +34,10 @@ services:
- - keycloak-network
- depends_on:
- - keycloak-db
- + extra_hosts:
- + - "cloud.mrx8086.com:172.23.171.133"
- healthcheck:
- - test: ["CMD", "curl", "-f", "http://localhost:8080/health/ready"]
- + test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
- interval: 30s
- timeout: 10s
- retries: 3
- @@ -48,7 +50,7 @@ services:
- POSTGRES_USER: ${KC_DB_USERNAME}
- POSTGRES_PASSWORD: ${KC_DB_PASSWORD}
- volumes:
- - - ../data/keycloak/db:/var/lib/postgresql/data
- + - ../data/keycloak-db:/var/lib/postgresql/data
- networks:
- - keycloak-network
- restart: unless-stopped
- @@ -63,6 +65,13 @@ services:
- image: nextcloud:latest
- container_name: nextcloud
- restart: unless-stopped
- + ports:
- + - "8081:80"
- + volumes:
- + - ../data/nextcloud:/var/www/html
- + - ../config/nextcloud/config:/var/www/html/config
- + - ../config/nextcloud/custom_apps:/var/www/html/custom_apps
- + - ../data/nextcloud-db:/var/lib/mysql
- environment:
- - MYSQL_HOST=nextcloud-db
- - MYSQL_DATABASE=nextcloud
- @@ -74,16 +83,24 @@ services:
- - OVERWRITEPROTOCOL=https
- - OVERWRITEHOST=cloud.mrx8086.com
- - OVERWRITEWEBROOT=/
- - - TRUSTED_PROXIES=172.18.0.0/16
- - volumes:
- - - ../data/nextcloud:/var/www/html
- - - ../config/nextcloud/config:/var/www/html/config
- - - ../config/nextcloud/custom_apps:/var/www/html/custom_apps
- - - ../config/nextcloud/data:/var/www/html/data
- + - TRUSTED_PROXIES=172.19.0.0/16
- + - NEXTCLOUD_URL=https://cloud.mrx8086.com
- + - NEXTCLOUD_DEBUG=1
- + - NEXTCLOUD_CONFIG_CUSTOM_SCOPE="openid profile groups-nextcloud"
- + healthcheck:
- + test: ["CMD", "curl", "-f", "http://localhost:80/"]
- + interval: 30s
- + timeout: 10s
- + retries: 3
- networks:
- - nextcloud-network
- depends_on:
- - nextcloud-db
- + extra_hosts:
- + - "auth.mrx8086.com:172.23.171.133"
- + dns:
- + - 8.8.8.8
- + - 8.8.4.4
-
- nextcloud-db:
- image: mariadb:10.6
- diff --git a/docs/context/scripts/setup_environment.sh b/docs/context/scripts/setup_environment.sh
- index 9a1b2da..1244eb8 100644
- --- a/docs/context/scripts/setup_environment.sh
- +++ b/docs/context/scripts/setup_environment.sh
- @@ -1,4 +1,5 @@
- #!/bin/bash
- +set -e
-
- # Ensure we're in the project root directory
- PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
- @@ -8,7 +9,8 @@ cd "${PROJECT_ROOT}"
- CREDENTIALS_DIR="config/credentials"
- DOCKER_DIR="docker"
- KEYCLOAK_SCRIPTS_DIR="scripts/setup/keycloak"
- -
- +ANSIBLE_PLAYBOOK="ansible/site.yml"
- +ANSIBLE_INVENTORY="ansible/inventory/staging/hosts"
-
- # Create necessary directories
- mkdir -p "${CREDENTIALS_DIR}"
- @@ -33,7 +35,7 @@ NEXTCLOUD_DB_USER=$(generate_password)
- NEXTCLOUD_DB_PASSWORD=$(generate_password)
- NEXTCLOUD_ADMIN_USER=$(generate_password)
- NEXTCLOUD_ADMIN_PASSWORD=$(generate_password)
- -
- +KEYCLOAK_NEXTCLOUD_CLIENT_SECRET=$(generate_password)
-
- # Create .env file in docker directory
- cat > "${DOCKER_DIR}/.env" << EOL
- @@ -64,6 +66,7 @@ PAPERLESS_CLIENT_ID=paperless
- NODERED_CLIENT_ID=nodered
- TESTADMIN_PASSWORD=${TESTADMIN_PASSWORD}
- TESTUSER_PASSWORD=${TESTUSER_PASSWORD}
- +KEYCLOAK_NEXTCLOUD_CLIENT_SECRET=${KEYCLOAK_NEXTCLOUD_CLIENT_SECRET}
- EOL
-
- # Create encrypted credentials documentation
- @@ -77,8 +80,10 @@ Password: ${KEYCLOAK_ADMIN_PASSWORD}
- Keycloak Database Credentials:
- Username: keycloak
- Password: ${KC_DB_PASSWORD}
- +
- Test Admin Credentials:
- Password: ${TESTADMIN_PASSWORD}
- +
- Test User Credentials:
- Password: ${TESTUSER_PASSWORD}
-
- @@ -86,6 +91,7 @@ Nextcloud Database Credentials:
- Root Password: ${NEXTCLOUD_DB_ROOT_PASSWORD}
- User: ${NEXTCLOUD_DB_USER}
- Password: ${NEXTCLOUD_DB_PASSWORD}
- +
- Nextcloud Admin Credentials:
- Username: ${NEXTCLOUD_ADMIN_USER}
- Password: ${NEXTCLOUD_ADMIN_PASSWORD}
- @@ -101,4 +107,40 @@ echo ".env file for docker-compose has been created in: ${DOCKER_DIR}/.env"
- echo ".env file for setup_realm.js has been created in: ${KEYCLOAK_SCRIPTS_DIR}/.env"
- echo ""
- echo "To view credentials, use:"
- -echo "gpg -d ${CREDENTIALS_DIR}/credentials_${SETUP_DATE}.txt.gpg"
- \ No newline at end of file
- +echo "gpg -d ${CREDENTIALS_DIR}/credentials_${SETUP_DATE}.txt.gpg"
- +
- +echo ">>> Nextcloud Konfiguration..."
- +
- +# Verify if variable is set from earlier in the script
- +echo ">>> Debug: Checking original variable..."
- +echo ">>> Debug: KEYCLOAK_NEXTCLOUD_CLIENT_SECRET = ${KEYCLOAK_NEXTCLOUD_CLIENT_SECRET}"
- +
- +# Try reading from .env file if variable is empty
- +if [ -z "${KEYCLOAK_NEXTCLOUD_CLIENT_SECRET}" ]; then
- + echo ">>> Debug: Variable is empty, trying to read from .env file..."
- + KEYCLOAK_NEXTCLOUD_CLIENT_SECRET=$(grep KEYCLOAK_NEXTCLOUD_CLIENT_SECRET "${KEYCLOAK_SCRIPTS_DIR}/.env" | cut -d '=' -f2)
- + echo ">>> Debug: Value from .env file = ${KEYCLOAK_NEXTCLOUD_CLIENT_SECRET}"
- +fi
- +
- +# Ensure we have a value
- +if [ -z "${KEYCLOAK_NEXTCLOUD_CLIENT_SECRET}" ]; then
- + echo ">>> Error: Could not get client secret value"
- + exit 1
- +fi
- +
- +# Escape special characters in the secret for JSON
- +ESCAPED_SECRET=$(echo "$KEYCLOAK_NEXTCLOUD_CLIENT_SECRET" | sed 's/["\]/\\&/g')
- +echo ">>> Debug: Escaped secret = $ESCAPED_SECRET"
- +
- +# Create the extra vars
- +EXTRA_VARS="{\"client_secret\": \"$ESCAPED_SECRET\"}"
- +echo ">>> Debug: Extra vars = $EXTRA_VARS"
- +
- +# Run Ansible with the extra vars
- +sudo ansible-playbook \
- + -i "$ANSIBLE_INVENTORY" \
- + "$ANSIBLE_PLAYBOOK" \
- + --extra-vars "$EXTRA_VARS" \
- + -v
- +
- +echo ">>> Fertig"
- \ No newline at end of file
- diff --git a/scripts/install/setup_environment.sh b/scripts/install/setup_environment.sh
- old mode 100644
- new mode 100755
- index aba63dc..5b7bc0e
- --- a/scripts/install/setup_environment.sh
- +++ b/scripts/install/setup_environment.sh
- @@ -1,31 +1,163 @@
- #!/bin/bash
- +set -e
-
- # Ensure we're in the project root directory
- PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
- -cd "${PROJECT_ROOT}"
-
- # Define directories relative to project root
- -CREDENTIALS_DIR="config/credentials"
- -DOCKER_DIR="docker"
- +CREDENTIALS_DIR="${PROJECT_ROOT}/config/credentials"
- +DOCKER_DIR="${PROJECT_ROOT}/docker"
- +KEYCLOAK_SETUP_DIR="${PROJECT_ROOT}/scripts/setup/keycloak"
- +ANSIBLE_PLAYBOOK="${PROJECT_ROOT}/ansible/site.yml"
- +ANSIBLE_INVENTORY="${PROJECT_ROOT}/ansible/inventory/staging/hosts"
- +NEXTCLOUD_DATA_DIR="${PROJECT_ROOT}/data/nextcloud/data"
- +TEMP_FILE=$(mktemp)
- +KEYCLOAK_DB_DIR="${PROJECT_ROOT}/data/keycloak-db"
-
- # Create necessary directories
- -mkdir -p "${CREDENTIALS_DIR}"
- -mkdir -p "${DOCKER_DIR}"
- +sudo mkdir -p "${CREDENTIALS_DIR}"
- +sudo mkdir -p "${DOCKER_DIR}"
- +sudo mkdir -p "${KEYCLOAK_SETUP_DIR}"
- +
- +# Initialize password variables
- +KEYCLOAK_ADMIN_PASSWORD=""
- +KC_DB_PASSWORD=""
- +TESTADMIN_PASSWORD=""
- +TESTUSER_PASSWORD=""
- +TESTSERVICEUSER_PASSWORD=""
- +KEYCLOAK_NEXTCLOUD_CLIENT_SECRET=""
- +
- +# Function to read a password from a .env file
- +read_password_from_env() {
- + local env_file="$1"
- + local variable_name="$2"
- + if [ -f "$env_file" ]; then
- + grep "^${variable_name}=" "$env_file" | cut -d '=' -f2
- + fi
- +}
-
- # Function to generate secure passwords
- generate_password() {
- - openssl rand -base64 24 | tr -dc 'a-zA-Z0-9' | head -c 24
- + openssl rand -base64 32
- +}
- +
- +# Function to generate password if empty
- +generate_password_if_empty() {
- + local variable_name="$1"
- + eval "local value=\$$variable_name"
- + if [ -z "$value" ]; then
- + eval "$variable_name=\"$(generate_password)\""
- + echo ">>> Generiertes Passwort für: $variable_name"
- + fi
- +}
- +
- +# Function to create .env file
- +create_env_file() {
- + local env_file="$1"
- + local content="$2"
- + if [ ! -f "$env_file" ]; then
- + echo "$content" > "$env_file"
- + echo ">>> .env file created: $env_file"
- + else
- + echo ">>> .env file already exists: $env_file"
- + fi
- }
-
- +echo ">>> Überprüfe bestehende .env Dateien und lese Passwörter..."
- +
- +# Try reading passwords from existing .env files
- +if [ -f "$DOCKER_DIR/.env" ]; then
- + KC_DB_PASSWORD=$(read_password_from_env "$DOCKER_DIR/.env" "KC_DB_PASSWORD")
- + KEYCLOAK_ADMIN_PASSWORD=$(read_password_from_env "$DOCKER_DIR/.env" "KEYCLOAK_ADMIN_PASSWORD")
- +fi
- +
- +if [ -f "$KEYCLOAK_SETUP_DIR/.env" ]; then
- + KEYCLOAK_ADMIN_PASSWORD=$(read_password_from_env "$KEYCLOAK_SETUP_DIR/.env" "KEYCLOAK_ADMIN_PASSWORD") # Überschreibt ggf. den Wert aus docker/.env
- + TESTADMIN_PASSWORD=$(read_password_from_env "$KEYCLOAK_SETUP_DIR/.env" "TESTADMIN_PASSWORD")
- + TESTUSER_PASSWORD=$(read_password_from_env "$KEYCLOAK_SETUP_DIR/.env" "TESTUSER_PASSWORD")
- + TESTSERVICEUSER_PASSWORD=$(read_password_from_env "$KEYCLOAK_SETUP_DIR/.env" "TESTSERVICEUSER_PASSWORD")
- + KEYCLOAK_NEXTCLOUD_CLIENT_SECRET=$(read_password_from_env "$KEYCLOAK_SETUP_DIR/.env" "KEYCLOAK_NEXTCLOUD_CLIENT_SECRET")
- +fi
- +
- +echo ">>> Generiere neue Passwörter für fehlende Werte..."
- +
- +# Generate passwords if they are still empty
- +generate_password_if_empty KEYCLOAK_ADMIN_PASSWORD
- +generate_password_if_empty KC_DB_PASSWORD
- +generate_password_if_empty TESTADMIN_PASSWORD
- +generate_password_if_empty TESTUSER_PASSWORD
- +generate_password_if_empty TESTSERVICEUSER_PASSWORD
- +generate_password_if_empty KEYCLOAK_NEXTCLOUD_CLIENT_SECRET
- +
- # Date for documentation
- SETUP_DATE=$(date '+%Y-%m-%d_%H-%M-%S')
-
- -# Generate passwords
- -KEYCLOAK_ADMIN_PASSWORD=$(generate_password)
- -KC_DB_PASSWORD=$(generate_password)
- +# Create credentials content
- +CREDENTIALS_CONTENT=$(cat <<EOL
- +Setup Date: ${SETUP_DATE}
- +
- +Keycloak Admin Credentials:
- +Username: admin
- +Password: ${KEYCLOAK_ADMIN_PASSWORD}
- +
- +Keycloak Database Credentials:
- +Username: keycloak
- +Password: ${KC_DB_PASSWORD}
- +
- +Test User Credentials:
- +Admin Password: ${TESTADMIN_PASSWORD}
- +User Password: ${TESTUSER_PASSWORD}
- +Service User Password: ${TESTSERVICEUSER_PASSWORD}
- +Nextcloud Client Secret: ${KEYCLOAK_NEXTCLOUD_CLIENT_SECRET}
- +
- +EOL
- +)
- +
- +# Store credentials hash
- +CREDENTIALS_HASH=$(echo "$CREDENTIALS_CONTENT" | sha256sum | awk '{print $1}')
- +echo "$CREDENTIALS_HASH" > "${CREDENTIALS_DIR}/credentials_hash.txt"
- +echo ">>> Credentials hash stored in: ${CREDENTIALS_DIR}/credentials_hash.txt"
- +
- +# Set GPG PASSPHRASE
- +export GPG_PASSPHRASE=$(generate_password)
- +# Set GPG agent environment variable
- +export GPG_TTY=$(tty)
- +
- +echo ">>> Trying openssl encryption first"
- +# Alternative Verschlüsselung mit Openssl
- +echo "$CREDENTIALS_CONTENT" > "$TEMP_FILE"
- + if openssl enc -aes-256-cbc -pbkdf2 -salt -in "$TEMP_FILE" -out "${CREDENTIALS_DIR}/credentials_${SETUP_DATE}.txt.enc" -k "$GPG_PASSPHRASE" ; then
- + echo ">>> Credentials encrypted successfully using openssl"
- + mv "${CREDENTIALS_DIR}/credentials_${SETUP_DATE}.txt.enc" "${CREDENTIALS_DIR}/credentials_${SETUP_DATE}.txt.gpg"
- + else
- + echo ">>> Openssl encryption failed, trying gpg"
- +
- + # Attempt to kill existing gpg agent
- + gpgconf --kill gpg-agent 2>/dev/null
- + echo ">>> Attempting to manually start gpg-agent with pinentry-curses"
- + gpg-agent --daemon --pinentry-program /usr/bin/pinentry-curses
- + gpg-connect-agent /bye 2>/dev/null
- + eval $(gpg-agent --daemon)
- + gpg-connect-agent updatestartuptty /bye 2>/dev/null
- +
- + # Attempt to encrypt credentials using GPG with error handling
- + if echo "$CREDENTIALS_CONTENT" | gpg --symmetric --cipher-algo AES256 -vvv -o "${CREDENTIALS_DIR}/credentials_${SETUP_DATE}.txt.gpg" ; then
- + echo ">>> Credentials encrypted successfully using gpg."
- + else
- + echo ">>> GPG encryption failed. Attempting GPG encryption with password workaround."
- + # Attempt encryption with passphrase workaround
- + if echo "$CREDENTIALS_CONTENT" | gpg --batch --passphrase "$GPG_PASSPHRASE" --symmetric --cipher-algo AES256 -vvv -o "${CREDENTIALS_DIR}/credentials_${SETUP_DATE}.txt.gpg"; then
- + echo ">>> Credentials encrypted successfully using gpg with passphrase workaround."
- + else
- + echo ">>> GPG encryption with passphrase workaround failed"
- + exit 1
- + fi
- + fi
- + fi
- +rm "$TEMP_FILE"
-
- # Create .env file in docker directory
- -cat > "${DOCKER_DIR}/.env" << EOL
- +DOCKER_ENV_CONTENT=$(cat <<EOL
- # Generated on ${SETUP_DATE}
- # Keycloak Admin
- KEYCLOAK_ADMIN_PASSWORD=${KEYCLOAK_ADMIN_PASSWORD}
- @@ -34,27 +166,69 @@ KEYCLOAK_ADMIN_PASSWORD=${KEYCLOAK_ADMIN_PASSWORD}
- KC_DB_USERNAME=keycloak
- KC_DB_PASSWORD=${KC_DB_PASSWORD}
- EOL
- +)
- +create_env_file "$DOCKER_DIR/.env" "$DOCKER_ENV_CONTENT"
-
- -# Create encrypted credentials documentation
- -cat > "${CREDENTIALS_DIR}/credentials_${SETUP_DATE}.txt" << EOL
- -Setup Date: ${SETUP_DATE}
- +# Create .env file in scripts/setup/keycloak directory
- +KEYCLOAK_ENV_CONTENT=$(cat <<EOL
- +KEYCLOAK_URL=https://auth.mrx8086.com
- +KEYCLOAK_ADMIN_USER=admin
- +KEYCLOAK_ADMIN_PASSWORD=${KEYCLOAK_ADMIN_PASSWORD}
- +NEXTCLOUD_CLIENT_ID=nextcloud
- +PAPERLESS_CLIENT_ID=paperless
- +NODERED_CLIENT_ID=nodered
- +TESTADMIN_PASSWORD=${TESTADMIN_PASSWORD}
- +TESTUSER_PASSWORD=${TESTUSER_PASSWORD}
- +TESTSERVICEUSER_PASSWORD=${TESTSERVICEUSER_PASSWORD}
- +KEYCLOAK_NEXTCLOUD_CLIENT_SECRET=${KEYCLOAK_NEXTCLOUD_CLIENT_SECRET}
- +EOL
- +)
- +create_env_file "$KEYCLOAK_SETUP_DIR/.env" "$KEYCLOAK_ENV_CONTENT"
-
- -Keycloak Admin Credentials:
- -Username: admin
- -Password: ${KEYCLOAK_ADMIN_PASSWORD}
- +echo ">>> Environment setup completed!"
-
- -Keycloak Database Credentials:
- -Username: keycloak
- -Password: ${KC_DB_PASSWORD}
- -EOL
- +# --------------- KEYCLOAK KONFIGURATION ---------------
- +echo ">>> Keycloak Konfiguration..."
- +cd "$KEYCLOAK_SETUP_DIR"
- +
- +echo ">>> Starte setup_realm.js"
- +node setup_realm.js
- +
- +cd "$PROJECT_ROOT"
- +
- +# --------------- NEXTCLOUD KONFIGURATION ---------------
- +echo ">>> Nextcloud Konfiguration..."
- +
- +# Verify if variable is set from earlier in the script
- +echo ">>> Debug: Checking original variable..."
- +echo ">>> Debug: KEYCLOAK_NEXTCLOUD_CLIENT_SECRET = ${KEYCLOAK_NEXTCLOUD_CLIENT_SECRET}"
- +
- +# Try reading from .env file if variable is empty
- +if [ -z "${KEYCLOAK_NEXTCLOUD_CLIENT_SECRET}" ]; then
- + echo ">>> Debug: Variable is empty, trying to read from .env file..."
- + KEYCLOAK_NEXTCLOUD_CLIENT_SECRET=$(grep KEYCLOAK_NEXTCLOUD_CLIENT_SECRET "${KEYCLOAK_SETUP_DIR}/.env" | cut -d '=' -f2)
- + echo ">>> Debug: Value from .env file = ${KEYCLOAK_NEXTCLOUD_CLIENT_SECRET}"
- +fi
- +
- +# Ensure we have a value
- +if [ -z "${KEYCLOAK_NEXTCLOUD_CLIENT_SECRET}" ]; then
- + echo ">>> Error: Could not get client secret value"
- + exit 1
- +fi
- +
- +# Escape special characters in the secret for JSON
- +ESCAPED_SECRET=$(echo "$KEYCLOAK_NEXTCLOUD_CLIENT_SECRET" | sed 's/["\]/\\&/g')
- +echo ">>> Debug: Escaped secret = $ESCAPED_SECRET"
- +
- +# Create the extra vars
- +EXTRA_VARS="{\"client_secret\": \"$ESCAPED_SECRET\"}"
- +echo ">>> Debug: Extra vars = $EXTRA_VARS"
-
- -# Encrypt credentials file
- -gpg --symmetric --cipher-algo AES256 "${CREDENTIALS_DIR}/credentials_${SETUP_DATE}.txt"
- -rm "${CREDENTIALS_DIR}/credentials_${SETUP_DATE}.txt"
- +# Run Ansible with the extra vars
- +sudo ansible-playbook \
- + -i "$ANSIBLE_INVENTORY" \
- + "$ANSIBLE_PLAYBOOK" \
- + --extra-vars "$EXTRA_VARS" \
- + -v
-
- -echo "Environment setup completed!"
- -echo "Credentials have been saved and encrypted in: ${CREDENTIALS_DIR}/credentials_${SETUP_DATE}.txt.gpg"
- -echo ".env file has been created in: ${DOCKER_DIR}/.env"
- -echo ""
- -echo "To view credentials, use:"
- -echo "gpg -d ${CREDENTIALS_DIR}/credentials_${SETUP_DATE}.txt.gpg"
- \ No newline at end of file
- +echo ">>> Fertig"
- \ No newline at end of file
- diff --git a/scripts/setup/keycloak/.env b/scripts/setup/keycloak/.env
- index 8c6b3c3..0087b01 100644
- --- a/scripts/setup/keycloak/.env
- +++ b/scripts/setup/keycloak/.env
- @@ -6,4 +6,6 @@ PAPERLESS_CLIENT_ID=paperless
- NODERED_CLIENT_ID=nodered
- TESTADMIN_PASSWORD=TestAdminPwd
- TESTUSER_PASSWORD=TestUserPwd
- -KEYCLOAK_NEXTCLOUD_CLIENT_SECRET=D939xgzoxi58T2XZShdUPZP4gsI0kBOu
- \ No newline at end of file
- +TESTSERVICEUSER_PASSWORD=TestServiceUserPwd
- +KEYCLOAK_NEXTCLOUD_CLIENT_SECRET=OSbJ08zyjBWChwBR7S6c1q4sU0d8zvEK
- +NEXTCLOUD_URL=https://cloud.mrx8086.com
- \ No newline at end of file
- diff --git a/scripts/setup/keycloak/setup_realm.js b/scripts/setup/keycloak/setup_realm.js
- index 9f67b1e..784d543 100644
- --- a/scripts/setup/keycloak/setup_realm.js
- +++ b/scripts/setup/keycloak/setup_realm.js
- @@ -1,19 +1,26 @@
- import dotenv from 'dotenv';
- import axios from 'axios';
-
- -// Lade Umgebungsvariablen
- +// Load environment variables
- dotenv.config();
- +console.log('Environment variables loaded.');
-
- -// Konfigurationskonstanten
- +// Configuration constants
- const KEYCLOAK_URL = process.env.KEYCLOAK_URL || 'https://auth.mrx8086.com';
- const ADMIN_USERNAME = process.env.KEYCLOAK_ADMIN_USER;
- const ADMIN_PASSWORD = process.env.KEYCLOAK_ADMIN_PASSWORD;
- const REALM_NAME = 'office-automation';
-
- -// Client IDs und deren Konfiguration
- +console.log('Configuration constants set:', { KEYCLOAK_URL, ADMIN_USERNAME, REALM_NAME });
- +
- +// Client IDs and their configuration
- const CLIENTS = {
- [process.env.NEXTCLOUD_CLIENT_ID || 'nextcloud']: {
- - redirectUris: ["https://cloud.mrx8086.com/*"]
- + redirectUris: [
- + `https://cloud.mrx8086.com/apps/sociallogin/custom_oidc/keycloak`,
- + `https://cloud.mrx8086.com/apps/user_oidc/code`
- + ],
- + postLogoutRedirectUris: ["https://cloud.mrx8086.com/*"]
- },
- [process.env.PAPERLESS_CLIENT_ID || 'paperless']: {
- redirectUris: ["https://docs.mrx8086.com/*"]
- @@ -23,16 +30,18 @@ const CLIENTS = {
- }
- };
-
- -// Hilfsfunktion für API-Fehlerbehandlung
- +console.log('CLIENTS configuration:', CLIENTS);
- +
- +// Helper function for API error handling
- const handleAxiosError = (error, operation, config, response) => {
- console.error(`Error during ${operation}:`);
- if (config) {
- - console.error('Request:', {
- - method: config.method,
- - url: config.url,
- - headers: config.headers,
- - data: config.data,
- - });
- + console.error('Request:', {
- + method: config.method,
- + url: config.url,
- + headers: config.headers,
- + data: config.data,
- + });
- }
- if (error.response) {
- console.error('Response:', {
- @@ -45,8 +54,9 @@ const handleAxiosError = (error, operation, config, response) => {
- throw error;
- };
-
- -// Admin Token abrufen
- +// Get Admin Token
- async function getAdminToken() {
- + console.log('Getting admin token...');
- try {
- const response = await axios.post(
- `${KEYCLOAK_URL}/realms/master/protocol/openid-connect/token`,
- @@ -62,165 +72,146 @@ async function getAdminToken() {
- }
- }
- );
- + console.log('Admin token received.');
- return response.data.access_token;
- } catch (error) {
- - handleAxiosError(error, 'getting admin token');
- + handleAxiosError(error, 'getting admin token');
- }
- }
-
- -// Prüfen ob Realm existiert
- +// Check if Realm exists
- async function checkRealmExists(token) {
- + console.log(`Checking if realm ${REALM_NAME} exists...`);
- try {
- - await axios.get(
- - `${KEYCLOAK_URL}/admin/realms/${REALM_NAME}`,
- - {
- - headers: {
- - 'Authorization': `Bearer ${token}`
- - }
- - }
- - );
- + await axios.get(`${KEYCLOAK_URL}/admin/realms/${REALM_NAME}`, {
- + headers: { 'Authorization': `Bearer ${token}` }
- + });
- + console.log(`Realm ${REALM_NAME} exists.`);
- return true;
- } catch (error) {
- if (error.response?.status === 404) {
- + console.log(`Realm ${REALM_NAME} does not exist.`);
- return false;
- }
- handleAxiosError(error, 'checking realm existence');
- }
- }
-
- -
- -// Funktion um Client Infos abzufragen
- -async function getClient(token, clientId) {
- +// Function to get client information by clientId
- +async function getClientByClientId(token, clientId) {
- + console.log(`Getting client information for ${clientId}...`);
- try {
- - const response = await axios.get(
- - `${KEYCLOAK_URL}/admin/realms/${REALM_NAME}/clients`,
- - {
- - headers: {
- - 'Authorization': `Bearer ${token}`
- - },
- - params: {
- - clientId: clientId
- - }
- - }
- - );
- + const response = await axios.get(`${KEYCLOAK_URL}/admin/realms/${REALM_NAME}/clients`, {
- + headers: { 'Authorization': `Bearer ${token}` },
- + params: { clientId }
- + });
- if (response.data.length === 0) {
- - console.error(`Client ${clientId} not found`);
- + console.log(`Client ${clientId} not found`);
- return null;
- }
- -
- + console.log(`Client ${clientId} found.`);
- return response.data[0];
- } catch (error) {
- - handleAxiosError(error, `getting client ${clientId}`);
- + handleAxiosError(error, `getting client ${clientId}`);
- + return null;
- }
- }
-
- -// Prüfen ob Client existiert
- -async function checkClientExists(token, clientId) {
- - const client = await getClient(token, clientId);
- - return !!client;
- -}
- -
- +// Check if client exists
- +const checkClientExists = async (token, clientId) => !!await getClientByClientId(token, clientId);
-
- +// Get client mappers by client ID
- async function getClientMappers(token, clientId) {
- + console.log(`Getting client mappers for ${clientId}...`);
- + const client = await getClientByClientId(token, clientId);
- + if (!client) {
- + console.log(`Client ${clientId} not found, no mappers to get.`);
- + return [];
- + }
- try {
- - const client = await getClient(token, clientId);
- - if (!client) {
- - return [];
- - }
- const response = await axios.get(
- `${KEYCLOAK_URL}/admin/realms/${REALM_NAME}/clients/${client.id}/protocol-mappers/models`,
- - {
- - headers: {
- - 'Authorization': `Bearer ${token}`
- - }
- - }
- + { headers: { 'Authorization': `Bearer ${token}` } }
- );
- + console.log(`Client mappers for ${clientId} retrieved.`);
- return response.data;
- } catch (error) {
- - handleAxiosError(error, `getting client mappers for ${clientId}`, error.config, error.response);
- + handleAxiosError(error, `getting client mappers for ${clientId}`, error.config, error.response);
- return [];
- }
- }
-
- -async function getClientScopes(token, clientId){
- +// Get client scopes for a client
- +async function getClientScopes(token, clientId) {
- + console.log(`Getting client scopes for ${clientId}...`);
- + const client = await getClientByClientId(token, clientId);
- + if (!client) {
- + console.log(`Client ${clientId} not found, no client scopes to get.`);
- + return [];
- + }
- try {
- - const client = await getClient(token, clientId);
- - if(!client)
- - return [];
- -
- const response = await axios.get(
- `${KEYCLOAK_URL}/admin/realms/${REALM_NAME}/clients/${client.id}/client-scopes`,
- - {
- - headers: {
- - 'Authorization': `Bearer ${token}`
- - }
- - }
- + { headers: { 'Authorization': `Bearer ${token}` } }
- );
- -
- + console.log(`Client scopes for ${clientId} retrieved.`);
- return response.data;
- -
- - } catch(error){
- - handleAxiosError(error, `getting client scopes for ${clientId}`, error.config, error.response);
- + } catch (error) {
- + handleAxiosError(error, `getting client scopes for ${clientId}`, error.config, error.response);
- return [];
- }
- }
-
- +// Get a specific client scope by name
- async function getClientScope(token, scopeName) {
- + console.log(`Getting client scope "${scopeName}"...`);
- try {
- const response = await axios.get(
- `${KEYCLOAK_URL}/admin/realms/${REALM_NAME}/client-scopes`,
- - {
- - headers: {
- - 'Authorization': `Bearer ${token}`
- - },
- - params: {
- - name: scopeName
- - }
- - }
- + { headers: { 'Authorization': `Bearer ${token}` } }
- );
- -
- - if(response.data.length === 0){
- - console.error(`Client Scope ${scopeName} not found`);
- + const foundScope = response.data.find(scope => scope.name === scopeName);
- + if (!foundScope) {
- + console.log(`Client Scope "${scopeName}" not found`);
- return null;
- }
- -
- - return response.data[0]
- - } catch (error){
- + console.log(`Client scope "${scopeName}" found:`, foundScope);
- + return foundScope;
- + } catch (error) {
- handleAxiosError(error, `getting client scope ${scopeName}`, error.config, error.response);
- return null;
- }
- }
-
- -async function addDefaultClientScope(token, clientId, scopeName){
- +// Add a default client scope to a client
- +async function addDefaultClientScope(token, clientId, scopeName) {
- + console.log(`Adding client scope "${scopeName}" as default for client "${clientId}"...`);
- + const client = await getClientByClientId(token, clientId);
- + const scope = await getClientScope(token, scopeName);
- + if (!client || !scope) {
- + console.log(`Client "${clientId}" or scope "${scopeName}" not found, cannot add as default scope.`);
- + return;
- + }
- try {
- - const client = await getClient(token, clientId);
- - const scope = await getClientScope(token, scopeName);
- - if(!client || !scope){
- - return null;
- - }
- -
- -
- await axios.put(
- - `${KEYCLOAK_URL}/admin/realms/${REALM_NAME}/clients/${client.id}/default-client-scopes/${scope.id}`,
- - null,
- - {
- - headers: {
- - 'Authorization': `Bearer ${token}`,
- - 'Content-Type': 'application/json'
- - }
- - }
- + `${KEYCLOAK_URL}/admin/realms/${REALM_NAME}/clients/${client.id}/default-client-scopes/${scope.id}`,
- + null,
- + {
- + headers: {
- + 'Authorization': `Bearer ${token}`,
- + 'Content-Type': 'application/json'
- + }
- + }
- );
- -
- - console.log(`Client scope ${scopeName} added as default scope for client ${clientId}`)
- -
- - } catch(error){
- - handleAxiosError(error, `adding client scope ${scopeName} as default for client ${clientId}`);
- + console.log(`Client scope "${scopeName}" added as default scope for client "${clientId}"`);
- + } catch (error) {
- + handleAxiosError(error, `adding client scope "${scopeName}" as default for client "${clientId}"`);
- }
- }
-
- -
- -// Realm erstellen
- +// Create Realm
- async function createRealm(token) {
- + console.log(`Creating realm ${REALM_NAME}...`);
- const realmConfig = {
- realm: REALM_NAME,
- enabled: true,
- @@ -250,67 +241,47 @@ async function createRealm(token) {
- webAuthnPolicyUserVerificationRequirement: "preferred",
- webAuthnPolicyCreateTimeout: 0,
- webAuthnPolicyAvoidSameAuthenticatorRegister: false,
- - defaultDefaultClientScopes: [
- - "email",
- - "profile",
- - "roles",
- - "web-origins"
- - ],
- - defaultOptionalClientScopes: [
- - "address",
- - "phone",
- - "offline_access",
- - "microprofile-jwt"
- - ]
- + defaultDefaultClientScopes: ["email", "profile", "roles", "web-origins"],
- + defaultOptionalClientScopes: ["address", "phone", "offline_access", "microprofile-jwt"]
- };
- -
- try {
- - await axios.post(
- - `${KEYCLOAK_URL}/admin/realms`,
- - realmConfig,
- - {
- - headers: {
- - 'Authorization': `Bearer ${token}`,
- - 'Content-Type': 'application/json'
- - }
- + await axios.post(`${KEYCLOAK_URL}/admin/realms`, realmConfig, {
- + headers: {
- + 'Authorization': `Bearer ${token}`,
- + 'Content-Type': 'application/json'
- }
- - );
- + });
- console.log('Realm created successfully');
- } catch (error) {
- handleAxiosError(error, 'creating realm');
- }
- }
-
- -// Client erstellen
- +// Create client and manage mappers
- async function createClient(token, clientId, clientName, redirectUris) {
- - let client;
- - const clientExists = await checkClientExists(token, clientId);
- + console.log(`Creating client "${clientId}"...`);
- + let client = await getClientByClientId(token, clientId);
-
- - if (!clientExists) {
- + if (!client) {
- const clientConfig = {
- clientId: clientId,
- name: clientName,
- enabled: true,
- protocol: "openid-connect",
- publicClient: false,
- - authorizationServicesEnabled: true,
- - serviceAccountsEnabled: true,
- + authorizationServicesEnabled: false,
- + serviceAccountsEnabled: false,
- standardFlowEnabled: true,
- implicitFlowEnabled: false,
- directAccessGrantsEnabled: true,
- redirectUris: redirectUris,
- - webOrigins: ["+"],
- - defaultClientScopes: [
- - "roles"
- - ],
- - optionalClientScopes: [
- - "address",
- - "phone",
- - "offline_access",
- - "microprofile-jwt"
- - ]
- + webOrigins: ["+"],
- + defaultClientScopes: ["roles"],
- + optionalClientScopes: ["address", "phone", "offline_access", "microprofile-jwt"],
- + rootUrl: process.env.NEXTCLOUD_URL,
- + baseUrl: process.env.NEXTCLOUD_URL,
- + adminUrl: process.env.NEXTCLOUD_URL,
- };
- -
- try {
- const response = await axios.post(
- `${KEYCLOAK_URL}/admin/realms/${REALM_NAME}/clients`,
- @@ -322,21 +293,34 @@ async function createClient(token, clientId, clientName, redirectUris) {
- }
- }
- );
- - console.log(`Client ${clientId} created successfully`);
- + console.log(`Client "${clientId}" created successfully`);
- client = response.data;
- } catch (error) {
- handleAxiosError(error, `creating client: ${clientId}`);
- return;
- }
- } else {
- - client = await getClient(token, clientId);
- - console.log(`Client ${clientId} already exists, checking mappers`);
- + console.log(`Client "${clientId}" already exists, checking mappers`);
- }
-
- - if (client) {
- -
- - const existingMappers = await getClientMappers(token, clientId)
- + if (client) {
- + try {
- + await axios.put(
- + `${KEYCLOAK_URL}/admin/realms/${REALM_NAME}/clients/${client.id}`,
- + { ...client, secret: process.env[`KEYCLOAK_${clientId.replace(/[^a-zA-Z0-9]/g, '').toUpperCase()}_CLIENT_SECRET`] },
- + {
- + headers: {
- + 'Authorization': `Bearer ${token}`,
- + 'Content-Type': 'application/json'
- + }
- + }
- + );
- + console.log(`Set client secret for client: ${clientId}`);
- + } catch (error) {
- + handleAxiosError(error, `setting client secret for client: ${clientId}`, error.config, error.response);
- + }
-
- + const existingMappers = await getClientMappers(token, clientId);
- const requiredMappers = [
- {
- name: "groups",
- @@ -367,10 +351,8 @@ async function createClient(token, clientId, clientName, redirectUris) {
-
- for (const mapper of requiredMappers) {
- const existingMapper = existingMappers.find(m => m.name === mapper.name);
- -
- try {
- if (existingMapper) {
- - // Update existierenden Mapper
- await axios.put(
- `${KEYCLOAK_URL}/admin/realms/${REALM_NAME}/clients/${client.id}/protocol-mappers/models/${existingMapper.id}`,
- { ...existingMapper, ...mapper },
- @@ -381,9 +363,8 @@ async function createClient(token, clientId, clientName, redirectUris) {
- }
- }
- );
- - console.log(`Mapper ${mapper.name} updated for client ${clientId}`);
- + console.log(`Mapper "${mapper.name}" updated for client "${clientId}"`);
- } else {
- - // Erstelle neuen Mapper
- await axios.post(
- `${KEYCLOAK_URL}/admin/realms/${REALM_NAME}/clients/${client.id}/protocol-mappers/models`,
- mapper,
- @@ -394,70 +375,69 @@ async function createClient(token, clientId, clientName, redirectUris) {
- }
- }
- );
- - console.log(`Mapper ${mapper.name} created for client ${clientId}`);
- + console.log(`Mapper "${mapper.name}" created for client "${clientId}"`);
- }
- } catch (error) {
- - handleAxiosError(error, `managing mapper ${mapper.name} for client ${clientId}`, error.config, error.response);
- - // Wir werfen den Fehler nicht weiter, damit andere Mapper noch verarbeitet werden können
- + handleAxiosError(error, `managing mapper "${mapper.name}" for client "${clientId}"`, error.config, error.response);
- + }
- + }
- +
- + if (clientId.includes("nextcloud")) {
- + await addDefaultClientScope(token, clientId, "openid");
- + await addDefaultClientScope(token, clientId, "profile");
- + await addDefaultClientScope(token, clientId, "groups-nextcloud");
- +
- + try {
- + await axios.put(
- + `${KEYCLOAK_URL}/admin/realms/${REALM_NAME}/clients/${client.id}`,
- + { ...client, defaultClientScopes: client.defaultClientScopes.filter(c => c !== "nextcloud-dedicated") },
- + {
- + headers: {
- + 'Authorization': `Bearer ${token}`,
- + 'Content-Type': 'application/json'
- + }
- + }
- + );
- + console.log(`Removed client scope nextcloud-dedicated from client: ${clientId}`);
- + } catch (error) {
- + handleAxiosError(error, `removing client scope nextcloud-dedicated from client: ${clientId}`, error.config, error.response);
- }
- }
- }
- }
-
- -
- -// Gruppen erstellen
- +// Create default groups
- async function createDefaultGroups(token) {
- + console.log('Creating default groups...');
- const groups = [
- - {
- - name: "Administrators",
- - path: "/Administrators",
- - attributes: {
- - "description": ["Full system access"]
- - }
- - },
- - {
- - name: "Users",
- - path: "/Users",
- - attributes: {
- - "description": ["Regular system users"]
- - }
- - }
- + { name: "nextcloud-admins", path: "/nextcloud-admins", attributes: { "description": ["Nextcloud administrators"] } },
- + { name: "nextcloud-users", path: "/nextcloud-users", attributes: { "description": ["Nextcloud regular users"] } },
- + { name: "nextcloud-youpi", path: "/nextcloud-youpi", attributes: { "description": ["Nextcloud youpi"] } },
- + { name: "nextcloud-service", path: "/nextcloud-service", attributes: { "description": ["Nextcloud service user"] } }
- ];
-
- for (const group of groups) {
- try {
- - // Prüfen ob Gruppe existiert
- - const existingGroup = await axios.get(
- - `${KEYCLOAK_URL}/admin/realms/${REALM_NAME}/groups`,
- - {
- - headers: {
- - 'Authorization': `Bearer ${token}`
- - },
- - params: {
- - search: group.name,
- - }
- - }
- - );
- + const existingGroups = await axios.get(`${KEYCLOAK_URL}/admin/realms/${REALM_NAME}/groups`, {
- + headers: { 'Authorization': `Bearer ${token}` },
- + params: { search: group.name, exact: true } // Added exact: true for precise matching
- + });
-
- - if (existingGroup.data.length > 0) {
- - console.log(`Group ${group.name} already exists`);
- + if (existingGroups.data.length > 0) {
- + console.log(`Group "${group.name}" already exists`);
- continue;
- }
-
- - await axios.post(
- - `${KEYCLOAK_URL}/admin/realms/${REALM_NAME}/groups`,
- - group,
- - {
- - headers: {
- - 'Authorization': `Bearer ${token}`,
- - 'Content-Type': 'application/json'
- - }
- + await axios.post(`${KEYCLOAK_URL}/admin/realms/${REALM_NAME}/groups`, group, {
- + headers: {
- + 'Authorization': `Bearer ${token}`,
- + 'Content-Type': 'application/json'
- }
- - );
- - console.log(`Group ${group.name} created successfully`);
- + });
- + console.log(`Group "${group.name}" created successfully`);
- } catch (error) {
- if (error.response?.status === 409) {
- - console.log(`Group ${group.name} already exists`);
- + console.log(`Group "${group.name}" already exists`);
- } else {
- handleAxiosError(error, `creating group: ${group.name}`);
- }
- @@ -465,44 +445,44 @@ async function createDefaultGroups(token) {
- }
- }
-
- -
- +// Create a test token for a user
- async function createTestToken(token, username) {
- + console.log(`Creating test token for user "${username}"...`);
- try {
- - const nextcloudClientId = Object.keys(CLIENTS).find(key => key.includes('nextcloud')) || 'nextcloud';
- - const client = await getClient(token, nextcloudClientId);
- + const nextcloudClientId = Object.keys(CLIENTS).find(key => key.includes('nextcloud')) || 'nextcloud';
- + const client = await getClientByClientId(token, nextcloudClientId);
-
- - if (!client)
- + if (!client) {
- + console.log(`Client "${nextcloudClientId}" not found, cannot create test token.`);
- return null;
- + }
- const response = await axios.post(
- `${KEYCLOAK_URL}/realms/${REALM_NAME}/protocol/openid-connect/token`,
- new URLSearchParams({
- 'client_id': nextcloudClientId,
- - 'client_secret': process.env.KEYCLOAK_NEXTCLOUD_CLIENT_SECRET,
- + 'client_secret': process.env[`KEYCLOAK_${nextcloudClientId.replace(/[^a-zA-Z0-9]/g, '').toUpperCase()}_CLIENT_SECRET`],
- 'username': username,
- 'password': process.env.TESTADMIN_PASSWORD || "initial123!",
- 'grant_type': 'password'
- }),
- - {
- - headers: {
- - 'Content-Type': 'application/x-www-form-urlencoded'
- - }
- - }
- + { headers: { 'Content-Type': 'application/x-www-form-urlencoded' } }
- );
- + console.log(`Test token for user "${username}" created.`);
- return response.data.access_token;
- } catch (error) {
- handleAxiosError(error, `getting test token for ${username}`, error.config, error.response);
- + return null;
- }
- }
-
- -// Funktion zum Decodieren eines JWT-Tokens
- +// Function to decode a JWT token
- function decodeToken(token) {
- try {
- const base64Url = token.split('.')[1];
- const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
- - const jsonPayload = decodeURIComponent(atob(base64).split('').map(function (c) {
- - return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
- - }).join(''));
- -
- + const jsonPayload = decodeURIComponent(atob(base64).split('').map(c =>
- + '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2)
- + ).join(''));
- return JSON.parse(jsonPayload);
- } catch (error) {
- console.error("Error decoding token:", error.message);
- @@ -510,87 +490,128 @@ function decodeToken(token) {
- }
- }
-
- -// Test-User erstellen
- +// Create initial users
- async function createInitialUsers(token) {
- + console.log('Creating initial users...');
- const users = [
- - {
- - username: "testadmin",
- - enabled: true,
- - emailVerified: true,
- - firstName: "Test",
- - lastName: "Admin",
- - email: "testadmin@mrx8086.com",
- - credentials: [{
- - type: "password",
- - value: process.env.TESTADMIN_PASSWORD || "initial123!",
- - temporary: true
- - }],
- - groups: ["/Administrators"]
- - },
- - {
- - username: "testuser",
- - enabled: true,
- - emailVerified: true,
- - firstName: "Test",
- - lastName: "User",
- - email: "testuser@mrx8086.com",
- - credentials: [{
- - type: "password",
- - value: process.env.TESTUSER_PASSWORD || "initial123!",
- - temporary: true
- - }],
- - groups: ["/Users"]
- - }
- + { username: "testadmin", enabled: true, emailVerified: true, firstName: "Test", lastName: "Admin", email: "testadmin@mrx8086.com", credentials: [{ type: "password", value: process.env.TESTADMIN_PASSWORD || "initial123!", temporary: false }], groups: ["nextcloud-admins", "nextcloud-users"] },
- + { username: "testuser", enabled: true, emailVerified: true, firstName: "Test", lastName: "User", email: "testuser@mrx8086.com", credentials: [{ type: "password", value: process.env.TESTUSER_PASSWORD || "initial123!", temporary: false }], groups: ["nextcloud-users", "nextcloud-youpi"] },
- + { username: "testserviceuser", enabled: true, emailVerified: true, firstName: "Test", lastName: "Service User", email: "testserviceuser@mrx8086.com", credentials: [{ type: "password", value: process.env.TESTSERVICEUSER_PASSWORD || "initial123!", temporary: false }], groups: ["nextcloud-service"] }
- ];
-
- for (const user of users) {
- try {
- - // Prüfen ob User existiert
- - const existingUsers = await axios.get(
- - `${KEYCLOAK_URL}/admin/realms/${REALM_NAME}/users`,
- - {
- - headers: {
- - 'Authorization': `Bearer ${token}`
- - },
- - params: {
- - username: user.username,
- - exact: true
- - }
- - }
- - );
- + const existingUsers = await axios.get(`${KEYCLOAK_URL}/admin/realms/${REALM_NAME}/users`, {
- + headers: { 'Authorization': `Bearer ${token}` },
- + params: { username: user.username, exact: true } // Added exact: true for precise matching
- + });
-
- if (existingUsers.data.length > 0) {
- - console.log(`User ${user.username} already exists`);
- + console.log(`User "${user.username}" already exists`);
- continue;
- }
-
- - // User erstellen
- - await axios.post(
- - `${KEYCLOAK_URL}/admin/realms/${REALM_NAME}/users`,
- - user,
- + await axios.post(`${KEYCLOAK_URL}/admin/realms/${REALM_NAME}/users`, user, {
- + headers: {
- + 'Authorization': `Bearer ${token}`,
- + 'Content-Type': 'application/json'
- + }
- + });
- + console.log(`User "${user.username}" created successfully`);
- +
- + } catch (error) {
- + handleAxiosError(error, `creating user: ${user.username}`, error.config, error.response);
- + }
- + }
- +}
- +
- +async function createGroupsNextcloudScope(token) {
- + const scopeName = "groups-nextcloud";
- + const mapperName = "groups-mapper";
- + console.log(`Starting createGroupsNextcloudScope`);
- + let clientScope = await getClientScope(token, scopeName);
- +
- + if (!clientScope) {
- + try {
- + console.log(`Creating client scope "${scopeName}"`);
- + clientScope = await axios.post(`${KEYCLOAK_URL}/admin/realms/${REALM_NAME}/client-scopes`,
- + {
- + "name": scopeName,
- + "protocol": "openid-connect",
- + "description": "Provides access to user group information for Nextcloud.", // Hinzugefügt: Beschreibung
- + "attributes": {},
- + "consentScreenText": "Grant access to user groups in Nextcloud",
- + "includeInTokenScope": true
- + },
- {
- headers: {
- 'Authorization': `Bearer ${token}`,
- 'Content-Type': 'application/json'
- }
- - }
- - );
- - console.log(`User ${user.username} created successfully`);
- + });
- + console.log(`Client scope "${scopeName}" created successfully`);
- + clientScope = response.data;
- + } catch (error) {
- + console.error(`Error creating client scope "${scopeName}":`, error);
- + handleAxiosError(error, `creating ${scopeName} client scope`, error.config, error.response);
- + return;
- + }
- + } else {
- + console.log(`Client scope "${scopeName}" exists, skipping creation`);
- + }
-
- + console.log("Client scope creation step finished");
- + if (clientScope) {
- + console.log(`Check for mapper "${mapperName}" in scope "${scopeName}"`);
- + try {
- + const mappersResponse = await axios.get(`${KEYCLOAK_URL}/admin/realms/${REALM_NAME}/client-scopes/${clientScope.id}/protocol-mappers/models`,
- + { headers: { 'Authorization': `Bearer ${token}` } }
- + );
- + if (!mappersResponse.data.find(m => m.name === mapperName)) {
- + try {
- + await axios.post(`${KEYCLOAK_URL}/admin/realms/${REALM_NAME}/client-scopes/${clientScope.id}/protocol-mappers/models`,
- + {
- + "name": mapperName,
- + "protocol": "openid-connect",
- + "protocolMapper": "oidc-group-membership-mapper",
- + "config": {
- + "full.path": "false",
- + "id.token.claim": "false",
- + "access.token.claim": "true",
- + "userinfo.token.claim": "true",
- + "claim.name": "groups",
- + "add.to.introspection": "false"
- + }
- + },
- + {
- + headers: {
- + 'Authorization': `Bearer ${token}`,
- + 'Content-Type': 'application/json'
- + }
- + });
- + console.log(`Mapper "${mapperName}" created for client scope "${scopeName}"`);
- + } catch (error) {
- + console.error(`Error creating mapper "${mapperName}" for scope "${scopeName}":`, error);
- + handleAxiosError(error, `creating mapper for ${scopeName}`, error.config, error.response);
- + }
- + } else {
- + console.log(`Mapper "${mapperName}" exists in client scope "${scopeName}", skipping creation`);
- + }
- } catch (error) {
- - handleAxiosError(error, `creating user: ${user.username}`, error.config, error.response);
- + console.error("Error checking for mappers:", error);
- + handleAxiosError(error, `checking mappers for ${scopeName}`, error.config, error.response);
- }
- }
- + console.log("Finished createGroupsNextcloudScope");
- }
-
- -
- -// Hauptfunktion
- +// Main function
- async function setupRealm() {
- try {
- console.log('Starting Keycloak setup...');
- const token = await getAdminToken();
-
- - // Prüfe ob Realm existiert
- + // Check if realm exists
- const realmExists = await checkRealmExists(token);
-
- if (!realmExists) {
- @@ -600,41 +621,37 @@ async function setupRealm() {
- console.log('Realm already exists, skipping base setup');
- }
-
- - // Clients erstellen
- + // Create client scope groups-nextcloud
- + await createGroupsNextcloudScope(token);
- +
- + // Create clients
- for (const clientId in CLIENTS) {
- await createClient(token, clientId, clientId, CLIENTS[clientId].redirectUris);
- }
-
- - const nextcloudClientId = Object.keys(CLIENTS).find(key => key.includes('nextcloud')) || 'nextcloud';
- - await addDefaultClientScope(token, nextcloudClientId, "openid");
- -
- - // Gruppen erstellen
- + // Create groups
- await createDefaultGroups(token);
-
- - // Test User erstellen
- + // Create test users
- await createInitialUsers(token);
-
- -
- - // Konfiguration des Office-Automation Realms mit Admin Token auslesen
- + // Read the configuration of the Office-Automation realm with Admin Token
- if (token) {
- console.log("Master Realm Admin Token:", token);
- -
- try {
- const realmConfig = await axios.get(`${KEYCLOAK_URL}/admin/realms/${REALM_NAME}`, {
- - headers: {
- - 'Authorization': `Bearer ${token}`
- - }
- + headers: { 'Authorization': `Bearer ${token}` }
- });
- - console.log("Office Automation Realm Configuration:", realmConfig.data)
- + console.log("Office Automation Realm Configuration:", realmConfig.data);
- } catch (error) {
- handleAxiosError(error, 'getting office realm configuration', error.config, error.response);
- }
- } else {
- - console.error("Error getting Master Realm admin token")
- + console.error("Error getting Master Realm admin token");
- }
-
- - // Test Token erstellen
- - const testToken = await createTestToken(token, "testadmin@mrx8086.com");
- + // Create Test Token
- + const testToken = await createTestToken(token, "testadmin");
-
- if (testToken) {
- console.log("Test Token generated successfully!");
- @@ -644,7 +661,6 @@ async function setupRealm() {
- } else {
- console.error("Error generating Test Token");
- }
- -
- console.log('Setup completed successfully');
-
- } catch (error) {
- @@ -653,5 +669,5 @@ async function setupRealm() {
- }
- }
-
- -// Script ausführen
- +// Execute the script
- setupRealm();
- \ No newline at end of file
- diff --git a/scripts/setup/keycloak/test_realm.js b/scripts/setup/keycloak/test_realm.js
- index 78c03a4..11e3254 100644
- --- a/scripts/setup/keycloak/test_realm.js
- +++ b/scripts/setup/keycloak/test_realm.js
- @@ -6,7 +6,7 @@ dotenv.config();
- const KEYCLOAK_URL = process.env.KEYCLOAK_URL || 'https://auth.mrx8086.com';
- const NEXTCLOUD_CLIENT_ID = process.env.NEXTCLOUD_CLIENT_ID || 'nextcloud';
- const TESTADMIN_USERNAME = "testadmin@mrx8086.com";
- -const TESTADMIN_PASSWORD = process.env.TESTADMIN_PASSWORD || "initial123!";
- +const TESTADMIN_PASSWORD = process.env.TESTADMIN_PASSWORD;
- const REALM_NAME = 'office-automation';
- const CLIENT_SECRET = process.env.KEYCLOAK_NEXTCLOUD_CLIENT_SECRET;
-
- @@ -74,28 +74,33 @@ function decodeToken(token) {
- }
- }
-
- +// Prüfe ob ein Admin Token korrekt generiert werden kann
- async function testKeycloakLogin() {
- - try {
- - const accessToken = await getAccessToken(TESTADMIN_USERNAME, TESTADMIN_PASSWORD);
- + try {
- + const accessToken = await getAccessToken(TESTADMIN_USERNAME, TESTADMIN_PASSWORD);
-
- - if (!accessToken) {
- - console.error('Failed to get access token.');
- - return;
- - }
- - console.log('Access Token:', accessToken);
- - const decodedToken = decodeToken(accessToken);
- - if (decodedToken) {
- - console.log('Decoded Access Token:', decodedToken);
- - if (decodedToken.groups.includes('/Administrators')){
- - console.log("Admin Group is set correctly!")
- - } else {
- - console.error("Admin Group is not set correctly!")
- - }
- - }
- + if (!accessToken) {
- + console.error('Failed to get access token.');
- + return;
- + }
- + console.log('Access Token:', accessToken);
- + const decodedToken = decodeToken(accessToken);
- + if(decodedToken) {
- + console.log('Decoded Access Token:', decodedToken);
- + if (Array.isArray(decodedToken.groups) && decodedToken.groups.includes('/nextcloud-admins')){
- + console.log("Admin Group is set correctly!")
- + } else if (typeof decodedToken.groups === 'string' && decodedToken.groups.includes('/nextcloud-admins')) {
- + console.log("Admin Group is set correctly!")
- + }
- + else {
- + console.error("Admin Group is not set correctly!")
- + }
- + }
-
- - } catch (error) {
- - console.error('An error occurred:', error);
- - }
- +
- + } catch (error) {
- + console.error('An error occurred:', error);
- + }
- }
-
- testKeycloakLogin();
- \ No newline at end of file
|