Compare commits
83 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
c9e8287b30 | |
|
|
5f2e57ba31 | |
|
|
bce323172f | |
|
|
1312069b0d | |
|
|
1b1b2b2bdf | |
|
|
e2ce5828f0 | |
|
|
8ee1665c72 | |
|
|
d524bdb9d9 | |
|
|
be57314b35 | |
|
|
34eeda50fe | |
|
|
058f6f69df | |
|
|
e671bcb492 | |
|
|
c4e8afbcd1 | |
|
|
384d688b85 | |
|
|
97dfa59faf | |
|
|
9d3253f71e | |
|
|
9b84d8cdef | |
|
|
879292770a | |
|
|
58956c3675 | |
|
|
d924ed3e44 | |
|
|
ceef7921f2 | |
|
|
7444015f3d | |
|
|
f82174d398 | |
|
|
15933a3216 | |
|
|
c1e3ba7516 | |
|
|
018877e803 | |
|
|
7a75827d61 | |
|
|
5fbe206e50 | |
|
|
a07e41591a | |
|
|
f09f011e21 | |
|
|
f67feb4884 | |
|
|
7c0d335967 | |
|
|
497c1f677a | |
|
|
5970840f9e | |
|
|
b9e05884a4 | |
|
|
378498b979 | |
|
|
2f31ec4a1c | |
|
|
1cedc88a94 | |
|
|
6b3b882b3f | |
|
|
923e70eca4 | |
|
|
64b421a130 | |
|
|
b1b98c35f0 | |
|
|
00dbb22236 | |
|
|
2496a96d42 | |
|
|
e76fbbc31c | |
|
|
a90148d0eb | |
|
|
68711923e6 | |
|
|
faaabe8db2 | |
|
|
d2c2bd4a45 | |
|
|
c68631d6af | |
|
|
b71264b18e | |
|
|
495a3f25cb | |
|
|
a877255eaf | |
|
|
f09908dc14 | |
|
|
29782b00a2 | |
|
|
a135b46346 | |
|
|
d46136027f | |
|
|
91fcafe339 | |
|
|
12e2f04c4c | |
|
|
ebeb635cfb | |
|
|
e665994285 | |
|
|
72961d814f | |
|
|
e0e39af30b | |
|
|
f1cc209f8f | |
|
|
be24450387 | |
|
|
650006295e | |
|
|
999b1021ad | |
|
|
6bd62b884b | |
|
|
a964e003aa | |
|
|
6b02c7becd | |
|
|
9430e2fcb4 | |
|
|
e13612adec | |
|
|
c6122ad2fd | |
|
|
3e2c1c021d | |
|
|
36a280bff3 | |
|
|
bd691696a3 | |
|
|
5d0c345246 | |
|
|
3d52c9d1b8 | |
|
|
c12901cd84 | |
|
|
d2131cf974 | |
|
|
6e5a36cc1e | |
|
|
c6acdbd814 | |
|
|
4db84200fa |
|
|
@ -1,8 +1,9 @@
|
|||
FROM ubuntu:16.04
|
||||
FROM ubuntu:22.04
|
||||
|
||||
RUN apt-get update && apt-get upgrade -y
|
||||
RUN apt-get update -y
|
||||
#&& apt-get upgrade -y
|
||||
|
||||
RUN apt-get install build-essential -y
|
||||
RUN apt-get install build-essential -y --fix-missing
|
||||
|
||||
RUN echo "mysql-server mysql-server/root_password password root" | debconf-set-selections
|
||||
|
||||
|
|
@ -12,7 +13,7 @@ RUN apt install mysql-server -y
|
|||
|
||||
RUN usermod -d /var/lib/mysql/ mysql
|
||||
|
||||
RUN mkdir /var/run/mysqld
|
||||
RUN [ -d /var/run/mysqld ] || mkdir -p /var/run/mysqld
|
||||
|
||||
ADD ./runMySQL.sh /runMySQL.sh
|
||||
|
||||
|
|
@ -22,36 +23,42 @@ RUN apt-get install -y git-core
|
|||
|
||||
RUN apt-get install -y wget
|
||||
|
||||
# Install OpenJDK 12
|
||||
RUN wget https://download.java.net/java/GA/jdk12.0.2/e482c34c86bd4bf8b56c0b35558996b9/10/GPL/openjdk-12.0.2_linux-x64_bin.tar.gz
|
||||
|
||||
RUN mkdir /usr/java
|
||||
|
||||
RUN mv openjdk-12.0.2_linux-x64_bin.tar.gz /usr/java
|
||||
|
||||
RUN cd /usr/java && tar -xzvf openjdk-12.0.2_linux-x64_bin.tar.gz
|
||||
|
||||
RUN echo 'JAVA_HOME=/usr/java/jdk-12.0.2' >> /etc/profile
|
||||
RUN echo 'PATH=$PATH:$HOME/bin:$JAVA_HOME/bin' >> /etc/profile
|
||||
|
||||
RUN apt-get install jq -y
|
||||
|
||||
RUN apt-get install curl -y
|
||||
|
||||
RUN apt-get install unzip -y
|
||||
|
||||
# Install OpenJDK 15.0.1
|
||||
RUN wget https://download.java.net/java/GA/jdk15.0.1/51f4f36ad4ef43e39d0dfdbaf6549e32/9/GPL/openjdk-15.0.1_linux-x64_bin.tar.gz
|
||||
# Install OpenJDK 21.0.7
|
||||
RUN wget https://github.com/adoptium/temurin21-binaries/releases/download/jdk-21.0.7%2B6/OpenJDK21U-jdk_x64_linux_hotspot_21.0.7_6.tar.gz
|
||||
|
||||
RUN mv openjdk-15.0.1_linux-x64_bin.tar.gz /usr/java
|
||||
|
||||
RUN cd /usr/java && tar -xzvf openjdk-15.0.1_linux-x64_bin.tar.gz
|
||||
|
||||
RUN echo 'JAVA_HOME=/usr/java/jdk-15.0.1' >> /etc/profile
|
||||
RUN mv OpenJDK21U-jdk_x64_linux_hotspot_21.0.7_6.tar.gz /usr/java
|
||||
RUN cd /usr/java && tar -xzvf OpenJDK21U-jdk_x64_linux_hotspot_21.0.7_6.tar.gz -C /usr/java/
|
||||
RUN mv /usr/java/jdk-21.0.7+6 /usr/java/jdk-21.0.7
|
||||
RUN echo 'JAVA_HOME=/usr/java/jdk-21.0.7' >> /etc/profile
|
||||
RUN echo 'JRE_HOME=/usr/java/jdk-21.0.7' >> /etc/profile
|
||||
RUN echo 'PATH=$PATH:$HOME/bin:$JAVA_HOME/bin' >> /etc/profile
|
||||
RUN echo 'export JAVA_HOME' >> /etc/profile
|
||||
RUN echo 'export JRE_HOME' >> /etc/profile
|
||||
RUN echo 'export PATH' >> /etc/profile
|
||||
|
||||
RUN update-alternatives --install "/usr/bin/java" "java" "/usr/java/jdk-12.0.2/bin/java" 1
|
||||
RUN update-alternatives --install "/usr/bin/javac" "javac" "/usr/java/jdk-12.0.2/bin/javac" 1
|
||||
RUN update-alternatives --install "/usr/bin/java" "java" "/usr/java/jdk-21.0.7/bin/java" 1
|
||||
RUN update-alternatives --install "/usr/bin/javac" "javac" "/usr/java/jdk-21.0.7/bin/javac" 1
|
||||
|
||||
#install postgres 13
|
||||
# Import Repository Signing Key
|
||||
RUN wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add -
|
||||
RUN DEBIAN_FRONTEND=noninteractive TZ=Etc/UTC apt-get -y install tzdata
|
||||
RUN apt install curl gpg gnupg2 software-properties-common apt-transport-https lsb-release ca-certificates sudo -y
|
||||
# Add PostgreSQL repository
|
||||
RUN echo "deb http://apt.postgresql.org/pub/repos/apt/ `lsb_release -cs`-pgdg main" | tee /etc/apt/sources.list.d/pgdg.list
|
||||
# Update again
|
||||
RUN apt update
|
||||
# Install PostgreSQL 13
|
||||
RUN apt install -y postgresql-13
|
||||
# Verify PostgreSQL 13 Installation on Ubuntu 22.04|20.04|18.04
|
||||
RUN psql --version
|
||||
# Manage PostgreSQL 13 service
|
||||
#you can manage with `service postgresql start`
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
FROM ubuntu:16.04
|
||||
|
||||
RUN apt-get update && apt-get upgrade -y
|
||||
|
||||
RUN apt-get install build-essential -y
|
||||
|
||||
RUN echo "mysql-server mysql-server/root_password password root" | debconf-set-selections
|
||||
|
||||
RUN echo "mysql-server mysql-server/root_password_again password root" | debconf-set-selections
|
||||
|
||||
RUN apt install mysql-server -y
|
||||
|
||||
RUN usermod -d /var/lib/mysql/ mysql
|
||||
|
||||
RUN mkdir /var/run/mysqld
|
||||
|
||||
ADD ./runMySQL.sh /runMySQL.sh
|
||||
|
||||
RUN chmod +x /runMySQL.sh
|
||||
|
||||
RUN apt-get install -y git-core
|
||||
|
||||
RUN apt-get install -y wget
|
||||
|
||||
# Install OpenJDK 12
|
||||
RUN wget https://download.java.net/java/GA/jdk12.0.2/e482c34c86bd4bf8b56c0b35558996b9/10/GPL/openjdk-12.0.2_linux-x64_bin.tar.gz
|
||||
|
||||
RUN mkdir /usr/java
|
||||
|
||||
RUN mv openjdk-12.0.2_linux-x64_bin.tar.gz /usr/java
|
||||
|
||||
RUN cd /usr/java && tar -xzvf openjdk-12.0.2_linux-x64_bin.tar.gz
|
||||
|
||||
RUN echo 'JAVA_HOME=/usr/java/jdk-12.0.2' >> /etc/profile
|
||||
RUN echo 'PATH=$PATH:$HOME/bin:$JAVA_HOME/bin' >> /etc/profile
|
||||
|
||||
RUN apt-get install jq -y
|
||||
|
||||
RUN apt-get install curl -y
|
||||
|
||||
RUN apt-get install unzip -y
|
||||
|
||||
# Install OpenJDK 21.0.7
|
||||
RUN wget https://download.java.net/java/GA/jdk21.0.7/51f4f36ad4ef43e39d0dfdbaf6549e32/9/GPL/openjdk-21.0.7_linux-x64_bin.tar.gz
|
||||
|
||||
RUN mv openjdk-21.0.7_linux-x64_bin.tar.gz /usr/java
|
||||
|
||||
RUN cd /usr/java && tar -xzvf openjdk-21.0.7_linux-x64_bin.tar.gz
|
||||
|
||||
RUN echo 'JAVA_HOME=/usr/java/jdk-21.0.7' >> /etc/profile
|
||||
RUN echo 'PATH=$PATH:$HOME/bin:$JAVA_HOME/bin' >> /etc/profile
|
||||
RUN echo 'export JAVA_HOME' >> /etc/profile
|
||||
RUN echo 'export JRE_HOME' >> /etc/profile
|
||||
RUN echo 'export PATH' >> /etc/profile
|
||||
|
||||
RUN update-alternatives --install "/usr/bin/java" "java" "/usr/java/jdk-12.0.2/bin/java" 1
|
||||
RUN update-alternatives --install "/usr/bin/javac" "javac" "/usr/java/jdk-12.0.2/bin/javac" 1
|
||||
|
|
@ -22,39 +22,29 @@ RUN apt-get install -y git-core
|
|||
|
||||
RUN apt-get install -y wget
|
||||
|
||||
# Install OpenJDK 12
|
||||
RUN wget https://download.java.net/java/GA/jdk12.0.2/e482c34c86bd4bf8b56c0b35558996b9/10/GPL/openjdk-12.0.2_linux-x64_bin.tar.gz
|
||||
|
||||
RUN mkdir /usr/java
|
||||
|
||||
RUN mv openjdk-12.0.2_linux-x64_bin.tar.gz /usr/java
|
||||
|
||||
RUN cd /usr/java && tar -xzvf openjdk-12.0.2_linux-x64_bin.tar.gz
|
||||
|
||||
RUN echo 'JAVA_HOME=/usr/java/jdk-12.0.2' >> /etc/profile
|
||||
RUN echo 'PATH=$PATH:$HOME/bin:$JAVA_HOME/bin' >> /etc/profile
|
||||
|
||||
RUN apt-get install jq -y
|
||||
|
||||
RUN apt-get install curl -y
|
||||
|
||||
RUN apt-get install unzip -y
|
||||
|
||||
# Install OpenJDK 15.0.1
|
||||
RUN wget https://download.java.net/java/GA/jdk15.0.1/51f4f36ad4ef43e39d0dfdbaf6549e32/9/GPL/openjdk-15.0.1_linux-x64_bin.tar.gz
|
||||
# Install OpenJDK 21.0.7
|
||||
RUN wget https://github.com/adoptium/temurin21-binaries/releases/download/jdk-21.0.7%2B6/OpenJDK21U-jdk_x64_linux_hotspot_21.0.7_6.tar.gz
|
||||
|
||||
RUN mv openjdk-15.0.1_linux-x64_bin.tar.gz /usr/java
|
||||
RUN mv OpenJDK21U-jdk_x64_linux_hotspot_21.0.7_6.tar.gz /usr/java
|
||||
RUN mkdir -p /usr/java/jdk-21.0.7
|
||||
RUN cd /usr/java && tar -xzvf OpenJDK21U-jdk_x64_linux_hotspot_21.0.7_6.tar.gz -C /usr/java/jdk-21.0.7
|
||||
|
||||
RUN cd /usr/java && tar -xzvf openjdk-15.0.1_linux-x64_bin.tar.gz
|
||||
|
||||
RUN echo 'JAVA_HOME=/usr/java/jdk-15.0.1' >> /etc/profile
|
||||
RUN echo 'JAVA_HOME=/usr/java/jdk-21.0.7' >> /etc/profile
|
||||
RUN echo 'PATH=$PATH:$HOME/bin:$JAVA_HOME/bin' >> /etc/profile
|
||||
RUN echo 'export JAVA_HOME' >> /etc/profile
|
||||
RUN echo 'export JRE_HOME' >> /etc/profile
|
||||
RUN echo 'export PATH' >> /etc/profile
|
||||
|
||||
RUN update-alternatives --install "/usr/bin/java" "java" "/usr/java/jdk-12.0.2/bin/java" 1
|
||||
RUN update-alternatives --install "/usr/bin/javac" "javac" "/usr/java/jdk-12.0.2/bin/javac" 1
|
||||
RUN update-alternatives --install "/usr/bin/java" "java" "/usr/java/jdk-21.0.7/bin/java" 1
|
||||
RUN update-alternatives --install "/usr/bin/javac" "javac" "/usr/java/jdk-21.0.7/bin/javac" 1
|
||||
|
||||
#install postgres 13
|
||||
# Import Repository Signing Key
|
||||
|
|
|
|||
|
|
@ -138,8 +138,8 @@ do
|
|||
cd supertokens-root
|
||||
rm gradle.properties
|
||||
|
||||
update-alternatives --install "/usr/bin/java" "java" "/usr/java/jdk-15.0.1/bin/java" 2
|
||||
update-alternatives --install "/usr/bin/javac" "javac" "/usr/java/jdk-15.0.1/bin/javac" 2
|
||||
update-alternatives --install "/usr/bin/java" "java" "/usr/java/jdk-21.0.7/bin/java" 2
|
||||
update-alternatives --install "/usr/bin/javac" "javac" "/usr/java/jdk-21.0.7/bin/javac" 2
|
||||
|
||||
coreX=$(cut -d'.' -f1 <<<"$coreVersion")
|
||||
coreY=$(cut -d'.' -f2 <<<"$coreVersion")
|
||||
|
|
|
|||
|
|
@ -4,20 +4,22 @@ RUN apt-get update && apt-get upgrade -y
|
|||
RUN apt-get install build-essential -y
|
||||
RUN apt-get install -y git-core wget unzip jq curl
|
||||
|
||||
# Install OpenJDK 15.0.1
|
||||
RUN wget https://download.java.net/java/GA/jdk15.0.1/51f4f36ad4ef43e39d0dfdbaf6549e32/9/GPL/openjdk-15.0.1_linux-x64_bin.tar.gz
|
||||
RUN mkdir -p /usr/java
|
||||
RUN mv openjdk-15.0.1_linux-x64_bin.tar.gz /usr/java
|
||||
RUN cd /usr/java && tar -xzvf openjdk-15.0.1_linux-x64_bin.tar.gz
|
||||
# Install OpenJDK 21.0.7
|
||||
RUN wget https://github.com/adoptium/temurin21-binaries/releases/download/jdk-21.0.7%2B6/OpenJDK21U-jdk_x64_linux_hotspot_21.0.7_6.tar.gz
|
||||
|
||||
RUN echo 'JAVA_HOME=/usr/java/jdk-15.0.1' >> /etc/profile
|
||||
RUN mv OpenJDK21U-jdk_x64_linux_hotspot_21.0.7_6.tar.gz /usr/java
|
||||
RUN mkdir -p /usr/java/
|
||||
RUN cd /usr/java && tar -xzvf OpenJDK21U-jdk_x64_linux_hotspot_21.0.7_6.tar.gz
|
||||
RUN mv /usr/java/jdk-21.0.7+6 /usr/java/jdk-21.0.7
|
||||
|
||||
RUN echo 'JAVA_HOME=/usr/java/jdk-21.0.7' >> /etc/profile
|
||||
RUN echo 'PATH=$PATH:$HOME/bin:$JAVA_HOME/bin' >> /etc/profile
|
||||
RUN echo 'export JAVA_HOME' >> /etc/profile
|
||||
RUN echo 'export JRE_HOME' >> /etc/profile
|
||||
RUN echo 'export PATH' >> /etc/profile
|
||||
|
||||
RUN update-alternatives --install "/usr/bin/java" "java" "/usr/java/jdk-15.0.1/bin/java" 1
|
||||
RUN update-alternatives --install "/usr/bin/javac" "javac" "/usr/java/jdk-15.0.1/bin/javac" 1
|
||||
RUN update-alternatives --install "/usr/bin/java" "java" "/usr/java/jdk-21.0.7/bin/java" 1
|
||||
RUN update-alternatives --install "/usr/bin/javac" "javac" "/usr/java/jdk-21.0.7/bin/javac" 1
|
||||
|
||||
RUN wget -O docker-entrypoint.sh https://raw.githubusercontent.com/supertokens/supertokens-docker-postgresql/master/docker-entrypoint.sh
|
||||
|
||||
|
|
@ -51,7 +53,7 @@ RUN set -x \
|
|||
&& gpgconf --kill all \
|
||||
&& rm -rf "$GNUPGHOME" /usr/local/bin/gosu.asc \
|
||||
&& chmod +x /usr/local/bin/gosu \
|
||||
&& wget -O jre.zip "https://raw.githubusercontent.com/supertokens/jre/master/jre-15.0.1-linux.zip" \
|
||||
&& wget -O jre.zip "https://raw.githubusercontent.com/supertokens/jre/master/jre-21.0.7-linux.zip" \
|
||||
&& mkdir -p /usr/lib/supertokens/jre \
|
||||
&& unzip jre.zip \
|
||||
&& mv jre-*/* /usr/lib/supertokens/jre \
|
||||
|
|
|
|||
|
|
@ -0,0 +1,56 @@
|
|||
import json
|
||||
import os
|
||||
|
||||
import http.client
|
||||
|
||||
def register_core_version(supertokens_api_key, core_version, plugin_interface_array, core_driver_array):
|
||||
print("Core Version: ", core_version)
|
||||
print("Plugin Interface Array: ", plugin_interface_array)
|
||||
print("Core Driver Array: ", core_driver_array)
|
||||
|
||||
conn = http.client.HTTPSConnection("api.supertokens.io")
|
||||
|
||||
payload = {
|
||||
"password": supertokens_api_key,
|
||||
"planType": "FREE",
|
||||
"version": core_version,
|
||||
"pluginInterfaces": plugin_interface_array,
|
||||
"coreDriverInterfaces": core_driver_array
|
||||
}
|
||||
|
||||
headers = {
|
||||
'Content-Type': 'application/json',
|
||||
'api-version': '0'
|
||||
}
|
||||
|
||||
conn.request("PUT", "/0/core", json.dumps(payload), headers)
|
||||
response = conn.getresponse()
|
||||
|
||||
if response.status != 200:
|
||||
print(f"failed core PUT API status code: {response.status}. Exiting!")
|
||||
exit(1)
|
||||
|
||||
conn.close()
|
||||
|
||||
|
||||
def read_core_version():
|
||||
with open('build.gradle', 'r') as file:
|
||||
for line in file:
|
||||
if 'version =' in line:
|
||||
return line.split('=')[1].strip().strip("'\"")
|
||||
raise Exception("Could not find version in build.gradle")
|
||||
|
||||
core_version = read_core_version()
|
||||
|
||||
with open('pluginInterfaceSupported.json', 'r') as fd:
|
||||
plugin_interface_array = json.load(fd)['versions']
|
||||
|
||||
with open('coreDriverInterfaceSupported.json', 'r') as fd:
|
||||
core_driver_array = json.load(fd)['versions']
|
||||
|
||||
register_core_version(
|
||||
supertokens_api_key=os.environ.get("SUPERTOKENS_API_KEY"),
|
||||
core_version=core_version,
|
||||
plugin_interface_array=plugin_interface_array,
|
||||
core_driver_array=core_driver_array
|
||||
)
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
import json
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
import http.client
|
||||
|
||||
|
||||
def register_plugin_version(supertokens_api_key, plugin_version, plugin_interface_array, plugin_name):
|
||||
print("Plugin Version: ", plugin_version)
|
||||
print("Plugin Interface Array: ", plugin_interface_array)
|
||||
print("Plugin Name: ", plugin_name)
|
||||
|
||||
conn = http.client.HTTPSConnection("api.supertokens.io")
|
||||
|
||||
payload = {
|
||||
"password": supertokens_api_key,
|
||||
"planType": "FREE",
|
||||
"version": plugin_version,
|
||||
"pluginInterfaces": plugin_interface_array,
|
||||
"name": plugin_name
|
||||
}
|
||||
|
||||
headers = {
|
||||
'Content-Type': 'application/json',
|
||||
'api-version': '0'
|
||||
}
|
||||
|
||||
conn.request("PUT", "/0/plugin", json.dumps(payload), headers)
|
||||
response = conn.getresponse()
|
||||
|
||||
if response.status != 200:
|
||||
print(f"failed plugin PUT API status code: {response.status}. Exiting!")
|
||||
print(f"response: {str(response.read())}")
|
||||
exit(1)
|
||||
|
||||
conn.close()
|
||||
|
||||
def read_plugin_version():
|
||||
with open('build.gradle', 'r') as file:
|
||||
for line in file:
|
||||
if 'version =' in line:
|
||||
return line.split('=')[1].strip().strip("'\"")
|
||||
raise Exception("Could not find version in build.gradle")
|
||||
|
||||
plugin_version = read_plugin_version()
|
||||
|
||||
with open('pluginInterfaceSupported.json', 'r') as fd:
|
||||
plugin_interface_array = json.load(fd)['versions']
|
||||
|
||||
def check_if_tag_exists(tag):
|
||||
try:
|
||||
result = subprocess.run(['git', 'tag', '-l', tag], capture_output=True, text=True)
|
||||
return tag in result.stdout
|
||||
except subprocess.CalledProcessError:
|
||||
print(f"Error checking for tag {tag}")
|
||||
return False
|
||||
|
||||
dev_tag = f"dev-v{plugin_version}"
|
||||
if not check_if_tag_exists(dev_tag):
|
||||
print(f"Tag {dev_tag} does not exist. Exiting!")
|
||||
exit(0)
|
||||
|
||||
register_plugin_version(
|
||||
supertokens_api_key=os.environ.get("SUPERTOKENS_API_KEY"),
|
||||
plugin_version=plugin_version,
|
||||
plugin_interface_array=plugin_interface_array,
|
||||
plugin_name=os.environ.get("PLUGIN_NAME")
|
||||
)
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
# Check for required arguments
|
||||
if [ "$#" -ne 2 ]; then
|
||||
echo "Usage: $0 <source-image:tag> <target-image:tag>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
SOURCE_IMAGE="$1"
|
||||
TARGET_IMAGE="$2"
|
||||
|
||||
# Platforms to support
|
||||
PLATFORMS=("linux/amd64" "linux/arm64")
|
||||
TEMP_IMAGES=()
|
||||
|
||||
# Pull, retag, and push platform-specific images
|
||||
for PLATFORM in "${PLATFORMS[@]}"; do
|
||||
ARCH=$(echo $PLATFORM | cut -d'/' -f2)
|
||||
TEMP_TAG="${TARGET_IMAGE}-${ARCH}"
|
||||
TEMP_IMAGES+=("$TEMP_TAG")
|
||||
|
||||
echo "Pulling $SOURCE_IMAGE for $PLATFORM..."
|
||||
docker pull --platform $PLATFORM "$SOURCE_IMAGE"
|
||||
|
||||
echo "Tagging as $TEMP_TAG..."
|
||||
docker tag "$SOURCE_IMAGE" "$TEMP_TAG"
|
||||
|
||||
echo "Pushing $TEMP_TAG..."
|
||||
docker push "$TEMP_TAG"
|
||||
done
|
||||
|
||||
# Create and push manifest for multi-arch image
|
||||
echo "Creating and pushing multi-arch manifest for $TARGET_IMAGE..."
|
||||
docker manifest create "$TARGET_IMAGE" "${TEMP_IMAGES[@]}"
|
||||
docker manifest push "$TARGET_IMAGE"
|
||||
|
||||
echo "✅ Multi-arch image pushed as $TARGET_IMAGE"
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
import http.client
|
||||
import json
|
||||
import time
|
||||
import os
|
||||
import sys
|
||||
|
||||
|
||||
REPO = "supertokens/supertokens-core"
|
||||
SHA = os.environ.get("GITHUB_SHA")
|
||||
NAME = os.environ.get("WORKFLOW_NAME", "Publish Dev Docker Image")
|
||||
|
||||
st = time.time()
|
||||
|
||||
def get_latest_actions():
|
||||
conn = http.client.HTTPSConnection("api.github.com")
|
||||
url = f"/repos/{REPO}/actions/runs"
|
||||
headers = {"User-Agent": "Python-http.client"}
|
||||
conn.request("GET", url, headers=headers)
|
||||
response = conn.getresponse()
|
||||
|
||||
if response.status == 200:
|
||||
data = response.read()
|
||||
runs = json.loads(data)['workflow_runs']
|
||||
found = False
|
||||
for run in runs:
|
||||
if run['head_sha'] == SHA and run['name'] == NAME:
|
||||
found = True
|
||||
break
|
||||
|
||||
if not found:
|
||||
print("No matching workflow run found.")
|
||||
sys.exit(1)
|
||||
|
||||
if run["status"] == "completed":
|
||||
if run["conclusion"] == "success":
|
||||
print("Workflow completed successfully.")
|
||||
return True
|
||||
else:
|
||||
print(f"Workflow failed with conclusion: {run['conclusion']}")
|
||||
sys.exit(1)
|
||||
else:
|
||||
print(f"Failed to fetch workflow runs: {response.status} {response.reason}")
|
||||
sys.exit(1)
|
||||
|
||||
return False
|
||||
|
||||
time.sleep(30) # Wait for 30 seconds before checking
|
||||
|
||||
|
||||
while not get_latest_actions():
|
||||
print("Waiting for the latest actions to complete...")
|
||||
time.sleep(10)
|
||||
if time.time() - st > 600:
|
||||
print("Timed out waiting for the latest actions.")
|
||||
sys.exit(1)
|
||||
|
|
@ -0,0 +1,107 @@
|
|||
name: Add dev tags for release
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
core-version:
|
||||
description: 'Core version'
|
||||
required: true
|
||||
type: string
|
||||
plugin-interface-version:
|
||||
description: 'Plugin interface version'
|
||||
required: true
|
||||
type: string
|
||||
new-release-for-plugin-interface:
|
||||
description: 'New release for plugin interface'
|
||||
required: true
|
||||
type: boolean
|
||||
postgresql-plugin-version:
|
||||
description: 'Postgres plugin version'
|
||||
required: true
|
||||
new-release-for-postgresql-plugin:
|
||||
description: 'New release for postgres plugin'
|
||||
required: true
|
||||
type: boolean
|
||||
|
||||
jobs:
|
||||
dependency-branches:
|
||||
name: Dependency Branches
|
||||
environment: publish
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
branches: ${{ steps.result.outputs.branches }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: supertokens/get-core-dependencies-action@main
|
||||
id: result
|
||||
with:
|
||||
run-for: add-dev-tag
|
||||
core-version: ${{ github.event.inputs.core-version }}
|
||||
plugin-interface-version: ${{ github.event.inputs.plugin-interface-version }}
|
||||
postgresql-plugin-version: ${{ github.event.inputs.postgresql-plugin-version }}
|
||||
add-dev-tag:
|
||||
environment: publish
|
||||
runs-on: ubuntu-latest
|
||||
needs: dependency-branches
|
||||
steps:
|
||||
- name: Set up JDK 21.0.7
|
||||
uses: actions/setup-java@v2
|
||||
with:
|
||||
java-version: 21.0.7
|
||||
distribution: zulu
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
repository: supertokens/supertokens-root
|
||||
path: ./supertokens-root
|
||||
ref: master
|
||||
- name: Checkout supertokens-core
|
||||
run: |
|
||||
cd supertokens-root
|
||||
git clone https://${{ secrets.GH_TOKEN }}@github.com/supertokens/supertokens-core.git
|
||||
cd supertokens-core
|
||||
git checkout ${{ fromJson(needs.dependency-branches.outputs.branches)['core'] }}
|
||||
- name: Checkout supertokens-plugin-interface
|
||||
run: |
|
||||
cd supertokens-root
|
||||
git clone https://${{ secrets.GH_TOKEN }}@github.com/supertokens/supertokens-plugin-interface.git
|
||||
cd supertokens-plugin-interface
|
||||
git checkout ${{ fromJson(needs.dependency-branches.outputs.branches)['plugin-interface'] }}
|
||||
- name: Checkout supertokens-postgresql-plugin
|
||||
run: |
|
||||
cd supertokens-root
|
||||
git clone https://${{ secrets.GH_TOKEN }}@github.com/supertokens/supertokens-postgresql-plugin.git
|
||||
cd supertokens-postgresql-plugin
|
||||
git checkout ${{ fromJson(needs.dependency-branches.outputs.branches)['postgresql'] }}
|
||||
- name: Load Modules
|
||||
run: |
|
||||
cd supertokens-root
|
||||
echo "core,master
|
||||
plugin-interface,master
|
||||
postgresql-plugin,master
|
||||
" > modules.txt
|
||||
cat modules.txt
|
||||
./loadModules
|
||||
- name: Setup test env
|
||||
run: cd supertokens-root && ./utils/setupTestEnv --local
|
||||
- name: Git config
|
||||
run: |
|
||||
git config --global user.name "Supertokens Bot"
|
||||
git config --global user.email "<>"
|
||||
- name: Add dev tag to plugin interface
|
||||
if: ${{ github.event.inputs.new-release-for-plugin-interface == 'true' }}
|
||||
run: |
|
||||
echo "Adding dev tag to plugin interface"
|
||||
cd supertokens-root/supertokens-plugin-interface
|
||||
./addDevTag
|
||||
- name: Add dev tag to postgres plugin
|
||||
if: ${{ github.event.inputs.new-release-for-postgresql-plugin == 'true' }}
|
||||
run: |
|
||||
echo "Adding dev tag to postgres plugin"
|
||||
cd supertokens-root/supertokens-postgresql-plugin
|
||||
./addDevTag
|
||||
- name: Add dev tag to core
|
||||
run: |
|
||||
echo "Adding dev tag to core"
|
||||
cd supertokens-root/supertokens-core
|
||||
./addDevTag
|
||||
|
|
@ -0,0 +1,134 @@
|
|||
name: Container Security Scan
|
||||
|
||||
on:
|
||||
# Allow manual triggering
|
||||
workflow_dispatch:
|
||||
|
||||
# Run automatically once a day at 2 AM UTC
|
||||
schedule:
|
||||
- cron: '0 2 * * *'
|
||||
|
||||
jobs:
|
||||
container-scan:
|
||||
name: Scan SuperTokens PostgreSQL Container
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Run Azure Container Scan
|
||||
id: container-scan
|
||||
uses: Azure/container-scan@v0
|
||||
continue-on-error: true
|
||||
with:
|
||||
image-name: supertokens/supertokens-postgresql:latest
|
||||
severity-threshold: LOW
|
||||
run-quality-checks: false
|
||||
env:
|
||||
DOCKER_CONTENT_TRUST: 1
|
||||
|
||||
- name: Upload scan results
|
||||
id: upload-scan-results
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: container-scan-results
|
||||
path: |
|
||||
${{ steps.container-scan.outputs.scan-report-path }}
|
||||
retention-days: 30
|
||||
|
||||
- name: Generate Security Summary
|
||||
id: security-summary
|
||||
run: |
|
||||
echo "summary<<EOF" >> $GITHUB_OUTPUT
|
||||
|
||||
echo "**Image:** \`supertokens/supertokens-postgresql:latest\`\n" >> $GITHUB_OUTPUT
|
||||
echo "**Scan Date:** \`$(date -u)\`\n" >> $GITHUB_OUTPUT
|
||||
echo "\n" >> $GITHUB_OUTPUT
|
||||
|
||||
# Get the scan report path from the container scan output
|
||||
SCAN_REPORT_PATH="${{ steps.container-scan.outputs.scan-report-path }}"
|
||||
|
||||
if [ -f "$SCAN_REPORT_PATH" ]; then
|
||||
# Count vulnerabilities by severity using the correct JSON structure
|
||||
critical=$(jq '[.vulnerabilities[]? | select(.severity == "CRITICAL")] | length' "$SCAN_REPORT_PATH" 2>/dev/null || echo "0")
|
||||
high=$(jq '[.vulnerabilities[]? | select(.severity == "HIGH")] | length' "$SCAN_REPORT_PATH" 2>/dev/null || echo "0")
|
||||
medium=$(jq '[.vulnerabilities[]? | select(.severity == "MEDIUM")] | length' "$SCAN_REPORT_PATH" 2>/dev/null || echo "0")
|
||||
low=$(jq '[.vulnerabilities[]? | select(.severity == "LOW")] | length' "$SCAN_REPORT_PATH" 2>/dev/null || echo "0")
|
||||
|
||||
total_vulns=$(jq '[.vulnerabilities[]?] | length' "$SCAN_REPORT_PATH" 2>/dev/null || echo "0")
|
||||
|
||||
echo "**Total Vulnerabilities:** $total_vulns\n" >> $GITHUB_OUTPUT
|
||||
echo "\n" >> $GITHUB_OUTPUT
|
||||
|
||||
echo "- 🔴 **Critical**: $critical\n" >> $GITHUB_OUTPUT
|
||||
echo "- 🟠 **High**: $high\n" >> $GITHUB_OUTPUT
|
||||
echo "- 🟡 **Medium**: $medium\n" >> $GITHUB_OUTPUT
|
||||
echo "- 🟢 **Low**: $low\n" >> $GITHUB_OUTPUT
|
||||
echo "\n" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "❌ **Scan results not found or scan failed**" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
echo "\n" >> $GITHUB_OUTPUT
|
||||
|
||||
echo "[📃 Download the full report](${{ steps.upload-scan-results.outputs.artifact-url }})\n" >> $GITHUB_OUTPUT
|
||||
|
||||
echo "EOF" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Add to Action Summary
|
||||
run: |
|
||||
echo "**Image:** \`supertokens/supertokens-postgresql:latest\`" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "**Scan Date:** \`$(date -u)\`" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
# Get the scan report path from the container scan output
|
||||
SCAN_REPORT_PATH="${{ steps.container-scan.outputs.scan-report-path }}"
|
||||
|
||||
if [ -f "$SCAN_REPORT_PATH" ]; then
|
||||
# Count vulnerabilities by severity using the correct JSON structure
|
||||
critical=$(jq '[.vulnerabilities[]? | select(.severity == "CRITICAL")] | length' "$SCAN_REPORT_PATH" 2>/dev/null || echo "0")
|
||||
high=$(jq '[.vulnerabilities[]? | select(.severity == "HIGH")] | length' "$SCAN_REPORT_PATH" 2>/dev/null || echo "0")
|
||||
medium=$(jq '[.vulnerabilities[]? | select(.severity == "MEDIUM")] | length' "$SCAN_REPORT_PATH" 2>/dev/null || echo "0")
|
||||
low=$(jq '[.vulnerabilities[]? | select(.severity == "LOW")] | length' "$SCAN_REPORT_PATH" 2>/dev/null || echo "0")
|
||||
|
||||
total_vulns=$(jq '[.vulnerabilities[]?] | length' "$SCAN_REPORT_PATH" 2>/dev/null || echo "0")
|
||||
|
||||
echo "**Total Vulnerabilities:** $total_vulns" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
echo "- 🔴 **Critical**: $critical" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- 🟠 **High**: $high" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- 🟡 **Medium**: $medium" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- 🟢 **Low**: $low" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
echo "**Vulnerabilities:**" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| ID | Package | Severity | | Description |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "|----|---------|----------|-|-------------|" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
# Extract and format vulnerabilities into a table with colored severity indicators, excluding LOW severity
|
||||
jq -r '.vulnerabilities[]? | select(.severity != "LOW") | "| \(.vulnerabilityId // "N/A") | \(.packageName // "N/A") | \(.severity // "UNKNOWN") | \(if .severity == "CRITICAL" then "🔴" elif .severity == "HIGH" then "🟠" elif .severity == "MEDIUM" then "🟡" else "🟢" end) | \((.description // "No description available") | gsub("\n"; " ")) |"' "$SCAN_REPORT_PATH" >> $GITHUB_STEP_SUMMARY
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
echo "❌ **Scan results not found or scan failed**" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "[📃 Download the full report](${{ steps.upload-scan-results.outputs.artifact-url }})" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
- name: Post notification on Slack channel
|
||||
id: deployment_message
|
||||
uses: slackapi/slack-github-action@v2.1.0
|
||||
with:
|
||||
method: chat.postMessage
|
||||
token: ${{ secrets.SLACK_BOT_TOKEN }}
|
||||
payload: |
|
||||
channel: ${{ secrets.SLACK_CHANNEL_ID }}
|
||||
text: ""
|
||||
blocks:
|
||||
- type: "header"
|
||||
text:
|
||||
type: "plain_text"
|
||||
text: "${{ steps.container-scan.outcome == 'success' && '✅' || '❌' }} Vulnerability Report: ${{ steps.container-scan.outcome == 'success' && 'All okay' || 'Needs attention' }}"
|
||||
- type: "markdown"
|
||||
text: "${{ steps.security-summary.outputs.summary }}"
|
||||
|
|
@ -0,0 +1,153 @@
|
|||
name: Checks for release
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- '[0-9]+.[0-9]+'
|
||||
tags:
|
||||
- 'dev-*'
|
||||
|
||||
jobs:
|
||||
dependency-versions:
|
||||
name: Dependency Versions
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
versions: ${{ steps.result.outputs.versions }}
|
||||
branches: ${{ steps.result.outputs.branches }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: supertokens/get-core-dependencies-action@main
|
||||
with:
|
||||
run-for: PR
|
||||
id: result
|
||||
new-core-version:
|
||||
environment: publish
|
||||
name: New core version
|
||||
runs-on: ubuntu-latest
|
||||
needs: [dependency-versions]
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Set up Python 3.11
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.11'
|
||||
- name: Run script
|
||||
env:
|
||||
SUPERTOKENS_API_KEY: ${{ secrets.SUPERTOKENS_API_KEY }}
|
||||
run: |
|
||||
python .github/helpers/register-new-core-version.py
|
||||
new-plugin-versions:
|
||||
environment: publish
|
||||
name: New plugin versions
|
||||
runs-on: ubuntu-latest
|
||||
needs: [dependency-versions]
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
plugin:
|
||||
- postgresql
|
||||
# no longer supported
|
||||
# - mysql
|
||||
# - mongodb
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
path: ./supertokens-plugin
|
||||
repository: supertokens/supertokens-${{ matrix.plugin }}-plugin
|
||||
ref: ${{ fromJson(needs.dependency-versions.outputs.branches)[matrix.plugin] }}
|
||||
fetch-depth: 0
|
||||
fetch-tags: true
|
||||
- name: Run script
|
||||
env:
|
||||
SUPERTOKENS_API_KEY: ${{ secrets.SUPERTOKENS_API_KEY }}
|
||||
PLUGIN_NAME: ${{ matrix.plugin }}
|
||||
run: |
|
||||
cd supertokens-plugin
|
||||
python ../.github/helpers/register-new-plugin-version.py
|
||||
unit-tests:
|
||||
name: Run unit tests
|
||||
needs: [new-core-version, new-plugin-versions]
|
||||
uses: ./.github/workflows/unit-test.yml
|
||||
wait-for-docker:
|
||||
name: Wait for Docker
|
||||
runs-on: ubuntu-latest
|
||||
needs: [new-core-version, new-plugin-versions]
|
||||
outputs:
|
||||
tag: ${{ steps.set_tag.outputs.TAG }}
|
||||
steps:
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.11'
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Wait for Docker build
|
||||
env:
|
||||
SHA: ${{ github.sha }}
|
||||
run: |
|
||||
python .github/helpers/wait-for-docker.py
|
||||
- name: set tag
|
||||
id: set_tag
|
||||
run: |
|
||||
echo "TAG=${GITHUB_REF}" | sed 's/refs\/heads\///g' | sed 's/\//_/g' >> $GITHUB_OUTPUT
|
||||
stress-tests:
|
||||
needs: [wait-for-docker]
|
||||
uses: ./.github/workflows/stress-tests.yml
|
||||
with:
|
||||
tag: ${{ needs.wait-for-docker.outputs.tag }}
|
||||
mark-as-passed:
|
||||
environment: publish
|
||||
needs: [dependency-versions, unit-tests, stress-tests]
|
||||
name: Mark as passed
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
plugin:
|
||||
- sqlite
|
||||
- postgresql
|
||||
# no longer supported
|
||||
# - mysql
|
||||
# - mongodb
|
||||
steps:
|
||||
- name: Mark plugin as passed
|
||||
if: matrix.plugin != 'sqlite' && fromJson(needs.dependency-versions.outputs.versions)[matrix.plugin] != ''
|
||||
uses: muhfaris/request-action@main
|
||||
with:
|
||||
url: https://api.supertokens.io/0/plugin
|
||||
method: PATCH
|
||||
headers: |
|
||||
{
|
||||
"Content-Type": "application/json",
|
||||
"api-version": "0"
|
||||
}
|
||||
body: |
|
||||
{
|
||||
"password": "${{ secrets.SUPERTOKENS_API_KEY }}",
|
||||
"version": "${{ fromJson(needs.dependency-versions.outputs.versions)[matrix.plugin] }}",
|
||||
"planType": "FREE",
|
||||
"name": "${{ matrix.plugin }}",
|
||||
"testPassed": true
|
||||
}
|
||||
- name: Mark core as passed
|
||||
if: matrix.plugin == 'sqlite' && fromJson(needs.dependency-versions.outputs.versions)['core'] != ''
|
||||
uses: muhfaris/request-action@main
|
||||
with:
|
||||
url: https://api.supertokens.io/0/core
|
||||
method: PATCH
|
||||
headers: |
|
||||
{
|
||||
"Content-Type": "application/json",
|
||||
"api-version": "0"
|
||||
}
|
||||
body: |
|
||||
{
|
||||
"password": "${{ secrets.SUPERTOKENS_API_KEY }}",
|
||||
"version": "${{ fromJson(needs.dependency-versions.outputs.versions)['core'] }}",
|
||||
"planType": "FREE",
|
||||
"testPassed": true
|
||||
}
|
||||
|
|
@ -0,0 +1,148 @@
|
|||
name: Do Release
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
core-version:
|
||||
description: 'Core version'
|
||||
required: true
|
||||
type: string
|
||||
plugin-interface-version:
|
||||
description: 'Plugin interface version'
|
||||
required: true
|
||||
type: string
|
||||
new-release-for-plugin-interface:
|
||||
description: 'New release for plugin interface'
|
||||
required: true
|
||||
type: boolean
|
||||
postgresql-plugin-version:
|
||||
description: 'Postgres plugin version'
|
||||
required: true
|
||||
new-release-for-postgresql-plugin:
|
||||
description: 'New release for postgres plugin'
|
||||
required: true
|
||||
type: boolean
|
||||
is-latest-release:
|
||||
description: 'Is this the latest release?'
|
||||
required: true
|
||||
type: boolean
|
||||
|
||||
jobs:
|
||||
dependency-branches:
|
||||
name: Dependency Branches
|
||||
environment: publish
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
branches: ${{ steps.result.outputs.branches }}
|
||||
versions: ${{ steps.result.outputs.versions }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: supertokens/get-core-dependencies-action@main
|
||||
id: result
|
||||
with:
|
||||
run-for: add-dev-tag
|
||||
core-version: ${{ github.event.inputs.core-version }}
|
||||
plugin-interface-version: ${{ github.event.inputs.plugin-interface-version }}
|
||||
postgresql-plugin-version: ${{ github.event.inputs.postgresql-plugin-version }}
|
||||
release-docker:
|
||||
environment: publish
|
||||
name: Release Docker
|
||||
runs-on: ubuntu-latest
|
||||
needs: dependency-branches
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
- name: Set up JDK 21.0.7
|
||||
uses: actions/setup-java@v2
|
||||
with:
|
||||
java-version: 21.0.7
|
||||
distribution: zulu
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ vars.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
- name: Tag and Push Docker Image
|
||||
run: |
|
||||
tag=${{ github.event.inputs.core-version }}
|
||||
major=$(echo $tag | cut -d. -f1)
|
||||
minor=$(echo $tag | cut -d. -f1,2)
|
||||
|
||||
bash .github/helpers/release-docker.sh supertokens/supertokens-dev-postgresql:$minor supertokens/supertokens-postgresql:$major
|
||||
bash .github/helpers/release-docker.sh supertokens/supertokens-dev-postgresql:$minor supertokens/supertokens-postgresql:$minor
|
||||
bash .github/helpers/release-docker.sh supertokens/supertokens-dev-postgresql:$minor supertokens/supertokens-postgresql:$tag
|
||||
|
||||
if [ "${{ github.event.inputs.is-latest-release }}" == "true" ]; then
|
||||
bash .github/helpers/release-docker.sh supertokens/supertokens-dev-postgresql:$minor supertokens/supertokens-postgresql:latest
|
||||
fi
|
||||
add-release-tag:
|
||||
environment: publish
|
||||
runs-on: ubuntu-latest
|
||||
needs: [dependency-branches, release-docker]
|
||||
steps:
|
||||
- name: Set up JDK 21.0.7
|
||||
uses: actions/setup-java@v2
|
||||
with:
|
||||
java-version: 21.0.7
|
||||
distribution: zulu
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
repository: supertokens/supertokens-root
|
||||
path: ./supertokens-root
|
||||
ref: master
|
||||
- name: Checkout supertokens-core
|
||||
run: |
|
||||
cd supertokens-root
|
||||
git clone https://${{ secrets.GH_TOKEN }}@github.com/supertokens/supertokens-core.git
|
||||
cd supertokens-core
|
||||
git checkout ${{ fromJson(needs.dependency-branches.outputs.branches)['core'] }}
|
||||
- name: Checkout supertokens-plugin-interface
|
||||
run: |
|
||||
cd supertokens-root
|
||||
git clone https://${{ secrets.GH_TOKEN }}@github.com/supertokens/supertokens-plugin-interface.git
|
||||
cd supertokens-plugin-interface
|
||||
git checkout ${{ fromJson(needs.dependency-branches.outputs.branches)['plugin-interface'] }}
|
||||
- name: Checkout supertokens-postgresql-plugin
|
||||
run: |
|
||||
cd supertokens-root
|
||||
git clone https://${{ secrets.GH_TOKEN }}@github.com/supertokens/supertokens-postgresql-plugin.git
|
||||
cd supertokens-postgresql-plugin
|
||||
git checkout ${{ fromJson(needs.dependency-branches.outputs.branches)['postgresql'] }}
|
||||
- name: Add release password
|
||||
run: |
|
||||
cd supertokens-root
|
||||
echo "${{ secrets.SUPERTOKENS_API_KEY }}" > releasePassword
|
||||
echo "${{ secrets.SUPERTOKENS_API_KEY }}" > apiPassword
|
||||
- name: Load Modules
|
||||
run: |
|
||||
cd supertokens-root
|
||||
echo "core,master
|
||||
plugin-interface,master
|
||||
postgresql-plugin,master
|
||||
" > modules.txt
|
||||
cat modules.txt
|
||||
./loadModules
|
||||
- name: Setup test env
|
||||
run: cd supertokens-root && ./utils/setupTestEnv --local
|
||||
- name: Git config
|
||||
run: |
|
||||
git config --global user.name "Supertokens Bot"
|
||||
git config --global user.email "<>"
|
||||
- name: Add release tag to plugin interface
|
||||
if: ${{ github.event.inputs.new-release-for-plugin-interface == 'true' }}
|
||||
run: |
|
||||
echo "Adding release tag to plugin interface"
|
||||
cd supertokens-root/supertokens-plugin-interface
|
||||
./addReleaseTag
|
||||
- name: Add release tag to postgres plugin
|
||||
if: ${{ github.event.inputs.new-release-for-postgresql-plugin == 'true' }}
|
||||
run: |
|
||||
echo "Adding release tag to postgres plugin"
|
||||
cd supertokens-root/supertokens-postgresql-plugin
|
||||
./addReleaseTag
|
||||
- name: Add release tag to core
|
||||
run: |
|
||||
echo "Adding release tag to core"
|
||||
cd supertokens-root/supertokens-core
|
||||
./addReleaseTag
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
name: "Enforcing changelog in PRs Workflow"
|
||||
on:
|
||||
pull_request:
|
||||
types: [ opened, synchronize, reopened, ready_for_review, labeled, unlabeled ]
|
||||
|
||||
jobs:
|
||||
# Enforces the update of a changelog file on every pull request
|
||||
changelog:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: dangoslen/changelog-enforcer@v2
|
||||
with:
|
||||
changeLogPath: 'CHANGELOG.md'
|
||||
skipLabels: 'Skip-Changelog'
|
||||
|
|
@ -1,20 +0,0 @@
|
|||
name: "Lint PR Title"
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types:
|
||||
- opened
|
||||
- reopened
|
||||
- edited
|
||||
- synchronize
|
||||
|
||||
jobs:
|
||||
pr-title:
|
||||
name: Lint PR title
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: amannn/action-semantic-pull-request@v3
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
validateSingleCommit: true
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
name: PR Checks
|
||||
on:
|
||||
pull_request:
|
||||
types: [ opened, synchronize, reopened, ready_for_review, labeled, unlabeled ]
|
||||
|
||||
jobs:
|
||||
pr-title:
|
||||
name: Lint PR title
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: amannn/action-semantic-pull-request@v3
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
validateSingleCommit: true
|
||||
changelog:
|
||||
name: Enforce Changelog
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: dangoslen/changelog-enforcer@v2
|
||||
with:
|
||||
changeLogPath: 'CHANGELOG.md'
|
||||
skipLabels: 'Skip-Changelog'
|
||||
unit-tests:
|
||||
name: Run unit tests
|
||||
uses: ./.github/workflows/unit-test.yml
|
||||
|
|
@ -3,10 +3,82 @@ on:
|
|||
push:
|
||||
branches:
|
||||
- "**"
|
||||
tags:
|
||||
- 'dev-*'
|
||||
|
||||
jobs:
|
||||
docker:
|
||||
dependency-branches:
|
||||
name: Dependency Branches
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
branches: ${{ steps.result.outputs.branches }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: supertokens/get-core-dependencies-action@main
|
||||
id: result
|
||||
with:
|
||||
run-for: PR
|
||||
core-branch: ${{ github.ref_name }}
|
||||
|
||||
docker:
|
||||
name: Docker
|
||||
runs-on: ubuntu-latest
|
||||
needs: dependency-branches
|
||||
outputs:
|
||||
tag: ${{ steps.set_tag.outputs.TAG }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
plugin:
|
||||
- postgresql
|
||||
# no longer supported
|
||||
# - mysql
|
||||
# - mongodb
|
||||
steps:
|
||||
- name: Set up JDK 21.0.7
|
||||
uses: actions/setup-java@v2
|
||||
with:
|
||||
java-version: 21.0.7
|
||||
distribution: zulu
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
repository: supertokens/supertokens-root
|
||||
path: ./supertokens-root
|
||||
ref: master
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
path: ./supertokens-root/supertokens-core
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
repository: supertokens/supertokens-plugin-interface
|
||||
path: ./supertokens-root/supertokens-plugin-interface
|
||||
ref: ${{ fromJson(needs.dependency-branches.outputs.branches)['plugin-interface'] }}
|
||||
- uses: actions/checkout@v2
|
||||
if: matrix.plugin != 'sqlite'
|
||||
with:
|
||||
repository: supertokens/supertokens-${{ matrix.plugin }}-plugin
|
||||
path: ./supertokens-root/supertokens-${{ matrix.plugin }}-plugin
|
||||
ref: ${{ fromJson(needs.dependency-branches.outputs.branches)[matrix.plugin] }}
|
||||
- name: Load Modules
|
||||
run: |
|
||||
cd supertokens-root
|
||||
echo "core,master
|
||||
plugin-interface,master
|
||||
${{ matrix.plugin }}-plugin,master
|
||||
" > modules.txt
|
||||
cat modules.txt
|
||||
./loadModules
|
||||
- name: Setup test env
|
||||
run: cd supertokens-root && ./utils/setupTestEnv --local
|
||||
- name: Generate config file
|
||||
run: |
|
||||
cd supertokens-root
|
||||
touch config_temp.yaml
|
||||
cat supertokens-core/config.yaml >> config_temp.yaml
|
||||
cat supertokens-${{ matrix.plugin }}-plugin/config.yaml >> config_temp.yaml
|
||||
mv config_temp.yaml config.yaml
|
||||
|
||||
- name: set tag
|
||||
id: set_tag
|
||||
run: |
|
||||
|
|
@ -17,26 +89,16 @@ jobs:
|
|||
with:
|
||||
username: ${{ vars.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
-
|
||||
name: Set up QEMU
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
# - name: Build and export to Docker
|
||||
# uses: docker/build-push-action@v6
|
||||
# with:
|
||||
# load: true
|
||||
# tags: ${{ env.TEST_TAG }}
|
||||
|
||||
# - name: Test
|
||||
# run: |
|
||||
# docker run --rm ${{ env.TEST_TAG }}
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
push: true
|
||||
tags: supertokens/supertokens-core:dev-branch-${{ steps.set_tag.outputs.TAG }}
|
||||
file: .github/helpers/Dockerfile
|
||||
context: ./supertokens-root
|
||||
tags: supertokens/supertokens-dev-${{ matrix.plugin }}:${{ steps.set_tag.outputs.TAG }}
|
||||
file: ./supertokens-root/supertokens-${{ matrix.plugin }}-plugin/.github/helpers/docker/Dockerfile
|
||||
platforms: linux/amd64,linux/arm64
|
||||
|
|
|
|||
|
|
@ -0,0 +1,47 @@
|
|||
name: Stress Tests
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
tag:
|
||||
description: 'Docker image tag to use'
|
||||
required: true
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
stress-tests:
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: '20'
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
cd stress-tests
|
||||
npm install
|
||||
- name: Update Docker image in compose
|
||||
run: |
|
||||
cd stress-tests
|
||||
sed -i 's|supertokens/supertokens-postgresql|supertokens/supertokens-dev-postgresql:${{ inputs.tag }}|' docker-compose.yml
|
||||
cat docker-compose.yml
|
||||
- name: Bring up the services
|
||||
run: |
|
||||
cd stress-tests
|
||||
docker compose up -d
|
||||
- name: Generate user jsons
|
||||
run: |
|
||||
cd stress-tests
|
||||
npm run generate-users
|
||||
- name: Run one million users test
|
||||
id: one-million-users
|
||||
run: |
|
||||
cd stress-tests
|
||||
npm run one-million-users | tee stress-tests.log
|
||||
- name: Display Test Statistics
|
||||
run: |
|
||||
echo "## Stress Test Results" >> $GITHUB_STEP_SUMMARY
|
||||
echo "| Test | Duration |" >> $GITHUB_STEP_SUMMARY
|
||||
echo "|------|----------|" >> $GITHUB_STEP_SUMMARY
|
||||
jq -r '.measurements[] | "| \(.title) | \(.formatted) |"' stress-tests/stats.json >> $GITHUB_STEP_SUMMARY
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
name: "Check if \"Run tests\" action succeeded"
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types:
|
||||
- opened
|
||||
- reopened
|
||||
- edited
|
||||
- synchronize
|
||||
|
||||
jobs:
|
||||
pr-run-test-action:
|
||||
name: Check if "Run tests" action succeeded
|
||||
timeout-minutes: 60
|
||||
concurrency:
|
||||
group: ${{ github.head_ref }}
|
||||
cancel-in-progress: true
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: node install
|
||||
run: cd ./.github/helpers && npm i
|
||||
- name: Calling github API
|
||||
run: cd ./.github/helpers && GITHUB_TOKEN=${{ github.token }} REPO=${{ github.repository }} RUN_ID=${{ github.run_id }} BRANCH=${{ github.head_ref }} JOB_ID=${{ github.job }} SOURCE_OWNER=${{ github.event.pull_request.head.repo.owner.login }} CURRENT_SHA=${{ github.event.pull_request.head.sha }} node node_modules/github-workflow-helpers/test-pass-check-pr.js
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
name: "Run tests"
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
pluginRepoOwnerName:
|
||||
description: 'supertokens-plugin-interface repo owner name'
|
||||
default: supertokens
|
||||
required: true
|
||||
pluginInterfaceBranch:
|
||||
description: 'supertokens-plugin-interface repos branch name'
|
||||
default: master
|
||||
required: true
|
||||
|
||||
jobs:
|
||||
test_job:
|
||||
name: Run tests
|
||||
timeout-minutes: 60
|
||||
runs-on: ubuntu-latest
|
||||
container: rishabhpoddar/supertokens_core_testing
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Cloning supertokens-root
|
||||
run: cd ../ && git clone https://github.com/supertokens/supertokens-root.git
|
||||
- name: Update Java 1
|
||||
run: update-alternatives --install "/usr/bin/java" "java" "/usr/java/jdk-15.0.1/bin/java" 2
|
||||
- name: Update Java 2
|
||||
run: update-alternatives --install "/usr/bin/javac" "javac" "/usr/java/jdk-15.0.1/bin/javac" 2
|
||||
- name: Modifying modules.txt in supertokens-root
|
||||
run: cd ../supertokens-root && echo "core,master\nplugin-interface,${{ github.event.inputs.pluginInterfaceBranch }},${{ github.event.inputs.pluginRepoOwnerName }}" > modules.txt
|
||||
- name: Contents of modules.txt
|
||||
run: cat ../supertokens-root/modules.txt
|
||||
- name: Running loadModules in supertokens-root
|
||||
run: cd ../supertokens-root && ./loadModules
|
||||
- name: Copying current supertokens-core branch into supertokens-root
|
||||
run: cd ../supertokens-root && rm -rf ./supertokens-core && cp -r ../supertokens-core ./
|
||||
- name: Building and running tests
|
||||
run: cd ../supertokens-root && ./startTestingEnv
|
||||
|
|
@ -0,0 +1,87 @@
|
|||
name: Unit Tests
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
|
||||
jobs:
|
||||
dependency-branches:
|
||||
name: Dependency Branches
|
||||
runs-on: ubuntu-22.04
|
||||
outputs:
|
||||
branches: ${{ steps.result.outputs.branches }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: supertokens/get-core-dependencies-action@main
|
||||
id: result
|
||||
with:
|
||||
run-for: PR
|
||||
core-branch: ${{ github.head_ref }}
|
||||
|
||||
test:
|
||||
name: Unit tests
|
||||
needs: dependency-branches
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
plugin:
|
||||
- sqlite
|
||||
- postgresql
|
||||
# no longer supported
|
||||
# - mysql
|
||||
# - mongodb
|
||||
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- name: Set up JDK 21.0.7
|
||||
uses: actions/setup-java@v2
|
||||
with:
|
||||
java-version: 21.0.7
|
||||
distribution: zulu
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
repository: supertokens/supertokens-root
|
||||
path: ./supertokens-root
|
||||
ref: master
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
path: ./supertokens-root/supertokens-core
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
repository: supertokens/supertokens-plugin-interface
|
||||
path: ./supertokens-root/supertokens-plugin-interface
|
||||
ref: ${{ fromJson(needs.dependency-branches.outputs.branches)['plugin-interface'] }}
|
||||
- uses: actions/checkout@v2
|
||||
if: matrix.plugin != 'sqlite'
|
||||
with:
|
||||
repository: supertokens/supertokens-${{ matrix.plugin }}-plugin
|
||||
path: ./supertokens-root/supertokens-${{ matrix.plugin }}-plugin
|
||||
ref: ${{ fromJson(needs.dependency-branches.outputs.branches)[matrix.plugin] }}
|
||||
- name: Load Modules
|
||||
run: |
|
||||
cd supertokens-root
|
||||
echo "core,master
|
||||
plugin-interface,master
|
||||
${{ matrix.plugin }}-plugin,master
|
||||
" > modules.txt
|
||||
cat modules.txt
|
||||
./loadModules
|
||||
- name: Setup test env
|
||||
run: cd supertokens-root && ./utils/setupTestEnv --local
|
||||
- name: Start ${{ matrix.plugin }} server
|
||||
if: matrix.plugin != 'sqlite'
|
||||
run: cd supertokens-root/supertokens-${{ matrix.plugin }}-plugin && ./startDb.sh
|
||||
- name: Run tests
|
||||
env:
|
||||
ST_PLUGIN_NAME: ${{ matrix.plugin }}
|
||||
run: |
|
||||
cd supertokens-root
|
||||
./gradlew test
|
||||
- name: Publish Test Report
|
||||
uses: mikepenz/action-junit-report@v5
|
||||
if: always()
|
||||
with:
|
||||
report_paths: '**/build/test-results/test/TEST-*.xml'
|
||||
detailed_summary: true
|
||||
include_passed: false
|
||||
annotate_notice: true
|
||||
|
|
@ -12,6 +12,7 @@ gradle-app.setting
|
|||
!cli/jar/**/*.jar
|
||||
!downloader/jar/**/*.jar
|
||||
!ee/jar/**/*.jar
|
||||
!src/main/resources/**/*.jar
|
||||
|
||||
*target*
|
||||
*.war
|
||||
|
|
|
|||
111
CHANGELOG.md
111
CHANGELOG.md
|
|
@ -7,6 +7,117 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|||
|
||||
## [Unreleased]
|
||||
|
||||
## [11.3.0]
|
||||
|
||||
- Adds SAML features
|
||||
- Fixes potential deadlock issue with `TelemetryProvider`
|
||||
- Adds DeadlockLogger as an utility for discovering deadlock issues
|
||||
|
||||
### Migration
|
||||
|
||||
```sql
|
||||
CREATE TABLE IF NOT EXISTS saml_clients (
|
||||
app_id VARCHAR(64) NOT NULL DEFAULT 'public',
|
||||
tenant_id VARCHAR(64) NOT NULL DEFAULT 'public',
|
||||
client_id VARCHAR(256) NOT NULL,
|
||||
client_secret TEXT,
|
||||
sso_login_url TEXT NOT NULL,
|
||||
redirect_uris TEXT NOT NULL,
|
||||
default_redirect_uri TEXT NOT NULL,
|
||||
idp_entity_id VARCHAR(256) NOT NULL,
|
||||
idp_signing_certificate TEXT NOT NULL,
|
||||
allow_idp_initiated_login BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
enable_request_signing BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
created_at BIGINT NOT NULL,
|
||||
updated_at BIGINT NOT NULL,
|
||||
CONSTRAINT saml_clients_pkey PRIMARY KEY(app_id, tenant_id, client_id),
|
||||
CONSTRAINT saml_clients_idp_entity_id_key UNIQUE (app_id, tenant_id, idp_entity_id),
|
||||
CONSTRAINT saml_clients_app_id_fkey FOREIGN KEY(app_id) REFERENCES apps (app_id) ON DELETE CASCADE,
|
||||
CONSTRAINT saml_clients_tenant_id_fkey FOREIGN KEY(app_id, tenant_id) REFERENCES tenants (app_id, tenant_id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS saml_clients_app_id_tenant_id_index ON saml_clients (app_id, tenant_id);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS saml_relay_state (
|
||||
app_id VARCHAR(64) NOT NULL DEFAULT 'public',
|
||||
tenant_id VARCHAR(64) NOT NULL DEFAULT 'public',
|
||||
relay_state VARCHAR(256) NOT NULL,
|
||||
client_id VARCHAR(256) NOT NULL,
|
||||
state TEXT NOT NULL,
|
||||
redirect_uri TEXT NOT NULL,
|
||||
created_at BIGINT NOT NULL,
|
||||
CONSTRAINT saml_relay_state_pkey PRIMARY KEY(app_id, tenant_id, relay_state),
|
||||
CONSTRAINT saml_relay_state_app_id_fkey FOREIGN KEY(app_id) REFERENCES apps (app_id) ON DELETE CASCADE,
|
||||
CONSTRAINT saml_relay_state_tenant_id_fkey FOREIGN KEY(app_id, tenant_id) REFERENCES tenants (app_id, tenant_id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS saml_relay_state_app_id_tenant_id_index ON saml_relay_state (app_id, tenant_id);
|
||||
CREATE INDEX IF NOT EXISTS saml_relay_state_expires_at_index ON saml_relay_state (expires_at);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS saml_claims (
|
||||
app_id VARCHAR(64) NOT NULL DEFAULT 'public',
|
||||
tenant_id VARCHAR(64) NOT NULL DEFAULT 'public',
|
||||
client_id VARCHAR(256) NOT NULL,
|
||||
code VARCHAR(256) NOT NULL,
|
||||
claims TEXT NOT NULL,
|
||||
created_at BIGINT NOT NULL,
|
||||
CONSTRAINT saml_claims_pkey PRIMARY KEY(app_id, tenant_id, code),
|
||||
CONSTRAINT saml_claims_app_id_fkey FOREIGN KEY(app_id) REFERENCES apps (app_id) ON DELETE CASCADE,
|
||||
CONSTRAINT saml_claims_tenant_id_fkey FOREIGN KEY(app_id, tenant_id) REFERENCES tenants (app_id, tenant_id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS saml_claims_app_id_tenant_id_index ON saml_claims (app_id, tenant_id);
|
||||
CREATE INDEX IF NOT EXISTS saml_claims_expires_at_index ON saml_claims (expires_at);
|
||||
```
|
||||
|
||||
## [11.2.1]
|
||||
|
||||
- Fixes deadlock issue with `ResourceDistributor`
|
||||
- Fixes race issues with Refreshing OAuth token
|
||||
|
||||
## [11.2.0]
|
||||
|
||||
- Adds opentelemetry-javaagent to the core distribution
|
||||
|
||||
## [11.1.1]
|
||||
|
||||
- Updates tomcat-embed to 11.0.12 because of security vulnerabilities
|
||||
|
||||
## [11.1.0]
|
||||
|
||||
- Adds hikari logs to opentelemetry
|
||||
- Fetches core and plugin config from env
|
||||
- Open Telemetry configuration is now optional
|
||||
- Migrates API calls from supertokens.io to supertokens.com
|
||||
|
||||
## [11.0.5]
|
||||
|
||||
- Adds all logs to telemetry which were logged with `io/supertokens/output/Logging.java`
|
||||
- Upgrades the embedded tomcat to 11.0.8 because of security vulnerabilities
|
||||
- Adds back previously removed `implementationDependencies.json`, but now it is generated by the build process
|
||||
|
||||
## [11.0.4]
|
||||
|
||||
- Fixes user to roles association in bulk import users when the user is not a primary user
|
||||
|
||||
## [11.0.3]
|
||||
|
||||
- Fixes BatchUpdateException checks and error handling to prevent bulk import users stuck in `PROCESSING` state
|
||||
- Adds more DEBUG logging to the bulk import users process
|
||||
|
||||
## [11.0.2]
|
||||
|
||||
- Fixes `AuthRecipe#getUserByAccountInfo` to consider the tenantId instead of the appId when fetching the webauthn user
|
||||
|
||||
## [11.0.1]
|
||||
|
||||
- Upgrades the embedded tomcat 11.0.6 and logback classic to 1.5.13 because of security vulnerabilities
|
||||
|
||||
## [11.0.0]
|
||||
|
||||
- Migrates tests to Github Actions
|
||||
- Updates JRE to 21.
|
||||
|
||||
## [10.1.4]
|
||||
|
||||
- Fixes bulk migration user roles association when there is no external userId assigned to the user
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ We're happy to help!:raised_hands:
|
|||
### Local Setup Prerequisites
|
||||
|
||||
- OS: Linux or macOS. Or if using Windows, you need to use [wsl2](https://docs.microsoft.com/en-us/windows/wsl/about).
|
||||
- JDK: openjdk 15.0.1. Installation instructions for Mac and Linux can be found
|
||||
- JDK: openjdk 21.0.7. Installation instructions for Mac and Linux can be found
|
||||
in [our wiki](https://github.com/supertokens/supertokens-core/wiki/Installing-OpenJDK-for-Mac-and-Linux)
|
||||
- IDE: [IntelliJ](https://www.jetbrains.com/idea/download/)(recommended) or equivalent IDE
|
||||
|
||||
|
|
|
|||
|
|
@ -256,7 +256,7 @@ If you think this is a project you could use in the future, please :star2: this
|
|||
</tr>
|
||||
<tr>
|
||||
<td align="center"><a href="https://github.com/Lehoczky"><img src="https://avatars.githubusercontent.com/u/31937175?v=4" width="100px;" alt=""/><br /><sub><b>Lehoczky Zoltán</b></sub></a></td>
|
||||
<td align="center"><a href="https://github.com/virajkanwade"><img src="https://avatars.githubusercontent.com/u/316111?v=4" width="100px;" alt=""/><br /><sub><b>Viraj Kanwade</b></sub></a></td>
|
||||
<td align="center"><a href="https://github.com/mavwolverine"><img src="https://avatars.githubusercontent.com/u/316111?v=4" width="100px;" alt=""/><br /><sub><b>Viraj Kanwade</b></sub></a></td>
|
||||
<td align="center"><a href="https://github.com/anuragmerndev"><img src="https://avatars.githubusercontent.com/u/144275260?v=4" width="100px;" alt=""/><br /><sub><b>Anurag Srivastava</b></sub></a></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
|
|
|||
72
build.gradle
72
build.gradle
|
|
@ -8,6 +8,8 @@
|
|||
|
||||
plugins {
|
||||
id 'application'
|
||||
id 'java-library'
|
||||
id "io.freefair.aspectj" version "8.13" //same as gradle version!
|
||||
}
|
||||
compileJava { options.encoding = "UTF-8" }
|
||||
compileTestJava { options.encoding = "UTF-8" }
|
||||
|
|
@ -19,18 +21,25 @@ compileTestJava { options.encoding = "UTF-8" }
|
|||
// }
|
||||
//}
|
||||
|
||||
version = "10.1.4"
|
||||
java {
|
||||
toolchain {
|
||||
languageVersion.set(JavaLanguageVersion.of(21))
|
||||
}
|
||||
}
|
||||
|
||||
version = "11.3.0"
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
maven { url 'https://build.shibboleth.net/nexus/content/repositories/releases/' }
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
// https://mvnrepository.com/artifact/com.google.code.gson/gson
|
||||
// if this changes, remember to also change in the ee folder's build.gradle
|
||||
implementation group: 'com.google.code.gson', name: 'gson', version: '2.3.1'
|
||||
implementation group: 'com.google.code.gson', name: 'gson', version: '2.13.1'
|
||||
|
||||
// https://mvnrepository.com/artifact/com.fasterxml.jackson.dataformat/jackson-dataformat-yaml
|
||||
implementation group: 'com.fasterxml.jackson.dataformat', name: 'jackson-dataformat-yaml', version: '2.18.2'
|
||||
|
|
@ -41,11 +50,8 @@ dependencies {
|
|||
// https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-core
|
||||
implementation group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.18.2'
|
||||
|
||||
// https://mvnrepository.com/artifact/ch.qos.logback/logback-classic
|
||||
implementation group: 'ch.qos.logback', name: 'logback-classic', version: '1.4.14'
|
||||
|
||||
// https://mvnrepository.com/artifact/org.apache.tomcat.embed/tomcat-embed-core
|
||||
implementation group: 'org.apache.tomcat.embed', name: 'tomcat-embed-core', version: '10.1.18'
|
||||
api group: 'org.apache.tomcat.embed', name: 'tomcat-embed-core', version: '11.0.12'
|
||||
|
||||
// https://mvnrepository.com/artifact/com.google.code.findbugs/jsr305
|
||||
implementation group: 'com.google.code.findbugs', name: 'jsr305', version: '3.0.2'
|
||||
|
|
@ -80,6 +86,28 @@ dependencies {
|
|||
// https://mvnrepository.com/artifact/com.webauthn4j/webauthn4j-core
|
||||
implementation group: 'com.webauthn4j', name: 'webauthn4j-core', version: '0.28.6.RELEASE'
|
||||
|
||||
implementation platform("io.opentelemetry.instrumentation:opentelemetry-instrumentation-bom-alpha:2.17.0-alpha")
|
||||
|
||||
// Open SAML
|
||||
implementation group: 'org.opensaml', name: 'opensaml-core', version: '4.3.1'
|
||||
implementation group: 'org.opensaml', name: 'opensaml-saml-impl', version: '4.3.1'
|
||||
implementation group: 'org.opensaml', name: 'opensaml-security-impl', version: '4.3.1'
|
||||
implementation group: 'org.opensaml', name: 'opensaml-profile-impl', version: '4.3.1'
|
||||
implementation group: 'org.opensaml', name: 'opensaml-xmlsec-impl', version: '4.3.1'
|
||||
|
||||
implementation("ch.qos.logback:logback-core:1.5.18")
|
||||
implementation("ch.qos.logback:logback-classic:1.5.18")
|
||||
|
||||
// OpenTelemetry core
|
||||
implementation("io.opentelemetry:opentelemetry-sdk")
|
||||
implementation("io.opentelemetry:opentelemetry-exporter-otlp")
|
||||
implementation("io.opentelemetry:opentelemetry-exporter-logging")
|
||||
implementation("io.opentelemetry:opentelemetry-api")
|
||||
|
||||
implementation("io.opentelemetry.semconv:opentelemetry-semconv")
|
||||
|
||||
implementation('org.aspectj:aspectjrt:1.9.24')
|
||||
|
||||
compileOnly project(":supertokens-plugin-interface")
|
||||
|
||||
// this is so that we can find plugin-interface jar while testing
|
||||
|
|
@ -107,43 +135,47 @@ jar {
|
|||
}
|
||||
|
||||
|
||||
task copyJars(type: Copy) {
|
||||
into "$buildDir/dependencies"
|
||||
tasks.register('copyJars', Copy) {
|
||||
from configurations.runtimeClasspath
|
||||
into layout.buildDirectory.dir("dependencies")
|
||||
}
|
||||
|
||||
test {
|
||||
jvmArgs '-Djava.security.egd=file:/dev/urandom'
|
||||
jvmArgs = ['-Djava.security.egd=file:/dev/urandom',
|
||||
"--add-opens=java.base/java.lang=ALL-UNNAMED",
|
||||
"--add-opens=java.base/java.util=ALL-UNNAMED",
|
||||
"--add-opens=java.base/java.util.concurrent=ALL-UNNAMED"]
|
||||
testLogging {
|
||||
outputs.upToDateWhen { false }
|
||||
showStandardStreams = true
|
||||
}
|
||||
maxParallelForks = Runtime.runtime.availableProcessors()
|
||||
}
|
||||
|
||||
import org.gradle.api.tasks.testing.logging.TestExceptionFormat
|
||||
import org.gradle.api.tasks.testing.logging.TestLogEvent
|
||||
|
||||
tasks.withType(Test) {
|
||||
tasks.withType(Test).configureEach {
|
||||
testLogging {
|
||||
// set options for log level LIFECYCLE
|
||||
events TestLogEvent.FAILED,
|
||||
events = [TestLogEvent.FAILED,
|
||||
TestLogEvent.PASSED,
|
||||
TestLogEvent.SKIPPED,
|
||||
TestLogEvent.STANDARD_OUT
|
||||
exceptionFormat TestExceptionFormat.FULL
|
||||
showExceptions true
|
||||
showCauses true
|
||||
showStackTraces true
|
||||
TestLogEvent.STANDARD_OUT]
|
||||
exceptionFormat = TestExceptionFormat.FULL
|
||||
showExceptions = true
|
||||
showCauses = true
|
||||
showStackTraces = true
|
||||
|
||||
// set options for log level DEBUG and INFO
|
||||
debug {
|
||||
events TestLogEvent.STARTED,
|
||||
events = [TestLogEvent.STARTED,
|
||||
TestLogEvent.FAILED,
|
||||
TestLogEvent.PASSED,
|
||||
TestLogEvent.SKIPPED,
|
||||
TestLogEvent.STANDARD_ERROR,
|
||||
TestLogEvent.STANDARD_OUT
|
||||
exceptionFormat TestExceptionFormat.FULL
|
||||
TestLogEvent.STANDARD_OUT]
|
||||
exceptionFormat = TestExceptionFormat.FULL
|
||||
}
|
||||
info.events = debug.events
|
||||
info.exceptionFormat = debug.exceptionFormat
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ plugins {
|
|||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
|
||||
maven { url 'https://build.shibboleth.net/nexus/content/repositories/releases/' }
|
||||
}
|
||||
|
||||
application {
|
||||
|
|
@ -16,13 +18,13 @@ jar {
|
|||
|
||||
dependencies {
|
||||
// https://mvnrepository.com/artifact/com.google.code.gson/gson
|
||||
implementation group: 'com.google.code.gson', name: 'gson', version: '2.3.1'
|
||||
implementation group: 'com.google.code.gson', name: 'gson', version: '2.13.1'
|
||||
|
||||
// https://mvnrepository.com/artifact/com.fasterxml.jackson.dataformat/jackson-dataformat-yaml
|
||||
implementation group: 'com.fasterxml.jackson.dataformat', name: 'jackson-dataformat-yaml', version: '2.16.1'
|
||||
implementation group: 'com.fasterxml.jackson.dataformat', name: 'jackson-dataformat-yaml', version: '2.18.2'
|
||||
|
||||
// https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-core
|
||||
implementation group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.16.1'
|
||||
implementation group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.18.2'
|
||||
|
||||
// https://mvnrepository.com/artifact/de.mkammerer/argon2-jvm
|
||||
implementation group: 'de.mkammerer', name: 'argon2-jvm', version: '2.11'
|
||||
|
|
@ -33,9 +35,9 @@ dependencies {
|
|||
testImplementation group: 'junit', name: 'junit', version: '4.12'
|
||||
}
|
||||
|
||||
task copyJars(type: Copy) {
|
||||
into "$buildDir/dependencies"
|
||||
tasks.register('copyJars', Copy) {
|
||||
from configurations.runtimeClasspath
|
||||
into layout.buildDirectory.dir("dependencies")
|
||||
}
|
||||
|
||||
test {
|
||||
|
|
@ -55,10 +57,10 @@ tasks.withType(Test) {
|
|||
TestLogEvent.PASSED,
|
||||
TestLogEvent.SKIPPED,
|
||||
TestLogEvent.STANDARD_OUT
|
||||
exceptionFormat TestExceptionFormat.FULL
|
||||
showExceptions true
|
||||
showCauses true
|
||||
showStackTraces true
|
||||
exceptionFormat = TestExceptionFormat.FULL
|
||||
showExceptions = true
|
||||
showCauses = true
|
||||
showStackTraces = true
|
||||
|
||||
// set options for log level DEBUG and INFO
|
||||
debug {
|
||||
|
|
@ -68,7 +70,7 @@ tasks.withType(Test) {
|
|||
TestLogEvent.SKIPPED,
|
||||
TestLogEvent.STANDARD_ERROR,
|
||||
TestLogEvent.STANDARD_OUT
|
||||
exceptionFormat TestExceptionFormat.FULL
|
||||
exceptionFormat = TestExceptionFormat.FULL
|
||||
}
|
||||
info.events = debug.events
|
||||
info.exceptionFormat = debug.exceptionFormat
|
||||
|
|
|
|||
|
|
@ -1,55 +1,40 @@
|
|||
{
|
||||
"_comment": "Contains list of implementation dependencies URL for this project",
|
||||
"list": [
|
||||
{
|
||||
"jar": "https://repo1.maven.org/maven2/com/google/code/gson/gson/2.3.1/gson-2.3.1.jar",
|
||||
"name": "Gson 2.3.1",
|
||||
"src": "https://repo1.maven.org/maven2/com/google/code/gson/gson/2.3.1/gson-2.3.1-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar": "https://repo1.maven.org/maven2/com/fasterxml/jackson/dataformat/jackson-dataformat-yaml/2.16.1/jackson-dataformat-yaml-2.16.1.jar",
|
||||
"name": "Jackson Dataformat 2.16.1",
|
||||
"src": "https://repo1.maven.org/maven2/com/fasterxml/jackson/dataformat/jackson-dataformat-yaml/2.16.1/jackson-dataformat-yaml-2.16.1-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar": "https://repo1.maven.org/maven2/org/yaml/snakeyaml/2.2/snakeyaml-2.2.jar",
|
||||
"name": "SnakeYAML 2.2",
|
||||
"src": "https://repo1.maven.org/maven2/org/yaml/snakeyaml/2.2/snakeyaml-2.2-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar": "https://repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-core/2.16.1/jackson-core-2.16.1.jar",
|
||||
"name": "Jackson core 2.16.1",
|
||||
"src": "https://repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-core/2.16.1/jackson-core-2.16.1-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar": "https://repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-databind/2.16.1/jackson-databind-2.16.1.jar",
|
||||
"name": "Jackson databind 2.16.1",
|
||||
"src": "https://repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-databind/2.16.1/jackson-databind-2.16.1-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar": "https://repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-annotations/2.16.1/jackson-annotations-2.16.1.jar",
|
||||
"name": "Jackson annotation 2.16.1",
|
||||
"src": "https://repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-annotations/2.16.1/jackson-annotations-2.16.1-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar": "https://repo1.maven.org/maven2/de/mkammerer/argon2-jvm/2.11/argon2-jvm-2.11.jar",
|
||||
"name": "Argon2-jvm 2.11",
|
||||
"src": "https://repo1.maven.org/maven2/de/mkammerer/argon2-jvm/2.11/argon2-jvm-2.11-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar": "https://repo1.maven.org/maven2/de/mkammerer/argon2-jvm-nolibs/2.11/argon2-jvm-nolibs-2.11.jar",
|
||||
"name": "Argon2-jvm no libs 2.11",
|
||||
"src": "https://repo1.maven.org/maven2/de/mkammerer/argon2-jvm-nolibs/2.11/argon2-jvm-nolibs-2.11-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar": "https://repo1.maven.org/maven2/org/mindrot/jbcrypt/0.4/jbcrypt-0.4.jar",
|
||||
"name": "SQLite JDBC Driver 3.30.1",
|
||||
"src": "https://repo1.maven.org/maven2/org/mindrot/jbcrypt/0.4/jbcrypt-0.4-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar": "https://repo1.maven.org/maven2/net/java/dev/jna/jna/5.8.0/jna-5.8.0.jar",
|
||||
"name": "JNA 5.8.0",
|
||||
"src": "https://repo1.maven.org/maven2/net/java/dev/jna/jna/5.8.0/jna-5.8.0-sources.jar"
|
||||
}
|
||||
]
|
||||
"_comment": "Contains list of implementation dependencies URL for this project. This is a generated file, don't modify the contents by hand.",
|
||||
"list": [
|
||||
{
|
||||
"jar":"https://repo.maven.apache.org/maven2/com/google/code/gson/gson/2.13.1/gson-2.13.1.jar",
|
||||
"name":"gson 2.13.1",
|
||||
"src":"https://repo.maven.apache.org/maven2/com/google/code/gson/gson/2.13.1/gson-2.13.1-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar":"https://repo.maven.apache.org/maven2/com/google/errorprone/error_prone_annotations/2.38.0/error_prone_annotations-2.38.0.jar",
|
||||
"name":"error_prone_annotations 2.38.0",
|
||||
"src":"https://repo.maven.apache.org/maven2/com/google/errorprone/error_prone_annotations/2.38.0/error_prone_annotations-2.38.0-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar":"https://repo.maven.apache.org/maven2/com/fasterxml/jackson/dataformat/jackson-dataformat-yaml/2.18.2/jackson-dataformat-yaml-2.18.2.jar",
|
||||
"name":"jackson-dataformat-yaml 2.18.2",
|
||||
"src":"https://repo.maven.apache.org/maven2/com/fasterxml/jackson/dataformat/jackson-dataformat-yaml/2.18.2/jackson-dataformat-yaml-2.18.2-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar":"https://repo.maven.apache.org/maven2/org/yaml/snakeyaml/2.3/snakeyaml-2.3.jar",
|
||||
"name":"snakeyaml 2.3",
|
||||
"src":"https://repo.maven.apache.org/maven2/org/yaml/snakeyaml/2.3/snakeyaml-2.3-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar":"https://repo.maven.apache.org/maven2/com/fasterxml/jackson/core/jackson-databind/2.18.2/jackson-databind-2.18.2.jar",
|
||||
"name":"jackson-databind 2.18.2",
|
||||
"src":"https://repo.maven.apache.org/maven2/com/fasterxml/jackson/core/jackson-databind/2.18.2/jackson-databind-2.18.2-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar":"https://repo.maven.apache.org/maven2/de/mkammerer/argon2-jvm/2.11/argon2-jvm-2.11.jar",
|
||||
"name":"argon2-jvm 2.11",
|
||||
"src":"https://repo.maven.apache.org/maven2/de/mkammerer/argon2-jvm/2.11/argon2-jvm-2.11-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar":"https://repo.maven.apache.org/maven2/org/mindrot/jbcrypt/0.4/jbcrypt-0.4.jar",
|
||||
"name":"jbcrypt 0.4",
|
||||
"src":"https://repo.maven.apache.org/maven2/org/mindrot/jbcrypt/0.4/jbcrypt-0.4-sources.jar"
|
||||
}
|
||||
]
|
||||
}
|
||||
BIN
cli/jar/cli.jar
BIN
cli/jar/cli.jar
Binary file not shown.
|
|
@ -43,12 +43,38 @@ public class StartHandler extends CommandHandler {
|
|||
String host = CLIOptionsParser.parseOption("--host", args);
|
||||
boolean foreground = CLIOptionsParser.hasKey("--foreground", args);
|
||||
boolean forceNoInMemDB = CLIOptionsParser.hasKey("--no-in-mem-db", args);
|
||||
boolean javaagentEnabled = CLIOptionsParser.hasKey("--javaagent", args);
|
||||
boolean jmxEnabled = CLIOptionsParser.hasKey("--jmx", args);
|
||||
String jmxPort = CLIOptionsParser.parseOption("--jmx-port", args);
|
||||
String jmxAuthenticate = CLIOptionsParser.parseOption("--jmx-authenticate", args);
|
||||
String jmxSSL = CLIOptionsParser.parseOption("--jmx-ssl", args);
|
||||
|
||||
List<String> commands = new ArrayList<>();
|
||||
if (OperatingSystem.getOS() == OperatingSystem.OS.WINDOWS) {
|
||||
commands.add(installationDir + "jre\\bin\\java.exe");
|
||||
commands.add("-classpath");
|
||||
commands.add("\"" + installationDir + "core\\*\";\"" + installationDir + "plugin-interface\\*\"");
|
||||
if (javaagentEnabled) {
|
||||
commands.add("-javaagent:\"" + installationDir + "agent\\opentelemetry-javaagent.jar\"");
|
||||
}
|
||||
if (jmxEnabled) {
|
||||
commands.add("-Dcom.sun.management.jmxremote");
|
||||
if (jmxPort != null) {
|
||||
commands.add("-Dcom.sun.management.jmxremote.port=" + jmxPort);
|
||||
} else {
|
||||
commands.add("-Dcom.sun.management.jmxremote.port=9010");
|
||||
}
|
||||
if (jmxAuthenticate != null) {
|
||||
commands.add("-Dcom.sun.management.jmxremote.authenticate=" + jmxAuthenticate);
|
||||
} else {
|
||||
commands.add("-Dcom.sun.management.jmxremote.authenticate=false");
|
||||
}
|
||||
if (jmxSSL != null) {
|
||||
commands.add("-Dcom.sun.management.jmxremote.ssl=" + jmxSSL);
|
||||
} else {
|
||||
commands.add("-Dcom.sun.management.jmxremote.ssl=false");
|
||||
}
|
||||
}
|
||||
if (space != null) {
|
||||
commands.add("-Xmx" + space + "M");
|
||||
}
|
||||
|
|
@ -77,6 +103,27 @@ public class StartHandler extends CommandHandler {
|
|||
commands.add("-classpath");
|
||||
commands.add(
|
||||
installationDir + "core/*:" + installationDir + "plugin-interface/*:" + installationDir + "ee/*");
|
||||
if (javaagentEnabled) {
|
||||
commands.add("-javaagent:" + installationDir + "agent/opentelemetry-javaagent.jar");
|
||||
}
|
||||
if (jmxEnabled) {
|
||||
commands.add("-Dcom.sun.management.jmxremote");
|
||||
if (jmxPort != null) {
|
||||
commands.add("-Dcom.sun.management.jmxremote.port=" + jmxPort);
|
||||
} else {
|
||||
commands.add("-Dcom.sun.management.jmxremote.port=9010");
|
||||
}
|
||||
if (jmxAuthenticate != null) {
|
||||
commands.add("-Dcom.sun.management.jmxremote.authenticate=" + jmxAuthenticate);
|
||||
} else {
|
||||
commands.add("-Dcom.sun.management.jmxremote.authenticate=false");
|
||||
}
|
||||
if (jmxSSL != null) {
|
||||
commands.add("-Dcom.sun.management.jmxremote.ssl=" + jmxSSL);
|
||||
} else {
|
||||
commands.add("-Dcom.sun.management.jmxremote.ssl=false");
|
||||
}
|
||||
}
|
||||
if (space != null) {
|
||||
commands.add("-Xmx" + space + "M");
|
||||
}
|
||||
|
|
@ -101,6 +148,7 @@ public class StartHandler extends CommandHandler {
|
|||
if (!foreground) {
|
||||
try {
|
||||
ProcessBuilder pb = new ProcessBuilder(commands);
|
||||
Logging.info("Command to be run: " + String.join(" ", pb.command()));
|
||||
pb.redirectErrorStream(true);
|
||||
Process process = pb.start();
|
||||
try (InputStreamReader in = new InputStreamReader(process.getInputStream());
|
||||
|
|
@ -181,6 +229,13 @@ public class StartHandler extends CommandHandler {
|
|||
new Option("--foreground", "Runs this instance of SuperTokens in the foreground (not as a daemon)"));
|
||||
options.add(
|
||||
new Option("--with-temp-dir", "Uses the passed dir as temp dir, instead of the internal default."));
|
||||
options.add(new Option("--javaagent", "Enables the OpenTelemetry Javaagent for tracing and metrics."));
|
||||
options.add(new Option("--jmx", "Enables JMX management and monitoring."));
|
||||
options.add(new Option("--jmx-port", "Sets the port for JMX. Defaults to 9010 if --jmx is passed."));
|
||||
options.add(new Option("--jmx-authenticate",
|
||||
"Sets whether JMX authentication is enabled or not. Defaults to false if --jmx is passed."));
|
||||
options.add(new Option("--jmx-ssl",
|
||||
"Sets whether JMX SSL is enabled or not. Defaults to false if --jmx is passed."));
|
||||
return options;
|
||||
}
|
||||
|
||||
|
|
|
|||
19
config.yaml
19
config.yaml
|
|
@ -182,3 +182,22 @@ core_config_version: 0
|
|||
# (DIFFERENT_ACROSS_APPS | OPTIONAL | Default: 3600000) long value. Time in milliseconds for how long a webauthn
|
||||
# account recovery token is valid for.
|
||||
# webauthn_recover_account_token_lifetime:
|
||||
|
||||
# (OPTIONAL | Default: null) string value. The URL of the OpenTelemetry collector to which the core
|
||||
# will send telemetry data. This should be in the format http://<host>:<port> or https://<host>:<port>.
|
||||
# otel_collector_connection_uri:
|
||||
|
||||
# (OPTIONAL | Default: false) boolean value. Enables or disables the deadlock logger.
|
||||
# deadlock_logger_enable:
|
||||
|
||||
# (OPTIONAL | Default: null) string value. If specified, uses this URL as ACS URL for handling legacy SAML clients
|
||||
# saml_legacy_acs_url:
|
||||
|
||||
# (OPTIONAL | Default: https://saml.supertokens.com) string value. Service provider's entity ID.
|
||||
# saml_sp_entity_id:
|
||||
|
||||
# OPTIONAL | Default: 300000) long value. Duration for which SAML claims will be valid before it is consumed
|
||||
# saml_claims_validity:
|
||||
|
||||
# OPTIONAL | Default: 300000) long value. Duration for which SAML relay state will be valid before it is consumed
|
||||
# saml_relay_state_validity:
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@
|
|||
"5.0",
|
||||
"5.1",
|
||||
"5.2",
|
||||
"5.3"
|
||||
"5.3",
|
||||
"5.4"
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -182,3 +182,22 @@ disable_telemetry: true
|
|||
# (DIFFERENT_ACROSS_APPS | OPTIONAL | Default: 3600000) long value. Time in milliseconds for how long a webauthn
|
||||
# account recovery token is valid for.
|
||||
# webauthn_recover_account_token_lifetime:
|
||||
|
||||
# (OPTIONAL | Default: null) string value. The URL of the OpenTelemetry collector to which the core
|
||||
# will send telemetry data. This should be in the format http://<host>:<port> or https://<host>:<port>.
|
||||
# otel_collector_connection_uri:
|
||||
|
||||
# (OPTIONAL | Default: false) boolean value. Enables or disables the deadlock logger.
|
||||
# deadlock_logger_enable:
|
||||
|
||||
# (OPTIONAL | Default: null) string value. If specified, uses this URL as ACS URL for handling legacy SAML clients
|
||||
saml_legacy_acs_url: "http://localhost:5225/api/oauth/saml"
|
||||
|
||||
# (OPTIONAL | Default: https://saml.supertokens.com) string value. Service provider's entity ID.
|
||||
# saml_sp_entity_id:
|
||||
|
||||
# OPTIONAL | Default: 300000) long value. Duration for which SAML claims will be valid before it is consumed
|
||||
# saml_claims_validity:
|
||||
|
||||
# OPTIONAL | Default: 300000) long value. Duration for which SAML relay state will be valid before it is consumed
|
||||
# saml_relay_state_validity:
|
||||
|
|
|
|||
|
|
@ -18,9 +18,9 @@ dependencies {
|
|||
testImplementation group: 'junit', name: 'junit', version: '4.12'
|
||||
}
|
||||
|
||||
task copyJars(type: Copy) {
|
||||
into "$buildDir/dependencies"
|
||||
tasks.register('copyJars', Copy) {
|
||||
from configurations.runtimeClasspath
|
||||
into layout.buildDirectory.dir("dependencies")
|
||||
}
|
||||
|
||||
test {
|
||||
|
|
@ -56,10 +56,10 @@ tasks.withType(Test) {
|
|||
TestLogEvent.PASSED,
|
||||
TestLogEvent.SKIPPED,
|
||||
TestLogEvent.STANDARD_OUT
|
||||
exceptionFormat TestExceptionFormat.FULL
|
||||
showExceptions true
|
||||
showCauses true
|
||||
showStackTraces true
|
||||
exceptionFormat = TestExceptionFormat.FULL
|
||||
showExceptions = true
|
||||
showCauses = true
|
||||
showStackTraces = true
|
||||
|
||||
// set options for log level DEBUG and INFO
|
||||
debug {
|
||||
|
|
@ -69,7 +69,7 @@ tasks.withType(Test) {
|
|||
TestLogEvent.SKIPPED,
|
||||
TestLogEvent.STANDARD_ERROR,
|
||||
TestLogEvent.STANDARD_OUT
|
||||
exceptionFormat TestExceptionFormat.FULL
|
||||
exceptionFormat = TestExceptionFormat.FULL
|
||||
}
|
||||
info.events = debug.events
|
||||
info.exceptionFormat = debug.exceptionFormat
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -14,6 +14,7 @@ exitIfNeeded
|
|||
|
||||
exitIfNeeded
|
||||
|
||||
|
||||
(cd ../../ && ./gradlew :$prefix-core:downloader:copyJars < /dev/null)
|
||||
|
||||
exitIfNeeded
|
||||
|
|
|
|||
|
|
@ -2,10 +2,12 @@ plugins {
|
|||
id 'java-library'
|
||||
}
|
||||
|
||||
version 'unspecified'
|
||||
version = 'unspecified'
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
|
||||
maven { url 'https://build.shibboleth.net/nexus/content/repositories/releases/' }
|
||||
}
|
||||
|
||||
jar {
|
||||
|
|
@ -13,7 +15,7 @@ jar {
|
|||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly group: 'com.google.code.gson', name: 'gson', version: '2.3.1'
|
||||
compileOnly group: 'com.google.code.gson', name: 'gson', version: '2.13.1'
|
||||
|
||||
compileOnly project(":supertokens-plugin-interface")
|
||||
testImplementation project(":supertokens-plugin-interface")
|
||||
|
|
@ -35,13 +37,13 @@ dependencies {
|
|||
testImplementation group: 'org.mockito', name: 'mockito-core', version: '3.1.0'
|
||||
|
||||
// https://mvnrepository.com/artifact/org.apache.tomcat.embed/tomcat-embed-core
|
||||
testImplementation group: 'org.apache.tomcat.embed', name: 'tomcat-embed-core', version: '10.1.18'
|
||||
testImplementation group: 'org.apache.tomcat.embed', name: 'tomcat-embed-core', version: '11.0.5'
|
||||
|
||||
// https://mvnrepository.com/artifact/ch.qos.logback/logback-classic
|
||||
testImplementation group: 'ch.qos.logback', name: 'logback-classic', version: '1.4.14'
|
||||
testImplementation group: 'ch.qos.logback', name: 'logback-classic', version: '1.5.13'
|
||||
|
||||
// https://mvnrepository.com/artifact/com.google.code.gson/gson
|
||||
testImplementation group: 'com.google.code.gson', name: 'gson', version: '2.3.1'
|
||||
testImplementation group: 'com.google.code.gson', name: 'gson', version: '2.13.1'
|
||||
|
||||
testImplementation 'com.tngtech.archunit:archunit-junit4:0.22.0'
|
||||
|
||||
|
|
@ -52,17 +54,18 @@ dependencies {
|
|||
testImplementation group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.16.1'
|
||||
|
||||
testImplementation group: 'org.jetbrains', name: 'annotations', version: '13.0'
|
||||
|
||||
}
|
||||
|
||||
task copyJars(type: Copy) {
|
||||
into "$buildDir/dependencies"
|
||||
tasks.register('copyJars', Copy) {
|
||||
from configurations.runtimeClasspath
|
||||
into layout.buildDirectory.dir("dependencies")
|
||||
}
|
||||
|
||||
def interfaceName = "io.supertokens.featureflag.EEFeatureFlagInterface"
|
||||
def className = "io.supertokens.ee.EEFeatureFlag"
|
||||
|
||||
task generateMetaInf {
|
||||
tasks.register('generateMetaInf') {
|
||||
doFirst {
|
||||
mkdir "src/main/resources/META-INF/services"
|
||||
file("src/main/resources/META-INF/services/${interfaceName}").text = "${className}"
|
||||
|
|
@ -89,10 +92,10 @@ tasks.withType(Test) {
|
|||
TestLogEvent.PASSED,
|
||||
TestLogEvent.SKIPPED,
|
||||
TestLogEvent.STANDARD_OUT
|
||||
exceptionFormat TestExceptionFormat.FULL
|
||||
showExceptions true
|
||||
showCauses true
|
||||
showStackTraces true
|
||||
exceptionFormat = TestExceptionFormat.FULL
|
||||
showExceptions = true
|
||||
showCauses = true
|
||||
showStackTraces = true
|
||||
|
||||
// set options for log level DEBUG and INFO
|
||||
debug {
|
||||
|
|
@ -102,7 +105,7 @@ tasks.withType(Test) {
|
|||
TestLogEvent.SKIPPED,
|
||||
TestLogEvent.STANDARD_ERROR,
|
||||
TestLogEvent.STANDARD_OUT
|
||||
exceptionFormat TestExceptionFormat.FULL
|
||||
exceptionFormat = TestExceptionFormat.FULL
|
||||
}
|
||||
info.events = debug.events
|
||||
info.exceptionFormat = debug.exceptionFormat
|
||||
|
|
|
|||
BIN
ee/jar/ee.jar
BIN
ee/jar/ee.jar
Binary file not shown.
|
|
@ -14,6 +14,7 @@ exitIfNeeded
|
|||
|
||||
exitIfNeeded
|
||||
|
||||
|
||||
(cd ../../ && ./gradlew :$prefix-core:ee:copyJars < /dev/null)
|
||||
|
||||
exitIfNeeded
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ import io.supertokens.pluginInterface.multitenancy.TenantIdentifier;
|
|||
import io.supertokens.pluginInterface.multitenancy.ThirdPartyConfig;
|
||||
import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException;
|
||||
import io.supertokens.pluginInterface.oauth.OAuthStorage;
|
||||
import io.supertokens.pluginInterface.saml.SAMLStorage;
|
||||
import io.supertokens.pluginInterface.session.sqlStorage.SessionSQLStorage;
|
||||
import io.supertokens.storageLayer.StorageLayer;
|
||||
import io.supertokens.utils.Utils;
|
||||
|
|
@ -386,6 +387,34 @@ public class EEFeatureFlag implements io.supertokens.featureflag.EEFeatureFlagIn
|
|||
return mauArr;
|
||||
}
|
||||
|
||||
private JsonObject getSAMLStats() throws TenantOrAppNotFoundException, StorageQueryException {
|
||||
JsonObject stats = new JsonObject();
|
||||
|
||||
stats.addProperty("connectionUriDomain", this.appIdentifier.getConnectionUriDomain());
|
||||
stats.addProperty("appId", this.appIdentifier.getAppId());
|
||||
|
||||
JsonArray tenantStats = new JsonArray();
|
||||
|
||||
TenantConfig[] tenantConfigs = Multitenancy.getAllTenantsForApp(this.appIdentifier, main);
|
||||
for (TenantConfig tenantConfig : tenantConfigs) {
|
||||
JsonObject tenantStat = new JsonObject();
|
||||
tenantStat.addProperty("tenantId", tenantConfig.tenantIdentifier.getTenantId());
|
||||
|
||||
{
|
||||
Storage storage = StorageLayer.getStorage(tenantConfig.tenantIdentifier, main);
|
||||
SAMLStorage samlStorage = StorageUtils.getSAMLStorage(storage);
|
||||
|
||||
JsonObject stat = new JsonObject();
|
||||
stat.addProperty("numberOfSAMLClients", samlStorage.countSAMLClients(tenantConfig.tenantIdentifier));
|
||||
stat.add(tenantConfig.tenantIdentifier.getTenantId(), stat);
|
||||
}
|
||||
}
|
||||
|
||||
stats.add("tenants", tenantStats);
|
||||
|
||||
return stats;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonObject getPaidFeatureStats() throws StorageQueryException, TenantOrAppNotFoundException {
|
||||
JsonObject usageStats = new JsonObject();
|
||||
|
|
@ -433,6 +462,10 @@ public class EEFeatureFlag implements io.supertokens.featureflag.EEFeatureFlagIn
|
|||
if (feature == EE_FEATURES.OAUTH) {
|
||||
usageStats.add(EE_FEATURES.OAUTH.toString(), getOAuthStats());
|
||||
}
|
||||
|
||||
if (feature == EE_FEATURES.SAML) {
|
||||
usageStats.add(EE_FEATURES.SAML.toString(), getSAMLStats());
|
||||
}
|
||||
}
|
||||
|
||||
usageStats.add("maus", getMAUs());
|
||||
|
|
@ -523,7 +556,7 @@ public class EEFeatureFlag implements io.supertokens.featureflag.EEFeatureFlagIn
|
|||
ProcessState.getInstance(main)
|
||||
.addState(ProcessState.PROCESS_STATE.LICENSE_KEY_CHECK_NETWORK_CALL, null, json);
|
||||
JsonObject licenseCheckResponse = HttpRequest.sendJsonPOSTRequest(this.main, REQUEST_ID,
|
||||
"https://api.supertokens.io/0/st/license/check",
|
||||
"https://api.supertokens.com/0/st/license/check",
|
||||
json, 10000, 10000, 0);
|
||||
if (licenseCheckResponse.get("status").getAsString().equalsIgnoreCase("OK")) {
|
||||
Logging.debug(main, appIdentifier.getAsPublicTenantIdentifier(), "API returned OK");
|
||||
|
|
|
|||
|
|
@ -44,14 +44,14 @@ public class TestMultitenancyStats {
|
|||
String[] args = {"../../"};
|
||||
|
||||
TestingProcessManager.TestingProcess process = TestingProcessManager.start(args);
|
||||
CronTaskTest.getInstance(process.main).setIntervalInSeconds(EELicenseCheck.RESOURCE_KEY, 1);
|
||||
CronTaskTest.getInstance(process.getProcess()).setIntervalInSeconds(EELicenseCheck.RESOURCE_KEY, 1);
|
||||
Assert.assertNotNull(process.checkOrWaitForEvent(ProcessState.PROCESS_STATE.STARTED));
|
||||
|
||||
if (StorageLayer.getStorage(process.getProcess()).getType() != STORAGE_TYPE.SQL) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (StorageLayer.isInMemDb(process.main)) {
|
||||
if (StorageLayer.isInMemDb(process.getProcess())) {
|
||||
// cause we keep all features enabled in memdb anyway
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ public class TestingProcessManager {
|
|||
String[] args = {"../../"};
|
||||
TestingProcess process = TestingProcessManager.start(args);
|
||||
process.checkOrWaitForEvent(PROCESS_STATE.STARTED);
|
||||
process.main.deleteAllInformationForTesting();
|
||||
process.getProcess().deleteAllInformationForTesting();
|
||||
process.kill();
|
||||
System.out.println("----------DELETE ALL INFORMATION----------");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,7 +24,8 @@ public abstract class Utils extends Mockito {
|
|||
try {
|
||||
|
||||
// remove config.yaml file
|
||||
ProcessBuilder pb = new ProcessBuilder("rm", "config.yaml");
|
||||
String workerId = System.getProperty("org.gradle.test.worker", "");
|
||||
ProcessBuilder pb = new ProcessBuilder("rm", "config" + workerId + ".yaml");
|
||||
pb.directory(new File(installDir));
|
||||
Process process = pb.start();
|
||||
process.waitFor();
|
||||
|
|
@ -58,7 +59,8 @@ public abstract class Utils extends Mockito {
|
|||
|
||||
// if the default config is not the same as the current config, we must reset the storage layer
|
||||
File ogConfig = new File("../../temp/config.yaml");
|
||||
File currentConfig = new File("../../config.yaml");
|
||||
String workerId = System.getProperty("org.gradle.test.worker", "");
|
||||
File currentConfig = new File("../../config" + workerId + ".yaml");
|
||||
if (currentConfig.isFile()) {
|
||||
byte[] ogConfigContent = Files.readAllBytes(ogConfig.toPath());
|
||||
byte[] currentConfigContent = Files.readAllBytes(currentConfig.toPath());
|
||||
|
|
@ -67,7 +69,7 @@ public abstract class Utils extends Mockito {
|
|||
}
|
||||
}
|
||||
|
||||
ProcessBuilder pb = new ProcessBuilder("cp", "temp/config.yaml", "./config.yaml");
|
||||
ProcessBuilder pb = new ProcessBuilder("cp", "temp/config.yaml", "./config" + workerId + ".yaml");
|
||||
pb.directory(new File(installDir));
|
||||
Process process = pb.start();
|
||||
process.waitFor();
|
||||
|
|
@ -96,14 +98,15 @@ public abstract class Utils extends Mockito {
|
|||
String newStr = "\n# " + key + ":";
|
||||
|
||||
StringBuilder originalFileContent = new StringBuilder();
|
||||
try (BufferedReader reader = new BufferedReader(new FileReader("../../config.yaml"))) {
|
||||
String workerId = System.getProperty("org.gradle.test.worker", "");
|
||||
try (BufferedReader reader = new BufferedReader(new FileReader("../../config" + workerId + ".yaml"))) {
|
||||
String currentReadingLine = reader.readLine();
|
||||
while (currentReadingLine != null) {
|
||||
originalFileContent.append(currentReadingLine).append(System.lineSeparator());
|
||||
currentReadingLine = reader.readLine();
|
||||
}
|
||||
String modifiedFileContent = originalFileContent.toString().replaceAll(oldStr, newStr);
|
||||
try (BufferedWriter writer = new BufferedWriter(new FileWriter("../../config.yaml"))) {
|
||||
try (BufferedWriter writer = new BufferedWriter(new FileWriter("../../config" + workerId + ".yaml"))) {
|
||||
writer.write(modifiedFileContent);
|
||||
}
|
||||
}
|
||||
|
|
@ -117,14 +120,15 @@ public abstract class Utils extends Mockito {
|
|||
String oldStr = "\n((#\\s)?)" + key + "(:|((:\\s).+))\n";
|
||||
String newStr = "\n" + key + ": " + value + "\n";
|
||||
StringBuilder originalFileContent = new StringBuilder();
|
||||
try (BufferedReader reader = new BufferedReader(new FileReader("../../config.yaml"))) {
|
||||
String workerId = System.getProperty("org.gradle.test.worker", "");
|
||||
try (BufferedReader reader = new BufferedReader(new FileReader("../../config" + workerId + ".yaml"))) {
|
||||
String currentReadingLine = reader.readLine();
|
||||
while (currentReadingLine != null) {
|
||||
originalFileContent.append(currentReadingLine).append(System.lineSeparator());
|
||||
currentReadingLine = reader.readLine();
|
||||
}
|
||||
String modifiedFileContent = originalFileContent.toString().replaceAll(oldStr, newStr);
|
||||
try (BufferedWriter writer = new BufferedWriter(new FileWriter("../../config.yaml"))) {
|
||||
try (BufferedWriter writer = new BufferedWriter(new FileWriter("../../config" + workerId + ".yaml"))) {
|
||||
writer.write(modifiedFileContent);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ public class DeleteLicenseKeyAPITest {
|
|||
|
||||
// check that no LicenseKey exits
|
||||
try {
|
||||
FeatureFlag.getInstance(process.main).getLicenseKey();
|
||||
FeatureFlag.getInstance(process.getProcess()).getLicenseKey();
|
||||
fail();
|
||||
} catch (NoLicenseKeyFoundException ignored) {
|
||||
}
|
||||
|
|
@ -58,7 +58,7 @@ public class DeleteLicenseKeyAPITest {
|
|||
|
||||
// check that no LicenseKey exits
|
||||
try {
|
||||
FeatureFlag.getInstance(process.main).getLicenseKey();
|
||||
FeatureFlag.getInstance(process.getProcess()).getLicenseKey();
|
||||
fail();
|
||||
} catch (NoLicenseKeyFoundException ignored) {
|
||||
}
|
||||
|
|
@ -90,7 +90,7 @@ public class DeleteLicenseKeyAPITest {
|
|||
|
||||
// check that no LicenseKey exits
|
||||
try {
|
||||
FeatureFlag.getInstance(process.main).getLicenseKey();
|
||||
FeatureFlag.getInstance(process.getProcess()).getLicenseKey();
|
||||
fail();
|
||||
} catch (NoLicenseKeyFoundException ignored) {
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ public class GetFeatureFlagAPITest {
|
|||
TestingProcessManager.TestingProcess process = TestingProcessManager.start(args);
|
||||
Assert.assertNotNull(process.checkOrWaitForEvent(PROCESS_STATE.STARTED));
|
||||
|
||||
if (StorageLayer.isInMemDb(process.main)) {
|
||||
if (StorageLayer.isInMemDb(process.getProcess())) {
|
||||
// cause we keep all features enabled in memdb anyway
|
||||
return;
|
||||
}
|
||||
|
|
@ -72,7 +72,7 @@ public class GetFeatureFlagAPITest {
|
|||
TestingProcessManager.TestingProcess process = TestingProcessManager.start(args);
|
||||
Assert.assertNotNull(process.checkOrWaitForEvent(PROCESS_STATE.STARTED));
|
||||
|
||||
if (StorageLayer.isInMemDb(process.main)) {
|
||||
if (StorageLayer.isInMemDb(process.getProcess())) {
|
||||
// cause we keep all features enabled in memdb anyway
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -85,7 +85,7 @@ public class GetLicenseKeyAPITest {
|
|||
|
||||
assertNotNull(process.checkOrWaitForEvent(PROCESS_STATE.STARTED));
|
||||
|
||||
Assert.assertNull(FeatureFlag.getInstance(process.main).getEeFeatureFlagInstance());
|
||||
Assert.assertNull(FeatureFlag.getInstance(process.getProcess()).getEeFeatureFlagInstance());
|
||||
|
||||
Assert.assertEquals(FeatureFlag.getInstance(process.getProcess()).getEnabledFeatures().length, 0);
|
||||
|
||||
|
|
|
|||
|
|
@ -74,9 +74,9 @@ public class SetLicenseKeyAPITest {
|
|||
|
||||
assertNotNull(process.checkOrWaitForEvent(PROCESS_STATE.STARTED));
|
||||
|
||||
Assert.assertNull(FeatureFlag.getInstance(process.main).getEeFeatureFlagInstance());
|
||||
Assert.assertNull(FeatureFlag.getInstance(process.getProcess()).getEeFeatureFlagInstance());
|
||||
|
||||
Assert.assertEquals(FeatureFlag.getInstance(process.getProcess()).getEnabledFeatures().length, 0);
|
||||
Assert.assertEquals(0, FeatureFlag.getInstance(process.getProcess()).getEnabledFeatures().length);
|
||||
|
||||
// set license key when ee folder does not exist
|
||||
JsonObject requestBody = new JsonObject();
|
||||
|
|
|
|||
|
|
@ -1,135 +1,285 @@
|
|||
{
|
||||
"_comment": "Contains list of implementation dependencies URL for this project",
|
||||
"list": [
|
||||
{
|
||||
"jar": "https://repo1.maven.org/maven2/com/google/code/gson/gson/2.3.1/gson-2.3.1.jar",
|
||||
"name": "Gson 2.3.1",
|
||||
"src": "https://repo1.maven.org/maven2/com/google/code/gson/gson/2.3.1/gson-2.3.1-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar": "https://repo1.maven.org/maven2/com/fasterxml/jackson/dataformat/jackson-dataformat-yaml/2.18.2/jackson-dataformat-yaml-2.18.2.jar",
|
||||
"name": "Jackson Dataformat 2.18.2",
|
||||
"src": "https://repo1.maven.org/maven2/com/fasterxml/jackson/dataformat/jackson-dataformat-yaml/2.18.2/jackson-dataformat-yaml-2.18.2-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar": "https://repo1.maven.org/maven2/com/fasterxml/jackson/dataformat/jackson-dataformat-cbor/2.18.2/jackson-dataformat-cbor-2.18.2.jar",
|
||||
"name": "Jackson Dataformat CBOR 2.18.2",
|
||||
"src": "https://repo1.maven.org/maven2/com/fasterxml/jackson/dataformat/jackson-dataformat-cbor/2.18.2/jackson-dataformat-cbor-2.18.2-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar": "https://repo1.maven.org/maven2/org/yaml/snakeyaml/2.3/snakeyaml-2.3.jar",
|
||||
"name": "SnakeYAML 2.3",
|
||||
"src": "https://repo1.maven.org/maven2/org/yaml/snakeyaml/2.3/snakeyaml-2.3-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar": "https://repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-core/2.18.2/jackson-core-2.18.2.jar",
|
||||
"name": "Jackson core 2.18.2",
|
||||
"src": "https://repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-core/2.18.2/jackson-core-2.18.2-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar": "https://repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-databind/2.18.2/jackson-databind-2.18.2.jar",
|
||||
"name": "Jackson databind 2.18.2",
|
||||
"src": "https://repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-databind/2.18.2/jackson-databind-2.18.2-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar": "https://repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-annotations/2.18.2/jackson-annotations-2.18.2.jar",
|
||||
"name": "Jackson annotation 2.18.2",
|
||||
"src": "https://repo1.maven.org/maven2/com/fasterxml/jackson/core/jackson-annotations/2.18.2/jackson-annotations-2.18.2-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar": "https://repo1.maven.org/maven2/ch/qos/logback/logback-classic/1.4.14/logback-classic-1.4.14.jar",
|
||||
"name": "Logback classic 1.4.14",
|
||||
"src": "https://repo1.maven.org/maven2/ch/qos/logback/logback-classic/1.4.14/logback-classic-1.4.14-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar": "https://repo1.maven.org/maven2/ch/qos/logback/logback-core/1.4.14/logback-core-1.4.14.jar",
|
||||
"name": "Logback core 1.4.14",
|
||||
"src": "https://repo1.maven.org/maven2/ch/qos/logback/logback-core/1.4.14/logback-core-1.4.14-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar": "https://repo1.maven.org/maven2/org/slf4j/slf4j-api/2.0.16/slf4j-api-2.0.16.jar",
|
||||
"name": "SLF4j API 2.0.16",
|
||||
"src": "https://repo1.maven.org/maven2/org/slf4j/slf4j-api/2.0.16/slf4j-api-2.0.16-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar": "https://repo1.maven.org/maven2/org/apache/tomcat/tomcat-annotations-api/10.1.18/tomcat-annotations-api-10.1.18.jar",
|
||||
"name": "Tomcat annotations API 10.1.18",
|
||||
"src": "https://repo1.maven.org/maven2/org/apache/tomcat/tomcat-annotations-api/10.1.18/tomcat-annotations-api-10.1.18-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar": "https://repo1.maven.org/maven2/org/apache/tomcat/embed/tomcat-embed-core/10.1.18/tomcat-embed-core-10.1.18.jar",
|
||||
"name": "Tomcat embed core API 10.1.1",
|
||||
"src": "https://repo1.maven.org/maven2/org/apache/tomcat/embed/tomcat-embed-core/10.1.18/tomcat-embed-core-10.1.18-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar": "https://repo1.maven.org/maven2/com/google/code/findbugs/jsr305/3.0.2/jsr305-3.0.2.jar",
|
||||
"name": "JSR305 3.0.2",
|
||||
"src": "https://repo1.maven.org/maven2/com/google/code/findbugs/jsr305/3.0.2/jsr305-3.0.2-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar": "https://repo1.maven.org/maven2/org/jetbrains/annotations/13.0/annotations-13.0.jar",
|
||||
"name": "JSR305 3.0.2",
|
||||
"src": "https://repo1.maven.org/maven2/org/jetbrains/annotations/13.0/annotations-13.0-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar": "https://repo1.maven.org/maven2/org/xerial/sqlite-jdbc/3.45.1.0/sqlite-jdbc-3.45.1.0.jar",
|
||||
"name": "SQLite JDBC Driver 3.45.1.0",
|
||||
"src": "https://repo1.maven.org/maven2/org/xerial/sqlite-jdbc/3.45.1.0/sqlite-jdbc-3.45.1.0-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar": "https://repo1.maven.org/maven2/org/mindrot/jbcrypt/0.4/jbcrypt-0.4.jar",
|
||||
"name": "JBCrypt 0.4",
|
||||
"src": "https://repo1.maven.org/maven2/org/mindrot/jbcrypt/0.4/jbcrypt-0.4-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar": "https://repo1.maven.org/maven2/com/auth0/java-jwt/4.4.0/java-jwt-4.4.0.jar",
|
||||
"name": "Auth0 Java JWT",
|
||||
"src": "https://repo1.maven.org/maven2/com/auth0/java-jwt/4.4.0/java-jwt-4.4.0-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar": "https://repo1.maven.org/maven2/de/mkammerer/argon2-jvm/2.11/argon2-jvm-2.11.jar",
|
||||
"name": "Argon2-jvm 2.11",
|
||||
"src": "https://repo1.maven.org/maven2/de/mkammerer/argon2-jvm/2.11/argon2-jvm-2.11-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar": "https://repo1.maven.org/maven2/de/mkammerer/argon2-jvm-nolibs/2.11/argon2-jvm-nolibs-2.11.jar",
|
||||
"name": "Argon2-jvm no libs 2.11",
|
||||
"src": "https://repo1.maven.org/maven2/de/mkammerer/argon2-jvm-nolibs/2.11/argon2-jvm-nolibs-2.11-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar": "https://repo1.maven.org/maven2/net/java/dev/jna/jna/5.8.0/jna-5.8.0.jar",
|
||||
"name": "JNA 5.8.0",
|
||||
"src": "https://repo1.maven.org/maven2/net/java/dev/jna/jna/5.8.0/jna-5.8.0-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar": "https://repo1.maven.org/maven2/com/lambdaworks/scrypt/1.4.0/scrypt-1.4.0.jar",
|
||||
"name": "Scrypt 1.4.0",
|
||||
"src": "https://repo1.maven.org/maven2/com/lambdaworks/scrypt/1.4.0/scrypt-1.4.0-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar": "https://repo1.maven.org/maven2/com/eatthepath/java-otp/0.4.0/java-otp-0.4.0.jar",
|
||||
"name": "Java OTP 0.4.0",
|
||||
"src": "https://repo1.maven.org/maven2/com/eatthepath/java-otp/0.4.0/java-otp-0.4.0-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar": "https://repo1.maven.org/maven2/commons-codec/commons-codec/1.15/commons-codec-1.15.jar",
|
||||
"name": "Commons Codec 1.15",
|
||||
"src": "https://repo1.maven.org/maven2/commons-codec/commons-codec/1.15/commons-codec-1.15-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar": "https://repo1.maven.org/maven2/com/googlecode/libphonenumber/libphonenumber/8.13.25/libphonenumber-8.13.25.jar",
|
||||
"name": "Libphonenumber 8.13.25",
|
||||
"src": "https://repo1.maven.org/maven2/com/googlecode/libphonenumber/libphonenumber/8.13.25/libphonenumber-8.13.25-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar": "https://repo1.maven.org/maven2/com/webauthn4j/webauthn4j-core/0.28.6.RELEASE/webauthn4j-core-0.28.6.RELEASE.jar",
|
||||
"name": "webauthn4j-core 0.28.6.RELEASE",
|
||||
"src": "https://repo1.maven.org/maven2/com/webauthn4j/webauthn4j-core/0.28.6.RELEASE/webauthn4j-core-0.28.6.RELEASE-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar": "https://repo1.maven.org/maven2/com/webauthn4j/webauthn4j-util/0.28.6.RELEASE/webauthn4j-util-0.28.6.RELEASE.jar",
|
||||
"name": "webauthn4j-util 0.28.6.RELEASE",
|
||||
"src": "https://repo1.maven.org/maven2/com/webauthn4j/webauthn4j-util/0.28.6.RELEASE/webauthn4j-util-0.28.6.RELEASE-sources.jar"
|
||||
}
|
||||
]
|
||||
"_comment": "Contains list of implementation dependencies URL for this project. This is a generated file, don't modify the contents by hand.",
|
||||
"list": [
|
||||
{
|
||||
"jar":"https://repo.maven.apache.org/maven2/org/apache/tomcat/embed/tomcat-embed-core/11.0.12/tomcat-embed-core-11.0.12.jar",
|
||||
"name":"tomcat-embed-core 11.0.12",
|
||||
"src":"https://repo.maven.apache.org/maven2/org/apache/tomcat/embed/tomcat-embed-core/11.0.12/tomcat-embed-core-11.0.12-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar":"https://repo.maven.apache.org/maven2/org/apache/tomcat/tomcat-annotations-api/11.0.12/tomcat-annotations-api-11.0.12.jar",
|
||||
"name":"tomcat-annotations-api 11.0.12",
|
||||
"src":"https://repo.maven.apache.org/maven2/org/apache/tomcat/tomcat-annotations-api/11.0.12/tomcat-annotations-api-11.0.12-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar":"https://repo.maven.apache.org/maven2/com/google/code/gson/gson/2.13.1/gson-2.13.1.jar",
|
||||
"name":"gson 2.13.1",
|
||||
"src":"https://repo.maven.apache.org/maven2/com/google/code/gson/gson/2.13.1/gson-2.13.1-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar":"https://repo.maven.apache.org/maven2/com/google/errorprone/error_prone_annotations/2.38.0/error_prone_annotations-2.38.0.jar",
|
||||
"name":"error_prone_annotations 2.38.0",
|
||||
"src":"https://repo.maven.apache.org/maven2/com/google/errorprone/error_prone_annotations/2.38.0/error_prone_annotations-2.38.0-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar":"https://repo.maven.apache.org/maven2/com/fasterxml/jackson/dataformat/jackson-dataformat-yaml/2.18.2/jackson-dataformat-yaml-2.18.2.jar",
|
||||
"name":"jackson-dataformat-yaml 2.18.2",
|
||||
"src":"https://repo.maven.apache.org/maven2/com/fasterxml/jackson/dataformat/jackson-dataformat-yaml/2.18.2/jackson-dataformat-yaml-2.18.2-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar":"https://repo.maven.apache.org/maven2/org/yaml/snakeyaml/2.3/snakeyaml-2.3.jar",
|
||||
"name":"snakeyaml 2.3",
|
||||
"src":"https://repo.maven.apache.org/maven2/org/yaml/snakeyaml/2.3/snakeyaml-2.3-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar":"https://repo.maven.apache.org/maven2/com/fasterxml/jackson/dataformat/jackson-dataformat-cbor/2.18.2/jackson-dataformat-cbor-2.18.2.jar",
|
||||
"name":"jackson-dataformat-cbor 2.18.2",
|
||||
"src":"https://repo.maven.apache.org/maven2/com/fasterxml/jackson/dataformat/jackson-dataformat-cbor/2.18.2/jackson-dataformat-cbor-2.18.2-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar":"https://repo.maven.apache.org/maven2/com/fasterxml/jackson/core/jackson-databind/2.18.2/jackson-databind-2.18.2.jar",
|
||||
"name":"jackson-databind 2.18.2",
|
||||
"src":"https://repo.maven.apache.org/maven2/com/fasterxml/jackson/core/jackson-databind/2.18.2/jackson-databind-2.18.2-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar":"https://repo.maven.apache.org/maven2/com/google/code/findbugs/jsr305/3.0.2/jsr305-3.0.2.jar",
|
||||
"name":"jsr305 3.0.2",
|
||||
"src":"https://repo.maven.apache.org/maven2/com/google/code/findbugs/jsr305/3.0.2/jsr305-3.0.2-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar":"https://repo.maven.apache.org/maven2/org/xerial/sqlite-jdbc/3.45.1.0/sqlite-jdbc-3.45.1.0.jar",
|
||||
"name":"sqlite-jdbc 3.45.1.0",
|
||||
"src":"https://repo.maven.apache.org/maven2/org/xerial/sqlite-jdbc/3.45.1.0/sqlite-jdbc-3.45.1.0-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar":"https://repo.maven.apache.org/maven2/org/slf4j/slf4j-api/2.0.17/slf4j-api-2.0.17.jar",
|
||||
"name":"slf4j-api 2.0.17",
|
||||
"src":"https://repo.maven.apache.org/maven2/org/slf4j/slf4j-api/2.0.17/slf4j-api-2.0.17-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar":"https://repo.maven.apache.org/maven2/org/mindrot/jbcrypt/0.4/jbcrypt-0.4.jar",
|
||||
"name":"jbcrypt 0.4",
|
||||
"src":"https://repo.maven.apache.org/maven2/org/mindrot/jbcrypt/0.4/jbcrypt-0.4-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar":"https://repo.maven.apache.org/maven2/org/jetbrains/annotations/13.0/annotations-13.0.jar",
|
||||
"name":"annotations 13.0",
|
||||
"src":"https://repo.maven.apache.org/maven2/org/jetbrains/annotations/13.0/annotations-13.0-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar":"https://repo.maven.apache.org/maven2/de/mkammerer/argon2-jvm/2.11/argon2-jvm-2.11.jar",
|
||||
"name":"argon2-jvm 2.11",
|
||||
"src":"https://repo.maven.apache.org/maven2/de/mkammerer/argon2-jvm/2.11/argon2-jvm-2.11-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar":"https://repo.maven.apache.org/maven2/com/auth0/java-jwt/4.4.0/java-jwt-4.4.0.jar",
|
||||
"name":"java-jwt 4.4.0",
|
||||
"src":"https://repo.maven.apache.org/maven2/com/auth0/java-jwt/4.4.0/java-jwt-4.4.0-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar":"https://repo.maven.apache.org/maven2/com/lambdaworks/scrypt/1.4.0/scrypt-1.4.0.jar",
|
||||
"name":"scrypt 1.4.0",
|
||||
"src":"https://repo.maven.apache.org/maven2/com/lambdaworks/scrypt/1.4.0/scrypt-1.4.0-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar":"https://repo.maven.apache.org/maven2/com/eatthepath/java-otp/0.4.0/java-otp-0.4.0.jar",
|
||||
"name":"java-otp 0.4.0",
|
||||
"src":"https://repo.maven.apache.org/maven2/com/eatthepath/java-otp/0.4.0/java-otp-0.4.0-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar":"https://repo.maven.apache.org/maven2/commons-codec/commons-codec/1.15/commons-codec-1.15.jar",
|
||||
"name":"commons-codec 1.15",
|
||||
"src":"https://repo.maven.apache.org/maven2/commons-codec/commons-codec/1.15/commons-codec-1.15-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar":"https://repo.maven.apache.org/maven2/com/googlecode/libphonenumber/libphonenumber/8.13.25/libphonenumber-8.13.25.jar",
|
||||
"name":"libphonenumber 8.13.25",
|
||||
"src":"https://repo.maven.apache.org/maven2/com/googlecode/libphonenumber/libphonenumber/8.13.25/libphonenumber-8.13.25-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar":"https://repo.maven.apache.org/maven2/com/webauthn4j/webauthn4j-core/0.28.6.RELEASE/webauthn4j-core-0.28.6.RELEASE.jar",
|
||||
"name":"webauthn4j-core 0.28.6.RELEASE",
|
||||
"src":"https://repo.maven.apache.org/maven2/com/webauthn4j/webauthn4j-core/0.28.6.RELEASE/webauthn4j-core-0.28.6.RELEASE-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar":"https://build.shibboleth.net/nexus/content/repositories/releases/org/opensaml/opensaml-core/4.3.1/opensaml-core-4.3.1.jar",
|
||||
"name":"opensaml-core 4.3.1",
|
||||
"src":"https://build.shibboleth.net/nexus/content/repositories/releases/org/opensaml/opensaml-core/4.3.1/opensaml-core-4.3.1-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar":"https://build.shibboleth.net/nexus/content/repositories/releases/net/shibboleth/utilities/java-support/8.4.1/java-support-8.4.1.jar",
|
||||
"name":"java-support 8.4.1",
|
||||
"src":"https://build.shibboleth.net/nexus/content/repositories/releases/net/shibboleth/utilities/java-support/8.4.1/java-support-8.4.1-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar":"https://repo.maven.apache.org/maven2/com/google/guava/guava/31.1-jre/guava-31.1-jre.jar",
|
||||
"name":"guava 31.1-jre",
|
||||
"src":"https://repo.maven.apache.org/maven2/com/google/guava/guava/31.1-jre/guava-31.1-jre-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar":"https://repo.maven.apache.org/maven2/com/google/guava/failureaccess/1.0.1/failureaccess-1.0.1.jar",
|
||||
"name":"failureaccess 1.0.1",
|
||||
"src":"https://repo.maven.apache.org/maven2/com/google/guava/failureaccess/1.0.1/failureaccess-1.0.1-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar":"https://repo.maven.apache.org/maven2/com/google/guava/listenablefuture/9999.0-empty-to-avoid-conflict-with-guava/listenablefuture-9999.0-empty-to-avoid-conflict-with-guava.jar",
|
||||
"name":"listenablefuture 9999.0-empty-to-avoid-conflict-with-guava",
|
||||
"src":"https://repo.maven.apache.org/maven2/com/google/guava/listenablefuture/9999.0-empty-to-avoid-conflict-with-guava/listenablefuture-9999.0-empty-to-avoid-conflict-with-guava-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar":"https://repo.maven.apache.org/maven2/com/google/j2objc/j2objc-annotations/1.3/j2objc-annotations-1.3.jar",
|
||||
"name":"j2objc-annotations 1.3",
|
||||
"src":"https://repo.maven.apache.org/maven2/com/google/j2objc/j2objc-annotations/1.3/j2objc-annotations-1.3-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar":"https://repo.maven.apache.org/maven2/io/dropwizard/metrics/metrics-core/4.2.25/metrics-core-4.2.25.jar",
|
||||
"name":"metrics-core 4.2.25",
|
||||
"src":"https://repo.maven.apache.org/maven2/io/dropwizard/metrics/metrics-core/4.2.25/metrics-core-4.2.25-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar":"https://build.shibboleth.net/nexus/content/repositories/releases/org/opensaml/opensaml-saml-impl/4.3.1/opensaml-saml-impl-4.3.1.jar",
|
||||
"name":"opensaml-saml-impl 4.3.1",
|
||||
"src":"https://build.shibboleth.net/nexus/content/repositories/releases/org/opensaml/opensaml-saml-impl/4.3.1/opensaml-saml-impl-4.3.1-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar":"https://build.shibboleth.net/nexus/content/repositories/releases/org/opensaml/opensaml-xmlsec-impl/4.3.1/opensaml-xmlsec-impl-4.3.1.jar",
|
||||
"name":"opensaml-xmlsec-impl 4.3.1",
|
||||
"src":"https://build.shibboleth.net/nexus/content/repositories/releases/org/opensaml/opensaml-xmlsec-impl/4.3.1/opensaml-xmlsec-impl-4.3.1-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar":"https://build.shibboleth.net/nexus/content/repositories/releases/org/opensaml/opensaml-security-impl/4.3.1/opensaml-security-impl-4.3.1.jar",
|
||||
"name":"opensaml-security-impl 4.3.1",
|
||||
"src":"https://build.shibboleth.net/nexus/content/repositories/releases/org/opensaml/opensaml-security-impl/4.3.1/opensaml-security-impl-4.3.1-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar":"https://build.shibboleth.net/nexus/content/repositories/releases/org/opensaml/opensaml-security-api/4.3.1/opensaml-security-api-4.3.1.jar",
|
||||
"name":"opensaml-security-api 4.3.1",
|
||||
"src":"https://build.shibboleth.net/nexus/content/repositories/releases/org/opensaml/opensaml-security-api/4.3.1/opensaml-security-api-4.3.1-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar":"https://build.shibboleth.net/nexus/content/repositories/releases/org/opensaml/opensaml-messaging-api/4.3.1/opensaml-messaging-api-4.3.1.jar",
|
||||
"name":"opensaml-messaging-api 4.3.1",
|
||||
"src":"https://build.shibboleth.net/nexus/content/repositories/releases/org/opensaml/opensaml-messaging-api/4.3.1/opensaml-messaging-api-4.3.1-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar":"https://repo.maven.apache.org/maven2/org/apache/httpcomponents/httpclient/4.5.14/httpclient-4.5.14.jar",
|
||||
"name":"httpclient 4.5.14",
|
||||
"src":"https://repo.maven.apache.org/maven2/org/apache/httpcomponents/httpclient/4.5.14/httpclient-4.5.14-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar":"https://repo.maven.apache.org/maven2/org/apache/httpcomponents/httpcore/4.4.16/httpcore-4.4.16.jar",
|
||||
"name":"httpcore 4.4.16",
|
||||
"src":"https://repo.maven.apache.org/maven2/org/apache/httpcomponents/httpcore/4.4.16/httpcore-4.4.16-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar":"https://repo.maven.apache.org/maven2/org/cryptacular/cryptacular/1.2.5/cryptacular-1.2.5.jar",
|
||||
"name":"cryptacular 1.2.5",
|
||||
"src":"https://repo.maven.apache.org/maven2/org/cryptacular/cryptacular/1.2.5/cryptacular-1.2.5-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar":"https://repo.maven.apache.org/maven2/org/bouncycastle/bcprov-jdk18on/1.72/bcprov-jdk18on-1.72.jar",
|
||||
"name":"bcprov-jdk18on 1.72",
|
||||
"src":"https://repo.maven.apache.org/maven2/org/bouncycastle/bcprov-jdk18on/1.72/bcprov-jdk18on-1.72-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar":"https://repo.maven.apache.org/maven2/org/bouncycastle/bcpkix-jdk18on/1.72/bcpkix-jdk18on-1.72.jar",
|
||||
"name":"bcpkix-jdk18on 1.72",
|
||||
"src":"https://repo.maven.apache.org/maven2/org/bouncycastle/bcpkix-jdk18on/1.72/bcpkix-jdk18on-1.72-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar":"https://repo.maven.apache.org/maven2/org/bouncycastle/bcutil-jdk18on/1.72/bcutil-jdk18on-1.72.jar",
|
||||
"name":"bcutil-jdk18on 1.72",
|
||||
"src":"https://repo.maven.apache.org/maven2/org/bouncycastle/bcutil-jdk18on/1.72/bcutil-jdk18on-1.72-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar":"https://build.shibboleth.net/nexus/content/repositories/releases/org/opensaml/opensaml-xmlsec-api/4.3.1/opensaml-xmlsec-api-4.3.1.jar",
|
||||
"name":"opensaml-xmlsec-api 4.3.1",
|
||||
"src":"https://build.shibboleth.net/nexus/content/repositories/releases/org/opensaml/opensaml-xmlsec-api/4.3.1/opensaml-xmlsec-api-4.3.1-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar":"https://repo.maven.apache.org/maven2/org/apache/santuario/xmlsec/2.3.4/xmlsec-2.3.4.jar",
|
||||
"name":"xmlsec 2.3.4",
|
||||
"src":"https://repo.maven.apache.org/maven2/org/apache/santuario/xmlsec/2.3.4/xmlsec-2.3.4-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar":"https://build.shibboleth.net/nexus/content/repositories/releases/org/opensaml/opensaml-saml-api/4.3.1/opensaml-saml-api-4.3.1.jar",
|
||||
"name":"opensaml-saml-api 4.3.1",
|
||||
"src":"https://build.shibboleth.net/nexus/content/repositories/releases/org/opensaml/opensaml-saml-api/4.3.1/opensaml-saml-api-4.3.1-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar":"https://build.shibboleth.net/nexus/content/repositories/releases/org/opensaml/opensaml-profile-api/4.3.1/opensaml-profile-api-4.3.1.jar",
|
||||
"name":"opensaml-profile-api 4.3.1",
|
||||
"src":"https://build.shibboleth.net/nexus/content/repositories/releases/org/opensaml/opensaml-profile-api/4.3.1/opensaml-profile-api-4.3.1-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar":"https://build.shibboleth.net/nexus/content/repositories/releases/org/opensaml/opensaml-soap-api/4.3.1/opensaml-soap-api-4.3.1.jar",
|
||||
"name":"opensaml-soap-api 4.3.1",
|
||||
"src":"https://build.shibboleth.net/nexus/content/repositories/releases/org/opensaml/opensaml-soap-api/4.3.1/opensaml-soap-api-4.3.1-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar":"https://build.shibboleth.net/nexus/content/repositories/releases/org/opensaml/opensaml-soap-impl/4.3.1/opensaml-soap-impl-4.3.1.jar",
|
||||
"name":"opensaml-soap-impl 4.3.1",
|
||||
"src":"https://build.shibboleth.net/nexus/content/repositories/releases/org/opensaml/opensaml-soap-impl/4.3.1/opensaml-soap-impl-4.3.1-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar":"https://build.shibboleth.net/nexus/content/repositories/releases/org/opensaml/opensaml-storage-api/4.3.1/opensaml-storage-api-4.3.1.jar",
|
||||
"name":"opensaml-storage-api 4.3.1",
|
||||
"src":"https://build.shibboleth.net/nexus/content/repositories/releases/org/opensaml/opensaml-storage-api/4.3.1/opensaml-storage-api-4.3.1-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar":"https://repo.maven.apache.org/maven2/org/apache/velocity/velocity-engine-core/2.3/velocity-engine-core-2.3.jar",
|
||||
"name":"velocity-engine-core 2.3",
|
||||
"src":"https://repo.maven.apache.org/maven2/org/apache/velocity/velocity-engine-core/2.3/velocity-engine-core-2.3-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar":"https://repo.maven.apache.org/maven2/org/apache/commons/commons-lang3/3.11/commons-lang3-3.11.jar",
|
||||
"name":"commons-lang3 3.11",
|
||||
"src":"https://repo.maven.apache.org/maven2/org/apache/commons/commons-lang3/3.11/commons-lang3-3.11-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar":"https://build.shibboleth.net/nexus/content/repositories/releases/org/opensaml/opensaml-profile-impl/4.3.1/opensaml-profile-impl-4.3.1.jar",
|
||||
"name":"opensaml-profile-impl 4.3.1",
|
||||
"src":"https://build.shibboleth.net/nexus/content/repositories/releases/org/opensaml/opensaml-profile-impl/4.3.1/opensaml-profile-impl-4.3.1-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar":"https://repo.maven.apache.org/maven2/ch/qos/logback/logback-core/1.5.18/logback-core-1.5.18.jar",
|
||||
"name":"logback-core 1.5.18",
|
||||
"src":"https://repo.maven.apache.org/maven2/ch/qos/logback/logback-core/1.5.18/logback-core-1.5.18-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar":"https://repo.maven.apache.org/maven2/ch/qos/logback/logback-classic/1.5.18/logback-classic-1.5.18.jar",
|
||||
"name":"logback-classic 1.5.18",
|
||||
"src":"https://repo.maven.apache.org/maven2/ch/qos/logback/logback-classic/1.5.18/logback-classic-1.5.18-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar":"https://repo.maven.apache.org/maven2/org/aspectj/aspectjrt/1.9.24/aspectjrt-1.9.24.jar",
|
||||
"name":"aspectjrt 1.9.24",
|
||||
"src":"https://repo.maven.apache.org/maven2/org/aspectj/aspectjrt/1.9.24/aspectjrt-1.9.24-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar":"https://repo.maven.apache.org/maven2/io/opentelemetry/opentelemetry-api/1.51.0/opentelemetry-api-1.51.0.jar",
|
||||
"name":"opentelemetry-api 1.51.0",
|
||||
"src":"https://repo.maven.apache.org/maven2/io/opentelemetry/opentelemetry-api/1.51.0/opentelemetry-api-1.51.0-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar":"https://repo.maven.apache.org/maven2/io/opentelemetry/opentelemetry-exporter-logging/1.51.0/opentelemetry-exporter-logging-1.51.0.jar",
|
||||
"name":"opentelemetry-exporter-logging 1.51.0",
|
||||
"src":"https://repo.maven.apache.org/maven2/io/opentelemetry/opentelemetry-exporter-logging/1.51.0/opentelemetry-exporter-logging-1.51.0-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar":"https://repo.maven.apache.org/maven2/io/opentelemetry/opentelemetry-sdk/1.51.0/opentelemetry-sdk-1.51.0.jar",
|
||||
"name":"opentelemetry-sdk 1.51.0",
|
||||
"src":"https://repo.maven.apache.org/maven2/io/opentelemetry/opentelemetry-sdk/1.51.0/opentelemetry-sdk-1.51.0-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar":"https://repo.maven.apache.org/maven2/io/opentelemetry/opentelemetry-exporter-otlp/1.51.0/opentelemetry-exporter-otlp-1.51.0.jar",
|
||||
"name":"opentelemetry-exporter-otlp 1.51.0",
|
||||
"src":"https://repo.maven.apache.org/maven2/io/opentelemetry/opentelemetry-exporter-otlp/1.51.0/opentelemetry-exporter-otlp-1.51.0-sources.jar"
|
||||
},
|
||||
{
|
||||
"jar":"https://repo.maven.apache.org/maven2/io/opentelemetry/semconv/opentelemetry-semconv/1.34.0/opentelemetry-semconv-1.34.0.jar",
|
||||
"name":"opentelemetry-semconv 1.34.0",
|
||||
"src":"https://repo.maven.apache.org/maven2/io/opentelemetry/semconv/opentelemetry-semconv/1.34.0/opentelemetry-semconv-1.34.0-sources.jar"
|
||||
}
|
||||
]
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"_comment": "contains a list of plugin interfaces branch names that this core supports",
|
||||
"versions": [
|
||||
"7.1"
|
||||
"8.3"
|
||||
]
|
||||
}
|
||||
|
|
@ -24,7 +24,7 @@ public class ActiveUsers {
|
|||
@TestOnly
|
||||
public static void updateLastActive(Main main, String userId) {
|
||||
try {
|
||||
ActiveUsers.updateLastActive(new AppIdentifier(null, null),
|
||||
ActiveUsers.updateLastActive(ResourceDistributor.getAppForTesting().toAppIdentifier(),
|
||||
main, userId);
|
||||
} catch (TenantOrAppNotFoundException e) {
|
||||
throw new IllegalStateException(e);
|
||||
|
|
@ -55,6 +55,6 @@ public class ActiveUsers {
|
|||
@TestOnly
|
||||
public static int countUsersActiveSince(Main main, long time)
|
||||
throws StorageQueryException, TenantOrAppNotFoundException {
|
||||
return countUsersActiveSince(main, new AppIdentifier(null, null), time);
|
||||
return countUsersActiveSince(main, ResourceDistributor.getAppForTesting().toAppIdentifier(), time);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,7 +22,9 @@ import io.supertokens.config.CoreConfig;
|
|||
import io.supertokens.cronjobs.Cronjobs;
|
||||
import io.supertokens.cronjobs.bulkimport.ProcessBulkImportUsers;
|
||||
import io.supertokens.cronjobs.cleanupOAuthSessionsAndChallenges.CleanupOAuthSessionsAndChallenges;
|
||||
import io.supertokens.cronjobs.deleteExpiredSAMLData.DeleteExpiredSAMLData;
|
||||
import io.supertokens.cronjobs.cleanupWebauthnExpiredData.CleanUpWebauthNExpiredDataCron;
|
||||
import io.supertokens.cronjobs.deadlocklogger.DeadlockLogger;
|
||||
import io.supertokens.cronjobs.deleteExpiredAccessTokenSigningKeys.DeleteExpiredAccessTokenSigningKeys;
|
||||
import io.supertokens.cronjobs.deleteExpiredDashboardSessions.DeleteExpiredDashboardSessions;
|
||||
import io.supertokens.cronjobs.deleteExpiredEmailVerificationTokens.DeleteExpiredEmailVerificationTokens;
|
||||
|
|
@ -42,7 +44,9 @@ import io.supertokens.pluginInterface.exceptions.DbInitException;
|
|||
import io.supertokens.pluginInterface.exceptions.InvalidConfigException;
|
||||
import io.supertokens.pluginInterface.exceptions.StorageQueryException;
|
||||
import io.supertokens.pluginInterface.multitenancy.TenantIdentifier;
|
||||
import io.supertokens.saml.SAMLBootstrap;
|
||||
import io.supertokens.storageLayer.StorageLayer;
|
||||
import io.supertokens.telemetry.TelemetryProvider;
|
||||
import io.supertokens.version.Version;
|
||||
import io.supertokens.webserver.Webserver;
|
||||
import org.jetbrains.annotations.TestOnly;
|
||||
|
|
@ -120,6 +124,8 @@ public class Main {
|
|||
CLIOptions.load(this, args);
|
||||
init();
|
||||
} catch (Exception e) {
|
||||
Logging.error(this, TenantIdentifier.BASE_TENANT, "What caused the crash: " + e.getMessage(), true,
|
||||
e);
|
||||
ProcessState.getInstance(this).addState(ProcessState.PROCESS_STATE.INIT_FAILURE, e);
|
||||
throw e;
|
||||
}
|
||||
|
|
@ -154,9 +160,12 @@ public class Main {
|
|||
// Handle kill signal gracefully
|
||||
handleKillSignalForWhenItHappens();
|
||||
|
||||
StorageLayer.loadStorageUCL(CLIOptions.get(this).getInstallationPath() + "plugin/");
|
||||
|
||||
// loading configs for core from config.yaml file.
|
||||
try {
|
||||
Config.loadBaseConfig(this);
|
||||
Logging.info(this, TenantIdentifier.BASE_TENANT, "Completed config.yaml loading.", true);
|
||||
} catch (InvalidConfigException e) {
|
||||
throw new QuitProgramException(e);
|
||||
}
|
||||
|
|
@ -164,12 +173,11 @@ public class Main {
|
|||
// loading version file
|
||||
Version.loadVersion(this, CLIOptions.get(this).getInstallationPath() + "version.yaml");
|
||||
|
||||
Logging.info(this, TenantIdentifier.BASE_TENANT, "Completed config.yaml loading.", true);
|
||||
TelemetryProvider.initialize(this);
|
||||
|
||||
// loading storage layer
|
||||
try {
|
||||
StorageLayer.initPrimary(this, CLIOptions.get(this).getInstallationPath() + "plugin/",
|
||||
Config.getBaseConfigAsJsonObject(this));
|
||||
StorageLayer.initPrimary(this, Config.getBaseConfigAsJsonObject(this));
|
||||
} catch (InvalidConfigException e) {
|
||||
throw new QuitProgramException(e);
|
||||
}
|
||||
|
|
@ -177,6 +185,9 @@ public class Main {
|
|||
// init file logging
|
||||
Logging.initFileLogging(this);
|
||||
|
||||
// Required for SAML related stuff
|
||||
SAMLBootstrap.initialize();
|
||||
|
||||
// initialise cron job handler
|
||||
Cronjobs.init(this);
|
||||
|
||||
|
|
@ -273,6 +284,13 @@ public class Main {
|
|||
|
||||
Cronjobs.addCronjob(this, CleanUpWebauthNExpiredDataCron.init(this, uniqueUserPoolIdsTenants));
|
||||
|
||||
// starts the DeadlockLogger if
|
||||
if (Config.getBaseConfig(this).isDeadlockLoggerEnabled()) {
|
||||
DeadlockLogger.getInstance().start();
|
||||
}
|
||||
|
||||
Cronjobs.addCronjob(this, DeleteExpiredSAMLData.init(this, uniqueUserPoolIdsTenants));
|
||||
|
||||
// this is to ensure tenantInfos are in sync for the new cron job as well
|
||||
MultitenancyHelper.getInstance(this).refreshCronjobs();
|
||||
|
||||
|
|
@ -362,12 +380,16 @@ public class Main {
|
|||
}
|
||||
|
||||
private void createDotStartedFileForThisProcess() throws IOException {
|
||||
String startedDir = ".started";
|
||||
if (isTesting) {
|
||||
startedDir = ".started" + System.getProperty("org.gradle.test.worker", "");
|
||||
}
|
||||
CoreConfig config = Config.getBaseConfig(this);
|
||||
String fileLocation = CLIOptions.get(this).getTempDirLocation() == null ? CLIOptions.get(this).getInstallationPath() : CLIOptions.get(this).getTempDirLocation();
|
||||
String fileName = OperatingSystem.getOS() == OperatingSystem.OS.WINDOWS
|
||||
? fileLocation + ".started\\" + config.getHost(this) + "-"
|
||||
? fileLocation + startedDir + "\\" + config.getHost(this) + "-"
|
||||
+ config.getPort(this)
|
||||
: fileLocation + ".started/" + config.getHost(this) + "-"
|
||||
: fileLocation + startedDir + "/" + config.getHost(this) + "-"
|
||||
+ config.getPort(this);
|
||||
File dotStarted = new File(fileName);
|
||||
if (!dotStarted.exists()) {
|
||||
|
|
@ -410,9 +432,10 @@ public class Main {
|
|||
|
||||
@TestOnly
|
||||
public void killForTestingAndWaitForShutdown() throws InterruptedException {
|
||||
assertIsTesting();
|
||||
wakeUpMainThreadToShutdown();
|
||||
mainThread.join();
|
||||
// Do not kill for now
|
||||
assertIsTesting();
|
||||
wakeUpMainThreadToShutdown();
|
||||
mainThread.join();
|
||||
}
|
||||
|
||||
// must not throw any error
|
||||
|
|
@ -441,6 +464,7 @@ public class Main {
|
|||
StorageLayer.close(this);
|
||||
removeDotStartedFileForThisProcess();
|
||||
Logging.stopLogging(this);
|
||||
TelemetryProvider.closeTelemetry(this);
|
||||
// uncomment this when you want to confirm that processes are actually shut.
|
||||
// printRunningThreadNames();
|
||||
|
||||
|
|
|
|||
|
|
@ -104,7 +104,7 @@ public class ProcessState extends ResourceDistributor.SingletonResource {
|
|||
public static class EventAndException {
|
||||
public Exception exception;
|
||||
public JsonObject data;
|
||||
PROCESS_STATE state;
|
||||
public PROCESS_STATE state;
|
||||
|
||||
public EventAndException(PROCESS_STATE state, Exception e) {
|
||||
this.state = state;
|
||||
|
|
|
|||
|
|
@ -35,16 +35,28 @@ public class ResourceDistributor {
|
|||
private final Map<KeyClass, SingletonResource> resources = new HashMap<>(1);
|
||||
private final Main main;
|
||||
|
||||
private static TenantIdentifier appUsedForTesting = TenantIdentifier.BASE_TENANT;
|
||||
|
||||
public ResourceDistributor(Main main) {
|
||||
this.main = main;
|
||||
}
|
||||
|
||||
public synchronized SingletonResource getResource(AppIdentifier appIdentifier, @Nonnull String key)
|
||||
@TestOnly
|
||||
public static void setAppForTesting(TenantIdentifier app) {
|
||||
appUsedForTesting = app;
|
||||
}
|
||||
|
||||
@TestOnly
|
||||
public static TenantIdentifier getAppForTesting() {
|
||||
return appUsedForTesting;
|
||||
}
|
||||
|
||||
public SingletonResource getResource(AppIdentifier appIdentifier, @Nonnull String key)
|
||||
throws TenantOrAppNotFoundException {
|
||||
return getResource(appIdentifier.getAsPublicTenantIdentifier(), key);
|
||||
}
|
||||
|
||||
public synchronized SingletonResource getResource(TenantIdentifier tenantIdentifier, @Nonnull String key)
|
||||
public SingletonResource getResource(TenantIdentifier tenantIdentifier, @Nonnull String key)
|
||||
throws TenantOrAppNotFoundException {
|
||||
// first we do exact match
|
||||
SingletonResource resource = resources.get(new KeyClass(tenantIdentifier, key));
|
||||
|
|
@ -58,14 +70,6 @@ public class ResourceDistributor {
|
|||
throw new TenantOrAppNotFoundException(tenantIdentifier);
|
||||
}
|
||||
|
||||
MultitenancyHelper.getInstance(main).refreshTenantsInCoreBasedOnChangesInCoreConfigOrIfTenantListChanged(true);
|
||||
|
||||
// we try again..
|
||||
resource = resources.get(new KeyClass(tenantIdentifier, key));
|
||||
if (resource != null) {
|
||||
return resource;
|
||||
}
|
||||
|
||||
// then we see if the user has configured anything to do with connectionUriDomain, and if they have,
|
||||
// then we must return null cause the user has not specifically added tenantId to it
|
||||
for (KeyClass currKey : resources.keySet()) {
|
||||
|
|
@ -89,11 +93,11 @@ public class ResourceDistributor {
|
|||
}
|
||||
|
||||
@TestOnly
|
||||
public synchronized SingletonResource getResource(@Nonnull String key) {
|
||||
return resources.get(new KeyClass(new TenantIdentifier(null, null, null), key));
|
||||
public SingletonResource getResource(@Nonnull String key) {
|
||||
return resources.get(new KeyClass(appUsedForTesting, key));
|
||||
}
|
||||
|
||||
public synchronized SingletonResource setResource(TenantIdentifier tenantIdentifier,
|
||||
public SingletonResource setResource(TenantIdentifier tenantIdentifier,
|
||||
@Nonnull String key,
|
||||
SingletonResource resource) {
|
||||
SingletonResource alreadyExists = resources.get(new KeyClass(tenantIdentifier, key));
|
||||
|
|
@ -104,7 +108,7 @@ public class ResourceDistributor {
|
|||
return resource;
|
||||
}
|
||||
|
||||
public synchronized SingletonResource removeResource(TenantIdentifier tenantIdentifier,
|
||||
public SingletonResource removeResource(TenantIdentifier tenantIdentifier,
|
||||
@Nonnull String key) {
|
||||
SingletonResource singletonResource = resources.get(new KeyClass(tenantIdentifier, key));
|
||||
if (singletonResource == null) {
|
||||
|
|
@ -114,18 +118,18 @@ public class ResourceDistributor {
|
|||
return singletonResource;
|
||||
}
|
||||
|
||||
public synchronized SingletonResource setResource(AppIdentifier appIdentifier,
|
||||
public SingletonResource setResource(AppIdentifier appIdentifier,
|
||||
@Nonnull String key,
|
||||
SingletonResource resource) {
|
||||
return setResource(appIdentifier.getAsPublicTenantIdentifier(), key, resource);
|
||||
}
|
||||
|
||||
public synchronized SingletonResource removeResource(AppIdentifier appIdentifier,
|
||||
public SingletonResource removeResource(AppIdentifier appIdentifier,
|
||||
@Nonnull String key) {
|
||||
return removeResource(appIdentifier.getAsPublicTenantIdentifier(), key);
|
||||
}
|
||||
|
||||
public synchronized void clearAllResourcesWithResourceKey(String inputKey) {
|
||||
public void clearAllResourcesWithResourceKey(String inputKey) {
|
||||
List<KeyClass> toRemove = new ArrayList<>();
|
||||
resources.forEach((key, value) -> {
|
||||
if (key.key.equals(inputKey)) {
|
||||
|
|
@ -137,7 +141,7 @@ public class ResourceDistributor {
|
|||
}
|
||||
}
|
||||
|
||||
public synchronized Map<KeyClass, SingletonResource> getAllResourcesWithResourceKey(String inputKey) {
|
||||
public Map<KeyClass, SingletonResource> getAllResourcesWithResourceKey(String inputKey) {
|
||||
Map<KeyClass, SingletonResource> result = new HashMap<>();
|
||||
resources.forEach((key, value) -> {
|
||||
if (key.key.equals(inputKey)) {
|
||||
|
|
@ -148,9 +152,9 @@ public class ResourceDistributor {
|
|||
}
|
||||
|
||||
@TestOnly
|
||||
public synchronized SingletonResource setResource(@Nonnull String key,
|
||||
public SingletonResource setResource(@Nonnull String key,
|
||||
SingletonResource resource) {
|
||||
return setResource(new TenantIdentifier(null, null, null), key, resource);
|
||||
return setResource(appUsedForTesting, key, resource);
|
||||
}
|
||||
|
||||
public interface Func<T> {
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
package io.supertokens.authRecipe;
|
||||
|
||||
import io.supertokens.Main;
|
||||
import io.supertokens.ResourceDistributor;
|
||||
import io.supertokens.authRecipe.exception.*;
|
||||
import io.supertokens.bulkimport.BulkImportUserUtils;
|
||||
import io.supertokens.featureflag.exceptions.FeatureNotEnabledException;
|
||||
|
|
@ -59,7 +60,7 @@ public class AuthRecipe {
|
|||
@TestOnly
|
||||
public static boolean unlinkAccounts(Main main, String recipeUserId)
|
||||
throws StorageQueryException, UnknownUserIdException, InputUserIdIsNotAPrimaryUserException {
|
||||
return unlinkAccounts(main, new AppIdentifier(null, null), StorageLayer.getStorage(main), recipeUserId);
|
||||
return unlinkAccounts(main, ResourceDistributor.getAppForTesting().toAppIdentifier(), StorageLayer.getStorage(main), recipeUserId);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -124,7 +125,7 @@ public class AuthRecipe {
|
|||
@TestOnly
|
||||
public static AuthRecipeUserInfo getUserById(Main main, String userId)
|
||||
throws StorageQueryException {
|
||||
return getUserById(new AppIdentifier(null, null), StorageLayer.getStorage(main), userId);
|
||||
return getUserById(ResourceDistributor.getAppForTesting().toAppIdentifier(), StorageLayer.getStorage(main), userId);
|
||||
}
|
||||
|
||||
public static AuthRecipeUserInfo getUserById(AppIdentifier appIdentifier, Storage storage, String userId)
|
||||
|
|
@ -203,7 +204,7 @@ public class AuthRecipe {
|
|||
throws StorageQueryException, UnknownUserIdException, InputUserIdIsNotAPrimaryUserException,
|
||||
RecipeUserIdAlreadyLinkedWithAnotherPrimaryUserIdException,
|
||||
AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException {
|
||||
return canLinkAccounts(new AppIdentifier(null, null), StorageLayer.getStorage(main), recipeUserId,
|
||||
return canLinkAccounts(ResourceDistributor.getAppForTesting().toAppIdentifier(), StorageLayer.getStorage(main), recipeUserId,
|
||||
primaryUserId);
|
||||
}
|
||||
|
||||
|
|
@ -508,7 +509,7 @@ public class AuthRecipe {
|
|||
FeatureNotEnabledException, InputUserIdIsNotAPrimaryUserException,
|
||||
RecipeUserIdAlreadyLinkedWithAnotherPrimaryUserIdException {
|
||||
try {
|
||||
return linkAccounts(main, new AppIdentifier(null, null),
|
||||
return linkAccounts(main, ResourceDistributor.getAppForTesting().toAppIdentifier(),
|
||||
StorageLayer.getStorage(main), recipeUserId, primaryUserId);
|
||||
} catch (TenantOrAppNotFoundException e) {
|
||||
throw new RuntimeException(e);
|
||||
|
|
@ -648,7 +649,7 @@ public class AuthRecipe {
|
|||
String recipeUserId)
|
||||
throws StorageQueryException, AccountInfoAlreadyAssociatedWithAnotherPrimaryUserIdException,
|
||||
RecipeUserIdAlreadyLinkedWithPrimaryUserIdException, UnknownUserIdException {
|
||||
return canCreatePrimaryUser(new AppIdentifier(null, null), StorageLayer.getStorage(main), recipeUserId);
|
||||
return canCreatePrimaryUser(ResourceDistributor.getAppForTesting().toAppIdentifier(), StorageLayer.getStorage(main), recipeUserId);
|
||||
}
|
||||
|
||||
public static CreatePrimaryUserResult canCreatePrimaryUser(AppIdentifier appIdentifier,
|
||||
|
|
@ -920,7 +921,7 @@ public class AuthRecipe {
|
|||
RecipeUserIdAlreadyLinkedWithPrimaryUserIdException, UnknownUserIdException,
|
||||
FeatureNotEnabledException {
|
||||
try {
|
||||
return createPrimaryUser(main, new AppIdentifier(null, null), StorageLayer.getStorage(main), recipeUserId);
|
||||
return createPrimaryUser(main, ResourceDistributor.getAppForTesting().toAppIdentifier(), StorageLayer.getStorage(main), recipeUserId);
|
||||
} catch (TenantOrAppNotFoundException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
|
@ -1181,7 +1182,7 @@ public class AuthRecipe {
|
|||
RECIPE_ID[] includeRecipeIds) throws StorageQueryException {
|
||||
try {
|
||||
Storage storage = StorageLayer.getStorage(main);
|
||||
return getUsersCountForTenant(TenantIdentifier.BASE_TENANT, storage, includeRecipeIds);
|
||||
return getUsersCountForTenant(ResourceDistributor.getAppForTesting(), storage, includeRecipeIds);
|
||||
} catch (TenantOrAppNotFoundException | BadPermissionException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
|
|
@ -1231,7 +1232,7 @@ public class AuthRecipe {
|
|||
throws StorageQueryException, UserPaginationToken.InvalidTokenException {
|
||||
try {
|
||||
Storage storage = StorageLayer.getStorage(main);
|
||||
return getUsers(TenantIdentifier.BASE_TENANT, storage,
|
||||
return getUsers(ResourceDistributor.getAppForTesting(), storage,
|
||||
limit, timeJoinedOrder, paginationToken, includeRecipeIds, dashboardSearchTags);
|
||||
} catch (TenantOrAppNotFoundException e) {
|
||||
throw new IllegalStateException(e);
|
||||
|
|
@ -1389,7 +1390,7 @@ public class AuthRecipe {
|
|||
public static void deleteUser(Main main, String userId, boolean removeAllLinkedAccounts)
|
||||
throws StorageQueryException, StorageTransactionLogicException {
|
||||
Storage storage = StorageLayer.getStorage(main);
|
||||
AppIdentifier appIdentifier = new AppIdentifier(null, null);
|
||||
AppIdentifier appIdentifier = ResourceDistributor.getAppForTesting().toAppIdentifier();
|
||||
UserIdMapping mapping = io.supertokens.useridmapping.UserIdMapping.getUserIdMapping(appIdentifier,
|
||||
storage, userId, UserIdType.ANY);
|
||||
|
||||
|
|
@ -1400,7 +1401,7 @@ public class AuthRecipe {
|
|||
public static void deleteUser(Main main, String userId)
|
||||
throws StorageQueryException, StorageTransactionLogicException {
|
||||
Storage storage = StorageLayer.getStorage(main);
|
||||
AppIdentifier appIdentifier = new AppIdentifier(null, null);
|
||||
AppIdentifier appIdentifier = ResourceDistributor.getAppForTesting().toAppIdentifier();
|
||||
UserIdMapping mapping = io.supertokens.useridmapping.UserIdMapping.getUserIdMapping(appIdentifier,
|
||||
storage, userId, UserIdType.ANY);
|
||||
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ import io.supertokens.multitenancy.Multitenancy;
|
|||
import io.supertokens.multitenancy.exception.AnotherPrimaryUserWithEmailAlreadyExistsException;
|
||||
import io.supertokens.multitenancy.exception.AnotherPrimaryUserWithPhoneNumberAlreadyExistsException;
|
||||
import io.supertokens.multitenancy.exception.AnotherPrimaryUserWithThirdPartyInfoAlreadyExistsException;
|
||||
import io.supertokens.output.Logging;
|
||||
import io.supertokens.passwordless.Passwordless;
|
||||
import io.supertokens.pluginInterface.Storage;
|
||||
import io.supertokens.pluginInterface.StorageUtils;
|
||||
|
|
@ -209,13 +210,28 @@ public class BulkImport {
|
|||
Storage bulkImportProxyStorage, List<BulkImportUser> users, Storage[] allStoragesForApp)
|
||||
throws StorageTransactionLogicException {
|
||||
try {
|
||||
Logging.debug(main, TenantIdentifier.BASE_TENANT, "Processing login methods..");
|
||||
processUsersLoginMethods(main, appIdentifier, bulkImportProxyStorage, users);
|
||||
Logging.debug(main, TenantIdentifier.BASE_TENANT, "Processing login methods DONE");
|
||||
Logging.debug(main, TenantIdentifier.BASE_TENANT, "Creating Primary users and linking accounts..");
|
||||
createPrimaryUsersAndLinkAccounts(main, appIdentifier, bulkImportProxyStorage, users);
|
||||
Logging.debug(main, TenantIdentifier.BASE_TENANT, "Creating Primary users and linking accounts DONE");
|
||||
Logging.debug(main, TenantIdentifier.BASE_TENANT, "Creating user id mappings..");
|
||||
createMultipleUserIdMapping(appIdentifier, users, allStoragesForApp);
|
||||
Logging.debug(main, TenantIdentifier.BASE_TENANT, "Creating user id mappings DONE");
|
||||
Logging.debug(main, TenantIdentifier.BASE_TENANT, "Verifying email addresses..");
|
||||
verifyMultipleEmailForAllLoginMethods(appIdentifier, bulkImportProxyStorage, users);
|
||||
Logging.debug(main, TenantIdentifier.BASE_TENANT, "Verifying email addresses DONE");
|
||||
Logging.debug(main, TenantIdentifier.BASE_TENANT, "Creating TOTP devices..");
|
||||
createMultipleTotpDevices(main, appIdentifier, bulkImportProxyStorage, users);
|
||||
Logging.debug(main, TenantIdentifier.BASE_TENANT, "Creating TOTP devices DONE");
|
||||
Logging.debug(main, TenantIdentifier.BASE_TENANT, "Creating user metadata..");
|
||||
createMultipleUserMetadata(appIdentifier, bulkImportProxyStorage, users);
|
||||
Logging.debug(main, TenantIdentifier.BASE_TENANT, "Creating user metadata DONE");
|
||||
Logging.debug(main, TenantIdentifier.BASE_TENANT, "Creating user roles..");
|
||||
createMultipleUserRoles(main, appIdentifier, bulkImportProxyStorage, users);
|
||||
Logging.debug(main, TenantIdentifier.BASE_TENANT, "Creating user roles DONE");
|
||||
Logging.debug(main, TenantIdentifier.BASE_TENANT, "Effective processUsersImportSteps DONE");
|
||||
} catch ( StorageQueryException | FeatureNotEnabledException |
|
||||
TenantOrAppNotFoundException e) {
|
||||
throw new StorageTransactionLogicException(e);
|
||||
|
|
@ -225,6 +241,7 @@ public class BulkImport {
|
|||
public static void processUsersLoginMethods(Main main, AppIdentifier appIdentifier, Storage storage,
|
||||
List<BulkImportUser> users) throws StorageTransactionLogicException {
|
||||
//sort login methods together
|
||||
Logging.debug(main, TenantIdentifier.BASE_TENANT, "Sorting login methods by recipeId..");
|
||||
Map<String, List<LoginMethod>> sortedLoginMethods = new HashMap<>();
|
||||
for (BulkImportUser user: users) {
|
||||
for(LoginMethod loginMethod : user.loginMethods){
|
||||
|
|
@ -236,19 +253,25 @@ public class BulkImport {
|
|||
}
|
||||
|
||||
List<ImportUserBase> importedUsers = new ArrayList<>();
|
||||
if (sortedLoginMethods.containsKey("emailpassword")) {
|
||||
importedUsers.addAll(
|
||||
processEmailPasswordLoginMethods(main, storage, sortedLoginMethods.get("emailpassword"),
|
||||
if (sortedLoginMethods.containsKey("emailpassword")) {
|
||||
Logging.debug(main, TenantIdentifier.BASE_TENANT, "Processing emailpassword login methods..");
|
||||
importedUsers.addAll(
|
||||
processEmailPasswordLoginMethods(main, storage, sortedLoginMethods.get("emailpassword"),
|
||||
appIdentifier));
|
||||
Logging.debug(main, TenantIdentifier.BASE_TENANT, "Processing emailpassword login methods DONE");
|
||||
}
|
||||
if (sortedLoginMethods.containsKey("thirdparty")) {
|
||||
Logging.debug(main, TenantIdentifier.BASE_TENANT, "Processing thirdparty login methods..");
|
||||
importedUsers.addAll(
|
||||
processThirdpartyLoginMethods(main, storage, sortedLoginMethods.get("thirdparty"),
|
||||
appIdentifier));
|
||||
Logging.debug(main, TenantIdentifier.BASE_TENANT, "Processing thirdparty login methods DONE");
|
||||
}
|
||||
if (sortedLoginMethods.containsKey("passwordless")) {
|
||||
Logging.debug(main, TenantIdentifier.BASE_TENANT, "Processing passwordless login methods..");
|
||||
importedUsers.addAll(processPasswordlessLoginMethods(main, appIdentifier, storage,
|
||||
sortedLoginMethods.get("passwordless")));
|
||||
Logging.debug(main, TenantIdentifier.BASE_TENANT, "Processing passwordless login methods DONE");
|
||||
}
|
||||
Set<String> actualKeys = new HashSet<>(sortedLoginMethods.keySet());
|
||||
List.of("emailpassword", "thirdparty", "passwordless").forEach(actualKeys::remove);
|
||||
|
|
@ -288,9 +311,9 @@ public class BulkImport {
|
|||
}
|
||||
|
||||
Passwordless.createPasswordlessUsers(storage, usersToImport);
|
||||
|
||||
return usersToImport;
|
||||
} catch (StorageQueryException | StorageTransactionLogicException e) {
|
||||
Logging.debug(main, TenantIdentifier.BASE_TENANT, "exception: " + e.getMessage());
|
||||
if (e.getCause() instanceof BulkImportBatchInsertException) {
|
||||
Map<String, Exception> errorsByPosition = ((BulkImportBatchInsertException) e.getCause()).exceptionByUserId;
|
||||
for (String userid : errorsByPosition.keySet()) {
|
||||
|
|
@ -664,7 +687,7 @@ public class BulkImport {
|
|||
rolesToUserByTenant.put(tenantIdentifier, new HashMap<>());
|
||||
}
|
||||
String userIdToUse = user.externalUserId != null ?
|
||||
user.externalUserId : user.primaryUserId;
|
||||
user.externalUserId : user.id;
|
||||
if(!rolesToUserByTenant.get(tenantIdentifier).containsKey(userIdToUse)){
|
||||
rolesToUserByTenant.get(tenantIdentifier).put(userIdToUse, new ArrayList<>());
|
||||
}
|
||||
|
|
@ -683,12 +706,31 @@ public class BulkImport {
|
|||
Map<String, String> emailToUserId = collectVerifiedEmailAddressesByUserIds(users);
|
||||
try {
|
||||
verifyCollectedEmailAddressesForUsers(appIdentifier, storage, emailToUserId);
|
||||
} catch (StorageQueryException e) {
|
||||
} catch (StorageQueryException | StorageTransactionLogicException e) {
|
||||
if (e.getCause() instanceof BulkImportBatchInsertException) {
|
||||
Map<String, Exception> errorsByPosition =
|
||||
((BulkImportBatchInsertException) e.getCause()).exceptionByUserId;
|
||||
for (String userid : errorsByPosition.keySet()) {
|
||||
Exception exception = errorsByPosition.get(userid);
|
||||
if (exception instanceof DuplicateEmailException) {
|
||||
String message =
|
||||
"E043: Email " + errorsByPosition.get(userid) + " is already verified for the user";
|
||||
errorsByPosition.put(userid, new Exception(message));
|
||||
} else if (exception instanceof NullPointerException) {
|
||||
String message = "E044: null email address was found for the userId " + userid +
|
||||
" while verifying the email";
|
||||
errorsByPosition.put(userid, new Exception(message));
|
||||
}
|
||||
}
|
||||
throw new StorageTransactionLogicException(
|
||||
new BulkImportBatchInsertException("translated", errorsByPosition));
|
||||
}
|
||||
throw new StorageTransactionLogicException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static void verifyCollectedEmailAddressesForUsers(AppIdentifier appIdentifier, Storage storage, Map<String, String> emailToUserId)
|
||||
private static void verifyCollectedEmailAddressesForUsers(AppIdentifier appIdentifier, Storage storage,
|
||||
Map<String, String> emailToUserId)
|
||||
throws StorageQueryException, StorageTransactionLogicException {
|
||||
if(!emailToUserId.isEmpty()) {
|
||||
EmailVerificationSQLStorage emailVerificationSQLStorage = StorageUtils
|
||||
|
|
@ -706,10 +748,11 @@ public class BulkImport {
|
|||
|
||||
@NotNull
|
||||
private static Map<String, String> collectVerifiedEmailAddressesByUserIds(List<BulkImportUser> users) {
|
||||
Map<String, String> emailToUserId = new HashMap<>();
|
||||
Map<String, String> emailToUserId = new LinkedHashMap<>();
|
||||
for (BulkImportUser user : users) {
|
||||
for (LoginMethod lm : user.loginMethods) {
|
||||
if(lm.isVerified) {
|
||||
//we skip passwordless` 'null' email addresses
|
||||
if (lm.isVerified && !(lm.recipeId.equals("passwordless") && lm.email == null)) {
|
||||
//collect the verified email addresses for the userId
|
||||
emailToUserId.put(lm.getSuperTokenOrExternalUserId(), lm.email);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -52,6 +52,8 @@ public class Config extends ResourceDistributor.SingletonResource {
|
|||
final ObjectMapper mapper = new ObjectMapper(new YAMLFactory());
|
||||
Object configObj = mapper.readValue(new File(configFilePath), Object.class);
|
||||
JsonObject jsonConfig = new GsonBuilder().serializeNulls().create().toJsonTree(configObj).getAsJsonObject();
|
||||
CoreConfig.updateConfigJsonFromEnv(jsonConfig);
|
||||
StorageLayer.updateConfigJsonFromEnv(main, jsonConfig);
|
||||
CoreConfig config = ConfigMapper.mapConfig(jsonConfig, CoreConfig.class);
|
||||
config.normalizeAndValidate(main, true);
|
||||
this.core = config;
|
||||
|
|
@ -91,12 +93,20 @@ public class Config extends ResourceDistributor.SingletonResource {
|
|||
// omit them from the output json.
|
||||
ObjectMapper yamlReader = new ObjectMapper(new YAMLFactory());
|
||||
Object obj = yamlReader.readValue(new File(getConfigFilePath(main)), Object.class);
|
||||
return new GsonBuilder().serializeNulls().create().toJsonTree(obj).getAsJsonObject();
|
||||
JsonObject configJson = new GsonBuilder().serializeNulls().create().toJsonTree(obj).getAsJsonObject();
|
||||
CoreConfig.updateConfigJsonFromEnv(configJson);
|
||||
StorageLayer.updateConfigJsonFromEnv(main, configJson);
|
||||
return configJson;
|
||||
}
|
||||
|
||||
private static String getConfigFilePath(Main main) {
|
||||
String configFile = "config.yaml";
|
||||
if (Main.isTesting) {
|
||||
String workerId = System.getProperty("org.gradle.test.worker", "");
|
||||
configFile = "config" + workerId + ".yaml";
|
||||
}
|
||||
return CLIOptions.get(main).getConfigFilePath() == null
|
||||
? CLIOptions.get(main).getInstallationPath() + "config.yaml"
|
||||
? CLIOptions.get(main).getInstallationPath() + configFile
|
||||
: CLIOptions.get(main).getConfigFilePath();
|
||||
}
|
||||
|
||||
|
|
@ -305,7 +315,7 @@ public class Config extends ResourceDistributor.SingletonResource {
|
|||
@TestOnly
|
||||
public static CoreConfig getConfig(Main main) {
|
||||
try {
|
||||
return getConfig(new TenantIdentifier(null, null, null), main);
|
||||
return getConfig(ResourceDistributor.getAppForTesting(), main);
|
||||
} catch (TenantOrAppNotFoundException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -67,7 +67,8 @@ public class CoreConfig {
|
|||
"oauth_provider_public_service_url",
|
||||
"oauth_provider_admin_service_url",
|
||||
"oauth_provider_consent_login_base_url",
|
||||
"oauth_provider_url_configured_in_oauth_provider"
|
||||
"oauth_provider_url_configured_in_oauth_provider",
|
||||
"saml_legacy_acs_url"
|
||||
};
|
||||
|
||||
@IgnoreForAnnotationCheck
|
||||
|
|
@ -75,11 +76,13 @@ public class CoreConfig {
|
|||
@ConfigDescription("The version of the core config.")
|
||||
private int core_config_version = -1;
|
||||
|
||||
@EnvName("ACCESS_TOKEN_VALIDITY")
|
||||
@NotConflictingInApp
|
||||
@JsonProperty
|
||||
@ConfigDescription("Time in seconds for how long an access token is valid for. [Default: 3600 (1 hour)]")
|
||||
private long access_token_validity = 3600; // in seconds
|
||||
|
||||
@EnvName("ACCESS_TOKEN_BLACKLISTING")
|
||||
@NotConflictingInApp
|
||||
@JsonProperty
|
||||
@ConfigDescription(
|
||||
|
|
@ -88,17 +91,20 @@ public class CoreConfig {
|
|||
"call that requires authentication. (Default: false)")
|
||||
private boolean access_token_blacklisting = false;
|
||||
|
||||
@EnvName("REFRESH_TOKEN_VALIDITY")
|
||||
@NotConflictingInApp
|
||||
@JsonProperty
|
||||
@ConfigDescription("Time in mins for how long a refresh token is valid for. [Default: 60 * 2400 (100 days)]")
|
||||
private double refresh_token_validity = 60 * 2400; // in mins
|
||||
|
||||
@EnvName("PASSWORD_RESET_TOKEN_LIFETIME")
|
||||
@IgnoreForAnnotationCheck
|
||||
@JsonProperty
|
||||
@ConfigDescription(
|
||||
"Time in milliseconds for how long a password reset token / link is valid for. [Default: 3600000 (1 hour)]")
|
||||
private long password_reset_token_lifetime = 3600000; // in MS
|
||||
|
||||
@EnvName("EMAIL_VERIFICATION_TOKEN_LIFETIME")
|
||||
@IgnoreForAnnotationCheck
|
||||
@JsonProperty
|
||||
@ConfigDescription(
|
||||
|
|
@ -106,23 +112,27 @@ public class CoreConfig {
|
|||
" 1000 (1 day)]")
|
||||
private long email_verification_token_lifetime = 24 * 3600 * 1000; // in MS
|
||||
|
||||
@EnvName("PASSWORDLESS_MAX_CODE_INPUT_ATTEMPTS")
|
||||
@IgnoreForAnnotationCheck
|
||||
@JsonProperty
|
||||
@ConfigDescription(
|
||||
"The maximum number of code input attempts per login before the user needs to restart. (Default: 5)")
|
||||
private int passwordless_max_code_input_attempts = 5;
|
||||
|
||||
@EnvName("PASSWORDLESS_CODE_LIFETIME")
|
||||
@IgnoreForAnnotationCheck
|
||||
@JsonProperty
|
||||
@ConfigDescription(
|
||||
"Time in milliseconds for how long a passwordless code is valid for. [Default: 900000 (15 mins)]")
|
||||
private long passwordless_code_lifetime = 900000; // in MS
|
||||
|
||||
@EnvName("TOTP_MAX_ATTEMPTS")
|
||||
@IgnoreForAnnotationCheck
|
||||
@JsonProperty
|
||||
@ConfigDescription("The maximum number of invalid TOTP attempts that will trigger rate limiting. (Default: 5)")
|
||||
private int totp_max_attempts = 5;
|
||||
|
||||
@EnvName("TOTP_RATE_LIMIT_COOLDOWN_SEC")
|
||||
@IgnoreForAnnotationCheck
|
||||
@JsonProperty
|
||||
@ConfigDescription(
|
||||
|
|
@ -133,6 +143,7 @@ public class CoreConfig {
|
|||
@IgnoreForAnnotationCheck
|
||||
private final String logDefault = "asdkfahbdfk3kjHS";
|
||||
|
||||
@EnvName("INFO_LOG_PATH")
|
||||
@ConfigYamlOnly
|
||||
@JsonProperty
|
||||
@ConfigDescription(
|
||||
|
|
@ -141,6 +152,7 @@ public class CoreConfig {
|
|||
"directory/logs/info.log)")
|
||||
private String info_log_path = logDefault;
|
||||
|
||||
@EnvName("ERROR_LOG_PATH")
|
||||
@ConfigYamlOnly
|
||||
@JsonProperty
|
||||
@ConfigDescription(
|
||||
|
|
@ -149,6 +161,7 @@ public class CoreConfig {
|
|||
"directory/logs/error.log)")
|
||||
private String error_log_path = logDefault;
|
||||
|
||||
@EnvName("ACCESS_TOKEN_SIGNING_KEY_DYNAMIC")
|
||||
@NotConflictingInApp
|
||||
@JsonProperty
|
||||
@ConfigDescription(
|
||||
|
|
@ -156,17 +169,20 @@ public class CoreConfig {
|
|||
" be signed using a static signing key. (Default: true)")
|
||||
private boolean access_token_signing_key_dynamic = true;
|
||||
|
||||
@EnvName("ACCESS_TOKEN_DYNAMIC_SIGNING_KEY_UPDATE_INTERVAL")
|
||||
@NotConflictingInApp
|
||||
@JsonProperty("access_token_dynamic_signing_key_update_interval")
|
||||
@JsonAlias({"access_token_dynamic_signing_key_update_interval", "access_token_signing_key_update_interval"})
|
||||
@ConfigDescription("Time in hours for how frequently the dynamic signing key will change. [Default: 168 (1 week)]")
|
||||
private double access_token_dynamic_signing_key_update_interval = 168; // in hours
|
||||
|
||||
@EnvName("SUPERTOKENS_PORT")
|
||||
@ConfigYamlOnly
|
||||
@JsonProperty
|
||||
@ConfigDescription("The port at which SuperTokens service runs. (Default: 3567)")
|
||||
private int port = 3567;
|
||||
|
||||
@EnvName("SUPERTOKENS_HOST")
|
||||
@ConfigYamlOnly
|
||||
@JsonProperty
|
||||
@ConfigDescription(
|
||||
|
|
@ -174,11 +190,13 @@ public class CoreConfig {
|
|||
" address associated with your machine. (Default: localhost)")
|
||||
private String host = "localhost";
|
||||
|
||||
@EnvName("MAX_SERVER_POOL_SIZE")
|
||||
@ConfigYamlOnly
|
||||
@JsonProperty
|
||||
@ConfigDescription("Sets the max thread pool size for incoming http server requests. (Default: 10)")
|
||||
private int max_server_pool_size = 10;
|
||||
|
||||
@EnvName("API_KEYS")
|
||||
@NotConflictingInApp
|
||||
@JsonProperty
|
||||
@HideFromDashboard
|
||||
|
|
@ -188,6 +206,7 @@ public class CoreConfig {
|
|||
"length of 20 chars. (Default: null)")
|
||||
private String api_keys = null;
|
||||
|
||||
@EnvName("DISABLE_TELEMETRY")
|
||||
@NotConflictingInApp
|
||||
@JsonProperty
|
||||
@ConfigDescription(
|
||||
|
|
@ -195,27 +214,32 @@ public class CoreConfig {
|
|||
"(Default: false)")
|
||||
private boolean disable_telemetry = false;
|
||||
|
||||
@EnvName("PASSWORD_HASHING_ALG")
|
||||
@NotConflictingInApp
|
||||
@JsonProperty
|
||||
@ConfigDescription("The password hashing algorithm to use. Values are \"ARGON2\" | \"BCRYPT\". (Default: BCRYPT)")
|
||||
@EnumProperty({"ARGON2", "BCRYPT"})
|
||||
private String password_hashing_alg = "BCRYPT";
|
||||
|
||||
@EnvName("ARGON2_ITERATIONS")
|
||||
@ConfigYamlOnly
|
||||
@JsonProperty
|
||||
@ConfigDescription("Number of iterations for argon2 password hashing. (Default: 1)")
|
||||
private int argon2_iterations = 1;
|
||||
|
||||
@EnvName("ARGON2_MEMORY_KB")
|
||||
@ConfigYamlOnly
|
||||
@JsonProperty
|
||||
@ConfigDescription("Amount of memory in kb for argon2 password hashing. [Default: 87795 (85 mb)]")
|
||||
private int argon2_memory_kb = 87795; // 85 mb
|
||||
|
||||
@EnvName("ARGON2_PARALLELISM")
|
||||
@ConfigYamlOnly
|
||||
@JsonProperty
|
||||
@ConfigDescription("Amount of parallelism for argon2 password hashing. (Default: 2)")
|
||||
private int argon2_parallelism = 2;
|
||||
|
||||
@EnvName("ARGON2_HASHING_POOL_SIZE")
|
||||
@ConfigYamlOnly
|
||||
@JsonProperty
|
||||
@ConfigDescription(
|
||||
|
|
@ -223,6 +247,7 @@ public class CoreConfig {
|
|||
"(Default: 1)")
|
||||
private int argon2_hashing_pool_size = 1;
|
||||
|
||||
@EnvName("FIREBASE_PASSWORD_HASHING_POOL_SIZE")
|
||||
@ConfigYamlOnly
|
||||
@JsonProperty
|
||||
@ConfigDescription(
|
||||
|
|
@ -230,6 +255,7 @@ public class CoreConfig {
|
|||
"(Default: 1)")
|
||||
private int firebase_password_hashing_pool_size = 1;
|
||||
|
||||
@EnvName("BCRYPT_LOG_ROUNDS")
|
||||
@ConfigYamlOnly
|
||||
@JsonProperty
|
||||
@ConfigDescription("Number of rounds to set for bcrypt password hashing. (Default: 11)")
|
||||
|
|
@ -245,13 +271,16 @@ public class CoreConfig {
|
|||
// # webserver_https_enabled:
|
||||
@ConfigYamlOnly
|
||||
@JsonProperty
|
||||
@IgnoreForAnnotationCheck
|
||||
private boolean webserver_https_enabled = false;
|
||||
|
||||
@EnvName("BASE_PATH")
|
||||
@ConfigYamlOnly
|
||||
@JsonProperty
|
||||
@ConfigDescription("Used to prepend a base path to all APIs when querying the core.")
|
||||
private String base_path = "";
|
||||
|
||||
@EnvName("LOG_LEVEL")
|
||||
@ConfigYamlOnly
|
||||
@JsonProperty
|
||||
@ConfigDescription(
|
||||
|
|
@ -260,11 +289,13 @@ public class CoreConfig {
|
|||
@EnumProperty({"DEBUG", "INFO", "WARN", "ERROR", "NONE"})
|
||||
private String log_level = "INFO";
|
||||
|
||||
@EnvName("FIREBASE_PASSWORD_HASHING_SIGNER_KEY")
|
||||
@NotConflictingInApp
|
||||
@JsonProperty
|
||||
@ConfigDescription("The signer key used for firebase scrypt password hashing. (Default: null)")
|
||||
private String firebase_password_hashing_signer_key = null;
|
||||
|
||||
@EnvName("IP_ALLOW_REGEX")
|
||||
@IgnoreForAnnotationCheck
|
||||
@JsonProperty
|
||||
@ConfigDescription(
|
||||
|
|
@ -272,6 +303,7 @@ public class CoreConfig {
|
|||
"127\\.\\d+\\.\\d+\\.\\d+|::1|0:0:0:0:0:0:0:1 to allow only localhost to query the core")
|
||||
private String ip_allow_regex = null;
|
||||
|
||||
@EnvName("IP_DENY_REGEX")
|
||||
@IgnoreForAnnotationCheck
|
||||
@JsonProperty
|
||||
@ConfigDescription(
|
||||
|
|
@ -279,6 +311,7 @@ public class CoreConfig {
|
|||
" address.")
|
||||
private String ip_deny_regex = null;
|
||||
|
||||
@EnvName("OAUTH_PROVIDER_PUBLIC_SERVICE_URL")
|
||||
@NotConflictingInApp
|
||||
@JsonProperty
|
||||
@HideFromDashboard
|
||||
|
|
@ -286,6 +319,7 @@ public class CoreConfig {
|
|||
"If specified, the core uses this URL to connect to the OAuth provider public service.")
|
||||
private String oauth_provider_public_service_url = null;
|
||||
|
||||
@EnvName("OAUTH_PROVIDER_ADMIN_SERVICE_URL")
|
||||
@NotConflictingInApp
|
||||
@JsonProperty
|
||||
@HideFromDashboard
|
||||
|
|
@ -293,6 +327,7 @@ public class CoreConfig {
|
|||
"If specified, the core uses this URL to connect to the OAuth provider admin service.")
|
||||
private String oauth_provider_admin_service_url = null;
|
||||
|
||||
@EnvName("OAUTH_PROVIDER_CONSENT_LOGIN_BASE_URL")
|
||||
@NotConflictingInApp
|
||||
@JsonProperty
|
||||
@HideFromDashboard
|
||||
|
|
@ -300,6 +335,7 @@ public class CoreConfig {
|
|||
"If specified, the core uses this URL to replace the default consent and login URLs to {apiDomain}.")
|
||||
private String oauth_provider_consent_login_base_url = null;
|
||||
|
||||
@EnvName("OAUTH_PROVIDER_URL_CONFIGURED_IN_OAUTH_PROVIDER")
|
||||
@NotConflictingInApp
|
||||
@JsonProperty
|
||||
@HideFromDashboard
|
||||
|
|
@ -307,12 +343,14 @@ public class CoreConfig {
|
|||
"If specified, the core uses this URL to parse responses from the oauth provider when the oauth provider's internal address differs from the known public provider address.")
|
||||
private String oauth_provider_url_configured_in_oauth_provider = null;
|
||||
|
||||
@EnvName("OAUTH_CLIENT_SECRET_ENCRYPTION_KEY")
|
||||
@ConfigYamlOnly
|
||||
@JsonProperty
|
||||
@HideFromDashboard
|
||||
@ConfigDescription("The encryption key used for saving OAuth client secret on the database.")
|
||||
private String oauth_client_secret_encryption_key = null;
|
||||
|
||||
@EnvName("SUPERTOKENS_SAAS_SECRET")
|
||||
@ConfigYamlOnly
|
||||
@JsonProperty
|
||||
@ConfigDescription(
|
||||
|
|
@ -322,6 +360,7 @@ public class CoreConfig {
|
|||
"regular api_keys config.")
|
||||
private String supertokens_saas_secret = null;
|
||||
|
||||
@EnvName("SUPERTOKENS_MAX_CDI_VERSION")
|
||||
@NotConflictingInApp
|
||||
@JsonProperty
|
||||
@HideFromDashboard
|
||||
|
|
@ -331,6 +370,7 @@ public class CoreConfig {
|
|||
"null)")
|
||||
private String supertokens_max_cdi_version = null;
|
||||
|
||||
@EnvName("SUPERTOKENS_SAAS_LOAD_ONLY_CUD")
|
||||
@ConfigYamlOnly
|
||||
@JsonProperty
|
||||
@ConfigDescription(
|
||||
|
|
@ -338,29 +378,73 @@ public class CoreConfig {
|
|||
"the database and block all other CUDs from being used from this instance.")
|
||||
private String supertokens_saas_load_only_cud = null;
|
||||
|
||||
@EnvName("SAML_LEGACY_ACS_URL")
|
||||
@NotConflictingInApp
|
||||
@JsonProperty
|
||||
@ConfigDescription("If specified, uses this URL as ACS URL for handling legacy SAML clients")
|
||||
@HideFromDashboard
|
||||
private String saml_legacy_acs_url = null;
|
||||
|
||||
@EnvName("SAML_SP_ENTITY_ID")
|
||||
@JsonProperty
|
||||
@IgnoreForAnnotationCheck
|
||||
@ConfigDescription("Service provider's entity ID")
|
||||
private String saml_sp_entity_id = null;
|
||||
|
||||
@EnvName("SAML_CLAIMS_VALIDITY")
|
||||
@JsonProperty
|
||||
@IgnoreForAnnotationCheck
|
||||
@ConfigDescription("Duration for which SAML claims will be valid before it is consumed")
|
||||
private long saml_claims_validity = 300000;
|
||||
|
||||
@EnvName("SAML_RELAY_STATE_VALIDITY")
|
||||
@JsonProperty
|
||||
@IgnoreForAnnotationCheck
|
||||
@ConfigDescription("Duration for which SAML relay state will be valid before it is consumed")
|
||||
private long saml_relay_state_validity = 300000;
|
||||
|
||||
@IgnoreForAnnotationCheck
|
||||
private Set<LOG_LEVEL> allowedLogLevels = null;
|
||||
|
||||
@IgnoreForAnnotationCheck
|
||||
private boolean isNormalizedAndValid = false;
|
||||
|
||||
@EnvName("BULK_MIGRATION_PARALLELISM")
|
||||
@NotConflictingInApp
|
||||
@JsonProperty
|
||||
@ConfigDescription("If specified, the supertokens core will use the specified number of threads to complete the " +
|
||||
"migration of users. (Default: number of available processor cores).")
|
||||
private int bulk_migration_parallelism = Runtime.getRuntime().availableProcessors();
|
||||
|
||||
@EnvName("BULK_MIGRATION_BATCH_SIZE")
|
||||
@NotConflictingInApp
|
||||
@JsonProperty
|
||||
@ConfigDescription("If specified, the supertokens core will load the specified number of users for migrating in " +
|
||||
"one single batch. (Default: 8000)")
|
||||
private int bulk_migration_batch_size = 8000;
|
||||
|
||||
@EnvName("WEBAUTHN_RECOVER_ACCOUNT_TOKEN_LIFETIME")
|
||||
@NotConflictingInApp
|
||||
@JsonProperty
|
||||
@ConfigDescription("Time in milliseconds for how long a webauthn account recovery token is valid for. [Default: 3600000 (1 hour)]")
|
||||
private long webauthn_recover_account_token_lifetime = 3600000; // in MS;
|
||||
|
||||
@EnvName("OTEL_COLLECTOR_CONNECTION_URI")
|
||||
@ConfigYamlOnly
|
||||
@JsonProperty
|
||||
@ConfigDescription(
|
||||
"The URL of the OpenTelemetry collector to which the core will send telemetry data. " +
|
||||
"This should be in the format http://<host>:<port> or https://<host>:<port>. (Default: " +
|
||||
"null)")
|
||||
private String otel_collector_connection_uri = null;
|
||||
|
||||
@EnvName("DEADLOCK_LOGGER_ENABLE")
|
||||
@ConfigYamlOnly
|
||||
@JsonProperty
|
||||
@ConfigDescription(
|
||||
"Enables or disables the deadlock logger. (Default: false)")
|
||||
private boolean deadlock_logger_enable = false;
|
||||
|
||||
@IgnoreForAnnotationCheck
|
||||
private static boolean disableOAuthValidationForTest = false;
|
||||
|
||||
|
|
@ -429,6 +513,10 @@ public class CoreConfig {
|
|||
return ip_deny_regex;
|
||||
}
|
||||
|
||||
public String getLogLevel() {
|
||||
return log_level;
|
||||
}
|
||||
|
||||
public Set<LOG_LEVEL> getLogLevels(Main main) {
|
||||
if (allowedLogLevels != null) {
|
||||
return allowedLogLevels;
|
||||
|
|
@ -608,12 +696,76 @@ public class CoreConfig {
|
|||
return bulk_migration_batch_size;
|
||||
}
|
||||
|
||||
public String getOtelCollectorConnectionURI() {
|
||||
return otel_collector_connection_uri;
|
||||
}
|
||||
|
||||
public boolean isDeadlockLoggerEnabled() {
|
||||
return deadlock_logger_enable;
|
||||
}
|
||||
|
||||
public String getSAMLLegacyACSURL() {
|
||||
return saml_legacy_acs_url;
|
||||
}
|
||||
|
||||
public String getSAMLSPEntityID() {
|
||||
return saml_sp_entity_id;
|
||||
}
|
||||
|
||||
public long getSAMLClaimsValidity() {
|
||||
return saml_claims_validity;
|
||||
}
|
||||
|
||||
public long getSAMLRelayStateValidity() {
|
||||
return saml_relay_state_validity;
|
||||
}
|
||||
|
||||
private String getConfigFileLocation(Main main) {
|
||||
return new File(CLIOptions.get(main).getConfigFilePath() == null
|
||||
? CLIOptions.get(main).getInstallationPath() + "config.yaml"
|
||||
: CLIOptions.get(main).getConfigFilePath()).getAbsolutePath();
|
||||
}
|
||||
|
||||
public static void updateConfigJsonFromEnv(JsonObject configJson) {
|
||||
Map<String, String> env = System.getenv();
|
||||
|
||||
for (Field field : CoreConfig.class.getDeclaredFields()) {
|
||||
if (field.isAnnotationPresent(EnvName.class)) {
|
||||
String envName = field.getAnnotation(EnvName.class).value();
|
||||
String stringValue = env.get(envName);
|
||||
|
||||
if (stringValue == null || stringValue.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (stringValue.startsWith("\"") && stringValue.endsWith("\"")) {
|
||||
stringValue = stringValue.substring(1, stringValue.length() - 1);
|
||||
stringValue = stringValue
|
||||
.replace("\\n", "\n")
|
||||
.replace("\\t", "\t")
|
||||
.replace("\\r", "\r")
|
||||
.replace("\\\"", "\"")
|
||||
.replace("\\'", "'")
|
||||
.replace("\\\\", "\\");
|
||||
}
|
||||
|
||||
if (field.getType().equals(String.class)) {
|
||||
configJson.addProperty(field.getName(), stringValue);
|
||||
} else if (field.getType().equals(int.class)) {
|
||||
configJson.addProperty(field.getName(), Integer.parseInt(stringValue));
|
||||
} else if (field.getType().equals(long.class)) {
|
||||
configJson.addProperty(field.getName(), Long.parseLong(stringValue));
|
||||
} else if (field.getType().equals(boolean.class)) {
|
||||
configJson.addProperty(field.getName(), Boolean.parseBoolean(stringValue));
|
||||
} else if (field.getType().equals(float.class)) {
|
||||
configJson.addProperty(field.getName(), Float.parseFloat(stringValue));
|
||||
} else if (field.getType().equals(double.class)) {
|
||||
configJson.addProperty(field.getName(), Double.parseDouble(stringValue));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void normalizeAndValidate(Main main, boolean includeConfigFilePath) throws InvalidConfigException {
|
||||
if (isNormalizedAndValid) {
|
||||
return;
|
||||
|
|
@ -836,6 +988,10 @@ public class CoreConfig {
|
|||
}
|
||||
|
||||
// Normalize
|
||||
if (saml_sp_entity_id == null) {
|
||||
saml_sp_entity_id = "https://saml.supertokens.com";
|
||||
}
|
||||
|
||||
if (ip_allow_regex != null) {
|
||||
ip_allow_regex = ip_allow_regex.trim();
|
||||
if (ip_allow_regex.equals("")) {
|
||||
|
|
@ -973,6 +1129,24 @@ public class CoreConfig {
|
|||
}
|
||||
}
|
||||
|
||||
if (Main.isTesting) {
|
||||
if (oauth_provider_public_service_url == null) {
|
||||
oauth_provider_public_service_url = "http://localhost:" + System.getProperty("ST_OAUTH_PROVIDER_SERVICE_PORT");
|
||||
}
|
||||
if (oauth_provider_admin_service_url == null) {
|
||||
oauth_provider_admin_service_url = "http://localhost:" + System.getProperty("ST_OAUTH_PROVIDER_ADMIN_PORT");
|
||||
}
|
||||
if (oauth_provider_url_configured_in_oauth_provider == null) {
|
||||
oauth_provider_url_configured_in_oauth_provider = "http://localhost:4444";
|
||||
}
|
||||
if (oauth_client_secret_encryption_key == null) {
|
||||
oauth_client_secret_encryption_key = "clientsecretencryptionkey";
|
||||
}
|
||||
if (oauth_provider_consent_login_base_url == null) {
|
||||
oauth_provider_consent_login_base_url = "http://localhost:3001/auth";
|
||||
}
|
||||
}
|
||||
|
||||
isNormalizedAndValid = true;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* Copyright (c) 2025, VRAI Labs and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* This software is licensed under the Apache License, Version 2.0 (the
|
||||
* "License") as published by the Apache Software Foundation.
|
||||
*
|
||||
* You may not use this file except in compliance with the License. You may
|
||||
* obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package io.supertokens.config.annotations;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
// Make annotation accessible at runtime so that config can be read from env
|
||||
@Target(ElementType.FIELD) // Annotation can only be applied to fields
|
||||
public @interface EnvName {
|
||||
String value(); // String value that provides a env var name for the field
|
||||
}
|
||||
|
|
@ -109,6 +109,9 @@ public class ProcessBulkImportUsers extends CronTask {
|
|||
}
|
||||
|
||||
List<List<BulkImportUser>> loadedUsersChunks = makeChunksOf(users, numberOfBatchChunks);
|
||||
for (List<BulkImportUser> chunk : loadedUsersChunks) {
|
||||
Logging.debug(main, app.getAsPublicTenantIdentifier(), "Chunk size: " + chunk.size());
|
||||
}
|
||||
|
||||
try {
|
||||
List<Future<?>> tasks = new ArrayList<>();
|
||||
|
|
@ -124,14 +127,26 @@ public class ProcessBulkImportUsers extends CronTask {
|
|||
Thread.sleep(1000);
|
||||
}
|
||||
Logging.debug(main, app.getAsPublicTenantIdentifier(), "Task " + task + " finished");
|
||||
Void result = (Void) task.get(); //to know if there were any errors while executing and for waiting in this thread for all the other threads to finish up
|
||||
try {
|
||||
Void result = (Void) task.get(); //to know if there were any errors while executing and for
|
||||
// waiting in this thread for all the other threads to finish up
|
||||
Logging.debug(main, app.getAsPublicTenantIdentifier(),
|
||||
"Task " + task + " finished with result: " + result);
|
||||
} catch (ExecutionException executionException) {
|
||||
Logging.error(main, app.getAsPublicTenantIdentifier(),
|
||||
"Error while processing bulk import users", true,
|
||||
executionException);
|
||||
throw new RuntimeException(executionException);
|
||||
}
|
||||
usersProcessed += loadedUsersChunks.get(tasks.indexOf(task)).size();
|
||||
failedUsers = bulkImportSQLStorage.getBulkImportUsersCount(app, BulkImportStorage.BULK_IMPORT_USER_STATUS.FAILED);
|
||||
Logging.debug(main, app.getAsPublicTenantIdentifier(), "Chunk " + tasks.indexOf(task) + " finished processing, all chunks processed: "
|
||||
+ usersProcessed + " users (" + failedUsers + " failed)");
|
||||
}
|
||||
|
||||
} catch (ExecutionException | InterruptedException e) {
|
||||
Logging.debug(main, app.getAsPublicTenantIdentifier(), "Processing round finished");
|
||||
} catch (InterruptedException e) {
|
||||
Logging.error(main, app.getAsPublicTenantIdentifier(), "Error while processing bulk import users", true,
|
||||
e);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -77,6 +77,8 @@ public class ProcessBulkUsersImportWorker implements Runnable {
|
|||
DbInitException {
|
||||
BulkImportUser user = null;
|
||||
try {
|
||||
Logging.debug(main, appIdentifier.getAsPublicTenantIdentifier(),
|
||||
"Processing bulk import users: " + users.size());
|
||||
final Storage[] allStoragesForApp = getAllProxyStoragesForApp(main, appIdentifier);
|
||||
int userIndexPointer = 0;
|
||||
List<BulkImportUser> validUsers = new ArrayList<>();
|
||||
|
|
@ -106,8 +108,8 @@ public class ProcessBulkUsersImportWorker implements Runnable {
|
|||
}
|
||||
// Since all the tenants of a user must share the storage, we will just use the
|
||||
// storage of the first tenantId of the first loginMethod
|
||||
|
||||
Map<SQLStorage, List<BulkImportUser>> partitionedUsers = partitionUsersByStorage(appIdentifier, validUsers);
|
||||
|
||||
for(SQLStorage bulkImportProxyStorage : partitionedUsers.keySet()) {
|
||||
boolean shouldRetryImmediatley = true;
|
||||
while (shouldRetryImmediatley) {
|
||||
|
|
@ -126,11 +128,13 @@ public class ProcessBulkUsersImportWorker implements Runnable {
|
|||
|
||||
while (true){
|
||||
try {
|
||||
baseTenantStorage.deleteBulkImportUsers(appIdentifier, toDelete);
|
||||
List<String> deletedIds = baseTenantStorage.deleteBulkImportUsers(appIdentifier,
|
||||
toDelete);
|
||||
break;
|
||||
} catch (Exception e) {
|
||||
// ignore and retry delete. The import transaction is already committed, the delete should happen no matter what
|
||||
Logging.debug(main, app.getAsPublicTenantIdentifier(), "Exception while deleting bulk import users: " + e.getMessage());
|
||||
Logging.debug(main, app.getAsPublicTenantIdentifier(),
|
||||
"Exception while deleting bulk import users: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
} catch (StorageTransactionLogicException | StorageQueryException e) {
|
||||
|
|
@ -148,9 +152,15 @@ public class ProcessBulkUsersImportWorker implements Runnable {
|
|||
}
|
||||
}
|
||||
} catch (StorageTransactionLogicException | InvalidConfigException e) {
|
||||
Logging.error(main, app.getAsPublicTenantIdentifier(),
|
||||
"Error while processing bulk import users: " + e.getMessage(), true, e);
|
||||
throw new RuntimeException(e);
|
||||
} catch (BulkImportBatchInsertException insertException) {
|
||||
handleProcessUserExceptions(app, users, insertException, baseTenantStorage);
|
||||
} catch (Exception e) {
|
||||
Logging.error(main, app.getAsPublicTenantIdentifier(),
|
||||
"Error while processing bulk import users: " + e.getMessage(), true, e);
|
||||
throw e;
|
||||
} finally {
|
||||
closeAllProxyStorages(); //closing it here to reuse the existing connection with all the users
|
||||
}
|
||||
|
|
@ -173,30 +183,40 @@ public class ProcessBulkUsersImportWorker implements Runnable {
|
|||
String[] errorMessage = { e.getMessage() };
|
||||
Map<String, String> bulkImportUserIdToErrorMessage = new HashMap<>();
|
||||
|
||||
if (e instanceof StorageTransactionLogicException) {
|
||||
StorageTransactionLogicException exception = (StorageTransactionLogicException) e;
|
||||
// If the exception is due to a StorageQueryException, we want to retry the entry after sometime instead
|
||||
// of marking it as FAILED. We will return early in that case.
|
||||
if (exception.actualException instanceof StorageQueryException) {
|
||||
Logging.error(main, null, "We got an StorageQueryException while processing a bulk import user entry. It will be retried again. Error Message: " + e.getMessage(), true);
|
||||
return;
|
||||
}
|
||||
if(exception.actualException instanceof BulkImportBatchInsertException){
|
||||
handleBulkImportException(usersBatch, (BulkImportBatchInsertException) exception.actualException, bulkImportUserIdToErrorMessage);
|
||||
} else {
|
||||
//fail the whole batch
|
||||
errorMessage[0] = exception.actualException.getMessage();
|
||||
for(BulkImportUser user : usersBatch){
|
||||
bulkImportUserIdToErrorMessage.put(user.id, errorMessage[0]);
|
||||
switch (e) {
|
||||
case StorageTransactionLogicException exception -> {
|
||||
// If the exception is due to a StorageQueryException, we want to retry the entry after sometime instead
|
||||
// of marking it as FAILED. We will return early in that case.
|
||||
if (exception.actualException instanceof StorageQueryException) {
|
||||
Logging.error(main, null,
|
||||
"We got an StorageQueryException while processing a bulk import user entry. It will be " +
|
||||
"retried again. Error Message: " +
|
||||
e.getMessage(), true);
|
||||
return;
|
||||
}
|
||||
if (exception.actualException instanceof BulkImportBatchInsertException) {
|
||||
handleBulkImportException(usersBatch, (BulkImportBatchInsertException) exception.actualException,
|
||||
bulkImportUserIdToErrorMessage);
|
||||
} else {
|
||||
//fail the whole batch
|
||||
errorMessage[0] = exception.actualException.getMessage();
|
||||
for (BulkImportUser user : usersBatch) {
|
||||
bulkImportUserIdToErrorMessage.put(user.id, errorMessage[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} else if (e instanceof InvalidBulkImportDataException) {
|
||||
errorMessage[0] = ((InvalidBulkImportDataException) e).errors.toString();
|
||||
} else if (e instanceof InvalidConfigException) {
|
||||
errorMessage[0] = e.getMessage();
|
||||
} else if (e instanceof BulkImportBatchInsertException) {
|
||||
handleBulkImportException(usersBatch, (BulkImportBatchInsertException) e, bulkImportUserIdToErrorMessage);
|
||||
case InvalidBulkImportDataException invalidBulkImportDataException ->
|
||||
errorMessage[0] = invalidBulkImportDataException.errors.toString();
|
||||
case InvalidConfigException invalidConfigException -> errorMessage[0] = e.getMessage();
|
||||
case BulkImportBatchInsertException bulkImportBatchInsertException ->
|
||||
handleBulkImportException(usersBatch, bulkImportBatchInsertException,
|
||||
bulkImportUserIdToErrorMessage);
|
||||
default -> {
|
||||
Logging.error(main, null,
|
||||
"We got an error while processing a bulk import user entry. It will be " +
|
||||
"retried again. Error Message: " +
|
||||
e.getMessage(), true);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
|
|
@ -294,7 +314,7 @@ public class ProcessBulkUsersImportWorker implements Runnable {
|
|||
Map<SQLStorage, List<BulkImportUser>> result = new HashMap<>();
|
||||
for(BulkImportUser user: users) {
|
||||
TenantIdentifier firstTenantIdentifier = new TenantIdentifier(appIdentifier.getConnectionUriDomain(),
|
||||
appIdentifier.getAppId(), user.loginMethods.get(0).tenantIds.get(0));
|
||||
appIdentifier.getAppId(), user.loginMethods.getFirst().tenantIds.getFirst());
|
||||
|
||||
SQLStorage bulkImportProxyStorage = (SQLStorage) getBulkImportProxyStorage(firstTenantIdentifier);
|
||||
if(!result.containsKey(bulkImportProxyStorage)){
|
||||
|
|
|
|||
|
|
@ -0,0 +1,84 @@
|
|||
/*
|
||||
* Copyright (c) 2025, VRAI Labs and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* This software is licensed under the Apache License, Version 2.0 (the
|
||||
* "License") as published by the Apache Software Foundation.
|
||||
*
|
||||
* You may not use this file except in compliance with the License. You may
|
||||
* obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package io.supertokens.cronjobs.deadlocklogger;
|
||||
|
||||
import java.lang.management.ManagementFactory;
|
||||
import java.lang.management.ThreadInfo;
|
||||
import java.lang.management.ThreadMXBean;
|
||||
import java.util.Arrays;
|
||||
|
||||
public class DeadlockLogger {
|
||||
|
||||
private static final DeadlockLogger INSTANCE = new DeadlockLogger();
|
||||
|
||||
private DeadlockLogger() {
|
||||
}
|
||||
|
||||
public static DeadlockLogger getInstance() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
public void start(){
|
||||
Thread deadlockLoggerThread = new Thread(deadlockDetector, "DeadlockLoggerThread");
|
||||
deadlockLoggerThread.setDaemon(true);
|
||||
deadlockLoggerThread.start();
|
||||
}
|
||||
|
||||
private final Runnable deadlockDetector = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
System.out.println("DeadlockLogger started!");
|
||||
while (true) {
|
||||
System.out.println("DeadlockLogger - checking");
|
||||
ThreadMXBean bean = ManagementFactory.getThreadMXBean();
|
||||
long[] threadIds = bean.findDeadlockedThreads(); // Returns null if no threads are deadlocked.
|
||||
System.out.println("DeadlockLogger - DeadlockedThreads: " + Arrays.toString(threadIds));
|
||||
if (threadIds != null) {
|
||||
ThreadInfo[] infos = bean.getThreadInfo(threadIds);
|
||||
boolean deadlockFound = false;
|
||||
System.out.println("DEADLOCK found!");
|
||||
for (ThreadInfo info : infos) {
|
||||
System.out.println("ThreadName: " + info.getThreadName());
|
||||
System.out.println("Thread ID: " + info.getThreadId());
|
||||
System.out.println("LockName: " + info.getLockName());
|
||||
System.out.println("LockOwnerName: " + info.getLockOwnerName());
|
||||
System.out.println("LockedMonitors: " + Arrays.toString(info.getLockedMonitors()));
|
||||
System.out.println("LockInfo: " + info.getLockInfo());
|
||||
System.out.println("Stack: " + Arrays.toString(info.getStackTrace()));
|
||||
System.out.println();
|
||||
deadlockFound = true;
|
||||
}
|
||||
System.out.println("*******************************");
|
||||
if(deadlockFound) {
|
||||
System.out.println(" ==== ALL THREAD INFO ===");
|
||||
ThreadInfo[] allThreads = bean.dumpAllThreads(true, true, 100);
|
||||
for (ThreadInfo threadInfo : allThreads) {
|
||||
System.out.println("THREAD: " + threadInfo.getThreadName());
|
||||
System.out.println("StackTrace: " + Arrays.toString(threadInfo.getStackTrace()));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
try {
|
||||
Thread.sleep(10000);
|
||||
} catch (InterruptedException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
package io.supertokens.cronjobs.deleteExpiredSAMLData;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import io.supertokens.Main;
|
||||
import io.supertokens.cronjobs.CronTask;
|
||||
import io.supertokens.cronjobs.CronTaskTest;
|
||||
import io.supertokens.pluginInterface.Storage;
|
||||
import io.supertokens.pluginInterface.StorageUtils;
|
||||
import io.supertokens.pluginInterface.multitenancy.TenantIdentifier;
|
||||
import io.supertokens.pluginInterface.saml.SAMLStorage;
|
||||
|
||||
public class DeleteExpiredSAMLData extends CronTask {
|
||||
public static final String RESOURCE_KEY = "io.supertokens.cronjobs.deleteExpiredSAMLData" +
|
||||
".DeleteExpiredSAMLData";
|
||||
|
||||
private DeleteExpiredSAMLData(Main main, List<List<TenantIdentifier>> tenantsInfo) {
|
||||
super("DeleteExpiredSAMLData", main, tenantsInfo, false);
|
||||
}
|
||||
|
||||
public static DeleteExpiredSAMLData init(Main main, List<List<TenantIdentifier>> tenantsInfo) {
|
||||
return (DeleteExpiredSAMLData) main.getResourceDistributor()
|
||||
.setResource(new TenantIdentifier(null, null, null), RESOURCE_KEY,
|
||||
new DeleteExpiredSAMLData(main, tenantsInfo));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doTaskPerStorage(Storage storage) throws Exception {
|
||||
SAMLStorage samlStorage = StorageUtils.getSAMLStorage(storage);
|
||||
samlStorage.removeExpiredSAMLCodesAndRelayStates();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIntervalTimeSeconds() {
|
||||
if (Main.isTesting) {
|
||||
Integer interval = CronTaskTest.getInstance(main).getIntervalInSeconds(RESOURCE_KEY);
|
||||
if (interval != null) {
|
||||
return interval;
|
||||
}
|
||||
}
|
||||
// Every hour
|
||||
return 3600;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getInitialWaitTimeSeconds() {
|
||||
if (!Main.isTesting) {
|
||||
return getIntervalTimeSeconds();
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -7,6 +7,7 @@ import io.supertokens.cronjobs.CronTaskTest;
|
|||
import io.supertokens.output.Logging;
|
||||
import io.supertokens.pluginInterface.STORAGE_TYPE;
|
||||
import io.supertokens.pluginInterface.multitenancy.TenantIdentifier;
|
||||
import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException;
|
||||
import io.supertokens.pluginInterface.totp.sqlStorage.TOTPSQLStorage;
|
||||
import io.supertokens.storageLayer.StorageLayer;
|
||||
import org.jetbrains.annotations.TestOnly;
|
||||
|
|
@ -30,7 +31,11 @@ public class DeleteExpiredTotpTokens extends CronTask {
|
|||
|
||||
@TestOnly
|
||||
public static DeleteExpiredTotpTokens getInstance(Main main) {
|
||||
return (DeleteExpiredTotpTokens) main.getResourceDistributor().getResource(RESOURCE_KEY);
|
||||
try {
|
||||
return (DeleteExpiredTotpTokens) main.getResourceDistributor().getResource(TenantIdentifier.BASE_TENANT, RESOURCE_KEY);
|
||||
} catch (TenantOrAppNotFoundException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -19,12 +19,9 @@ package io.supertokens.cronjobs.syncCoreConfigWithDb;
|
|||
import io.supertokens.Main;
|
||||
import io.supertokens.cronjobs.CronTask;
|
||||
import io.supertokens.cronjobs.CronTaskTest;
|
||||
import io.supertokens.cronjobs.deleteExpiredSessions.DeleteExpiredSessions;
|
||||
import io.supertokens.multitenancy.Multitenancy;
|
||||
import io.supertokens.multitenancy.MultitenancyHelper;
|
||||
import io.supertokens.pluginInterface.multitenancy.TenantIdentifier;
|
||||
|
||||
import java.util.List;
|
||||
import io.supertokens.pluginInterface.opentelemetry.WithinOtelSpan;
|
||||
|
||||
public class SyncCoreConfigWithDb extends CronTask {
|
||||
|
||||
|
|
@ -62,6 +59,7 @@ public class SyncCoreConfigWithDb extends CronTask {
|
|||
return 60;
|
||||
}
|
||||
|
||||
@WithinOtelSpan
|
||||
@Override
|
||||
protected void doTaskForTargetTenant(TenantIdentifier targetTenant) throws Exception {
|
||||
MultitenancyHelper.getInstance(main).refreshTenantsInCoreBasedOnChangesInCoreConfigOrIfTenantListChanged(true);
|
||||
|
|
|
|||
|
|
@ -157,7 +157,7 @@ public class Telemetry extends CronTask {
|
|||
json.add("maus", new JsonArray());
|
||||
}
|
||||
|
||||
String url = "https://api.supertokens.io/0/st/telemetry";
|
||||
String url = "https://api.supertokens.com/0/st/telemetry";
|
||||
|
||||
// we call the API only if we are not testing the core, of if the request can be mocked (in case a test
|
||||
// wants
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
package io.supertokens.dashboard;
|
||||
|
||||
import io.supertokens.Main;
|
||||
import io.supertokens.ResourceDistributor;
|
||||
import io.supertokens.dashboard.exceptions.UserSuspendedException;
|
||||
import io.supertokens.emailpassword.PasswordHashing;
|
||||
import io.supertokens.featureflag.EE_FEATURES;
|
||||
|
|
@ -55,7 +56,7 @@ public class Dashboard {
|
|||
throws StorageQueryException, DuplicateEmailException, FeatureNotEnabledException {
|
||||
try {
|
||||
Storage storage = StorageLayer.getStorage(main);
|
||||
return signUpDashboardUser(new AppIdentifier(null, null), storage,
|
||||
return signUpDashboardUser(ResourceDistributor.getAppForTesting().toAppIdentifier(), storage,
|
||||
main, email, password);
|
||||
} catch (TenantOrAppNotFoundException e) {
|
||||
throw new IllegalStateException(e);
|
||||
|
|
@ -103,7 +104,7 @@ public class Dashboard {
|
|||
public static DashboardUser[] getAllDashboardUsers(Main main)
|
||||
throws StorageQueryException {
|
||||
Storage storage = StorageLayer.getStorage(main);
|
||||
return getAllDashboardUsers(new AppIdentifier(null, null), storage, main);
|
||||
return getAllDashboardUsers(ResourceDistributor.getAppForTesting().toAppIdentifier(), storage, main);
|
||||
}
|
||||
|
||||
public static DashboardUser[] getAllDashboardUsers(AppIdentifier appIdentifier, Storage storage, Main main)
|
||||
|
|
@ -127,7 +128,7 @@ public class Dashboard {
|
|||
throws StorageQueryException, UserSuspendedException {
|
||||
try {
|
||||
Storage storage = StorageLayer.getStorage(main);
|
||||
return signInDashboardUser(new AppIdentifier(null, null), storage,
|
||||
return signInDashboardUser(ResourceDistributor.getAppForTesting().toAppIdentifier(), storage,
|
||||
main, email, password);
|
||||
} catch (TenantOrAppNotFoundException e) {
|
||||
throw new IllegalStateException(e);
|
||||
|
|
@ -159,7 +160,7 @@ public class Dashboard {
|
|||
public static boolean deleteUserWithUserId(Main main, String userId)
|
||||
throws StorageQueryException {
|
||||
Storage storage = StorageLayer.getStorage(main);
|
||||
return deleteUserWithUserId(new AppIdentifier(null, null), storage, userId);
|
||||
return deleteUserWithUserId(ResourceDistributor.getAppForTesting().toAppIdentifier(), storage, userId);
|
||||
}
|
||||
|
||||
public static boolean deleteUserWithUserId(AppIdentifier appIdentifier, Storage storage, String userId)
|
||||
|
|
@ -201,7 +202,7 @@ public class Dashboard {
|
|||
public static boolean deleteUserWithEmail(Main main, String email)
|
||||
throws StorageQueryException {
|
||||
Storage storage = StorageLayer.getStorage(main);
|
||||
return deleteUserWithEmail(new AppIdentifier(null, null), storage, email);
|
||||
return deleteUserWithEmail(ResourceDistributor.getAppForTesting().toAppIdentifier(), storage, email);
|
||||
}
|
||||
|
||||
public static boolean deleteUserWithEmail(AppIdentifier appIdentifier, Storage storage, String email)
|
||||
|
|
@ -223,7 +224,7 @@ public class Dashboard {
|
|||
try {
|
||||
Storage storage = StorageLayer.getStorage(main);
|
||||
return updateUsersCredentialsWithUserId(
|
||||
new AppIdentifier(null, null), storage, main, userId,
|
||||
ResourceDistributor.getAppForTesting().toAppIdentifier(), storage, main, userId,
|
||||
newEmail, newPassword);
|
||||
} catch (TenantOrAppNotFoundException e) {
|
||||
throw new IllegalStateException(e);
|
||||
|
|
@ -291,7 +292,7 @@ public class Dashboard {
|
|||
public static DashboardUser getDashboardUserByEmail(Main main, String email)
|
||||
throws StorageQueryException {
|
||||
Storage storage = StorageLayer.getStorage(main);
|
||||
return getDashboardUserByEmail(new AppIdentifier(null, null), storage, email);
|
||||
return getDashboardUserByEmail(ResourceDistributor.getAppForTesting().toAppIdentifier(), storage, email);
|
||||
}
|
||||
|
||||
public static DashboardUser getDashboardUserByEmail(AppIdentifier appIdentifier, Storage storage, String email)
|
||||
|
|
@ -305,7 +306,7 @@ public class Dashboard {
|
|||
public static boolean revokeSessionWithSessionId(Main main, String sessionId)
|
||||
throws StorageQueryException {
|
||||
Storage storage = StorageLayer.getStorage(main);
|
||||
return revokeSessionWithSessionId(new AppIdentifier(null, null), storage, sessionId);
|
||||
return revokeSessionWithSessionId(ResourceDistributor.getAppForTesting().toAppIdentifier(), storage, sessionId);
|
||||
}
|
||||
|
||||
public static boolean revokeSessionWithSessionId(AppIdentifier appIdentifier, Storage storage, String sessionId)
|
||||
|
|
@ -320,7 +321,7 @@ public class Dashboard {
|
|||
throws StorageQueryException {
|
||||
Storage storage = StorageLayer.getStorage(main);
|
||||
return getAllDashboardSessionsForUser(
|
||||
new AppIdentifier(null, null), storage, userId);
|
||||
ResourceDistributor.getAppForTesting().toAppIdentifier(), storage, userId);
|
||||
}
|
||||
|
||||
public static DashboardSessionInfo[] getAllDashboardSessionsForUser(AppIdentifier appIdentifier, Storage storage,
|
||||
|
|
@ -390,7 +391,7 @@ public class Dashboard {
|
|||
public static boolean isValidUserSession(Main main, String sessionId)
|
||||
throws StorageQueryException, UserSuspendedException {
|
||||
Storage storage = StorageLayer.getStorage(main);
|
||||
return isValidUserSession(new AppIdentifier(null, null), storage, main, sessionId);
|
||||
return isValidUserSession(ResourceDistributor.getAppForTesting().toAppIdentifier(), storage, main, sessionId);
|
||||
}
|
||||
|
||||
public static boolean isValidUserSession(AppIdentifier appIdentifier, Storage storage, Main main, String sessionId)
|
||||
|
|
|
|||
|
|
@ -16,7 +16,18 @@
|
|||
|
||||
package io.supertokens.emailpassword;
|
||||
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
import java.util.List;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import org.jetbrains.annotations.TestOnly;
|
||||
|
||||
import io.supertokens.Main;
|
||||
import io.supertokens.ResourceDistributor;
|
||||
import io.supertokens.authRecipe.AuthRecipe;
|
||||
import io.supertokens.config.Config;
|
||||
import io.supertokens.config.CoreConfig;
|
||||
|
|
@ -50,14 +61,6 @@ import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoun
|
|||
import io.supertokens.storageLayer.StorageLayer;
|
||||
import io.supertokens.utils.Utils;
|
||||
import io.supertokens.webserver.WebserverAPI;
|
||||
import org.jetbrains.annotations.TestOnly;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
import java.util.List;
|
||||
|
||||
public class EmailPassword {
|
||||
|
||||
|
|
@ -74,7 +77,7 @@ public class EmailPassword {
|
|||
@TestOnly
|
||||
public static long getPasswordResetTokenLifetimeForTests(Main main) {
|
||||
try {
|
||||
return getPasswordResetTokenLifetime(new TenantIdentifier(null, null, null), main);
|
||||
return getPasswordResetTokenLifetime(ResourceDistributor.getAppForTesting(), main);
|
||||
} catch (TenantOrAppNotFoundException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
|
|
@ -90,7 +93,7 @@ public class EmailPassword {
|
|||
throws DuplicateEmailException, StorageQueryException {
|
||||
try {
|
||||
Storage storage = StorageLayer.getStorage(main);
|
||||
return signUp(new TenantIdentifier(null, null, null), storage,
|
||||
return signUp(ResourceDistributor.getAppForTesting(), storage,
|
||||
main, email, password);
|
||||
} catch (TenantOrAppNotFoundException | BadPermissionException e) {
|
||||
throw new IllegalStateException(e);
|
||||
|
|
@ -157,7 +160,7 @@ public class EmailPassword {
|
|||
Storage storage = StorageLayer.getStorage(main);
|
||||
|
||||
return importUserWithPasswordHash(
|
||||
new TenantIdentifier(null, null, null), storage, main, email,
|
||||
ResourceDistributor.getAppForTesting(), storage, main, email,
|
||||
passwordHash, hashingAlgorithm);
|
||||
} catch (TenantOrAppNotFoundException | BadPermissionException e) {
|
||||
throw new IllegalStateException(e);
|
||||
|
|
@ -215,7 +218,7 @@ public class EmailPassword {
|
|||
|
||||
public static ImportUserResponse createUserWithPasswordHash(TenantIdentifier tenantIdentifier, Storage storage,
|
||||
@Nonnull String email,
|
||||
@Nonnull String passwordHash, @Nullable long timeJoined)
|
||||
@Nonnull String passwordHash, long timeJoined)
|
||||
throws StorageQueryException, DuplicateEmailException, TenantOrAppNotFoundException,
|
||||
StorageTransactionLogicException {
|
||||
EmailPasswordSQLStorage epStorage = StorageUtils.getEmailPasswordStorage(storage);
|
||||
|
|
@ -276,7 +279,7 @@ public class EmailPassword {
|
|||
try {
|
||||
Storage storage = StorageLayer.getStorage(main);
|
||||
return importUserWithPasswordHash(
|
||||
new TenantIdentifier(null, null, null), storage,
|
||||
ResourceDistributor.getAppForTesting(), storage,
|
||||
main, email, passwordHash, null);
|
||||
} catch (TenantOrAppNotFoundException | BadPermissionException e) {
|
||||
throw new IllegalStateException(e);
|
||||
|
|
@ -289,7 +292,7 @@ public class EmailPassword {
|
|||
throws StorageQueryException, WrongCredentialsException {
|
||||
try {
|
||||
Storage storage = StorageLayer.getStorage(main);
|
||||
return signIn(new TenantIdentifier(null, null, null), storage,
|
||||
return signIn(ResourceDistributor.getAppForTesting(), storage,
|
||||
main, email, password);
|
||||
} catch (TenantOrAppNotFoundException | BadPermissionException e) {
|
||||
throw new IllegalStateException(e);
|
||||
|
|
@ -353,7 +356,7 @@ public class EmailPassword {
|
|||
try {
|
||||
Storage storage = StorageLayer.getStorage(main);
|
||||
return generatePasswordResetTokenBeforeCdi4_0(
|
||||
new TenantIdentifier(null, null, null), storage,
|
||||
ResourceDistributor.getAppForTesting(), storage,
|
||||
main, userId);
|
||||
} catch (TenantOrAppNotFoundException | BadPermissionException | WebserverAPI.BadRequestException e) {
|
||||
throw new IllegalStateException(e);
|
||||
|
|
@ -366,7 +369,7 @@ public class EmailPassword {
|
|||
try {
|
||||
Storage storage = StorageLayer.getStorage(main);
|
||||
return generatePasswordResetToken(
|
||||
new TenantIdentifier(null, null, null), storage,
|
||||
ResourceDistributor.getAppForTesting(), storage,
|
||||
main, userId, null);
|
||||
} catch (TenantOrAppNotFoundException | BadPermissionException e) {
|
||||
throw new IllegalStateException(e);
|
||||
|
|
@ -379,7 +382,7 @@ public class EmailPassword {
|
|||
try {
|
||||
Storage storage = StorageLayer.getStorage(main);
|
||||
return generatePasswordResetToken(
|
||||
new TenantIdentifier(null, null, null), storage,
|
||||
ResourceDistributor.getAppForTesting(), storage,
|
||||
main, userId, email);
|
||||
} catch (TenantOrAppNotFoundException | BadPermissionException e) {
|
||||
throw new IllegalStateException(e);
|
||||
|
|
@ -456,7 +459,7 @@ public class EmailPassword {
|
|||
StorageTransactionLogicException {
|
||||
try {
|
||||
Storage storage = StorageLayer.getStorage(main);
|
||||
return resetPassword(new TenantIdentifier(null, null, null), storage,
|
||||
return resetPassword(ResourceDistributor.getAppForTesting(), storage,
|
||||
main, token, password);
|
||||
} catch (TenantOrAppNotFoundException e) {
|
||||
throw new IllegalStateException(e);
|
||||
|
|
@ -530,7 +533,7 @@ public class EmailPassword {
|
|||
StorageTransactionLogicException {
|
||||
try {
|
||||
Storage storage = StorageLayer.getStorage(main);
|
||||
return consumeResetPasswordToken(new TenantIdentifier(null, null, null), storage,
|
||||
return consumeResetPasswordToken(ResourceDistributor.getAppForTesting(), storage,
|
||||
token);
|
||||
} catch (TenantOrAppNotFoundException e) {
|
||||
throw new IllegalStateException(e);
|
||||
|
|
@ -628,7 +631,7 @@ public class EmailPassword {
|
|||
UnknownUserIdException, DuplicateEmailException, EmailChangeNotAllowedException {
|
||||
try {
|
||||
Storage storage = StorageLayer.getStorage(main);
|
||||
updateUsersEmailOrPassword(new AppIdentifier(null, null), storage,
|
||||
updateUsersEmailOrPassword(ResourceDistributor.getAppForTesting().toAppIdentifier(), storage,
|
||||
main, userId, email, password);
|
||||
} catch (TenantOrAppNotFoundException e) {
|
||||
throw new IllegalStateException(e);
|
||||
|
|
@ -725,7 +728,7 @@ public class EmailPassword {
|
|||
throws StorageQueryException {
|
||||
try {
|
||||
Storage storage = StorageLayer.getStorage(main);
|
||||
return getUserUsingId(new AppIdentifier(null, null), storage, userId);
|
||||
return getUserUsingId(ResourceDistributor.getAppForTesting().toAppIdentifier(), storage, userId);
|
||||
} catch (TenantOrAppNotFoundException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,6 +30,8 @@ import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoun
|
|||
import org.jetbrains.annotations.TestOnly;
|
||||
import org.mindrot.jbcrypt.BCrypt;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
|
||||
|
|
@ -42,6 +44,9 @@ public class PasswordHashing extends ResourceDistributor.SingletonResource {
|
|||
final BlockingQueue<Object> firebaseSCryptBoundedQueue;
|
||||
final Main main;
|
||||
|
||||
private final Map<String, String> cachedPasswordHashForTesting = new HashMap<>();
|
||||
public static boolean bypassHashCachingInTesting = false;
|
||||
|
||||
private PasswordHashing(Main main) {
|
||||
this.argon2BoundedQueue = new LinkedBlockingQueue<>(
|
||||
Config.getBaseConfig(main).getArgon2HashingPoolSize());
|
||||
|
|
@ -75,7 +80,7 @@ public class PasswordHashing extends ResourceDistributor.SingletonResource {
|
|||
@TestOnly
|
||||
public String createHashWithSalt(String password) {
|
||||
try {
|
||||
return createHashWithSalt(new AppIdentifier(null, null), password);
|
||||
return createHashWithSalt(ResourceDistributor.getAppForTesting().toAppIdentifier(), password);
|
||||
} catch (TenantOrAppNotFoundException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
|
|
@ -84,6 +89,10 @@ public class PasswordHashing extends ResourceDistributor.SingletonResource {
|
|||
public String createHashWithSalt(AppIdentifier appIdentifier, String password)
|
||||
throws TenantOrAppNotFoundException {
|
||||
|
||||
if (Main.isTesting && !bypassHashCachingInTesting && cachedPasswordHashForTesting.containsKey(password)) {
|
||||
return cachedPasswordHashForTesting.get(password);
|
||||
}
|
||||
|
||||
String passwordHash = "";
|
||||
|
||||
TenantIdentifier tenantIdentifier = appIdentifier.getAsPublicTenantIdentifier();
|
||||
|
|
@ -108,6 +117,10 @@ public class PasswordHashing extends ResourceDistributor.SingletonResource {
|
|||
} catch (UnsupportedPasswordHashingFormatException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
|
||||
if (Main.isTesting) {
|
||||
cachedPasswordHashForTesting.put(password, passwordHash);
|
||||
}
|
||||
return passwordHash;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ import io.supertokens.config.CoreConfig;
|
|||
import io.supertokens.emailpassword.exceptions.UnsupportedPasswordHashingFormatException;
|
||||
import io.supertokens.pluginInterface.multitenancy.AppIdentifier;
|
||||
import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException;
|
||||
import org.apache.tomcat.util.codec.binary.Base64;
|
||||
import org.apache.commons.codec.binary.Base64;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.crypto.Cipher;
|
||||
|
|
@ -118,9 +118,9 @@ public class PasswordHashingUtils {
|
|||
|
||||
// concatenating decoded salt + separator
|
||||
byte[] byteArrTemp = response.salt.getBytes(StandardCharsets.US_ASCII);
|
||||
byte[] decodedSaltBytes = Base64.decodeBase64(byteArrTemp, 0, byteArrTemp.length);
|
||||
byte[] decodedSaltBytes = Base64.decodeBase64(byteArrTemp);
|
||||
byteArrTemp = response.saltSeparator.getBytes(StandardCharsets.US_ASCII);
|
||||
byte[] decodedSaltSepBytes = Base64.decodeBase64(byteArrTemp, 0, byteArrTemp.length);
|
||||
byte[] decodedSaltSepBytes = Base64.decodeBase64(byteArrTemp);
|
||||
|
||||
byte[] saltConcat = new byte[decodedSaltBytes.length + decodedSaltSepBytes.length];
|
||||
System.arraycopy(decodedSaltBytes, 0, saltConcat, 0, decodedSaltBytes.length);
|
||||
|
|
@ -136,7 +136,7 @@ public class PasswordHashingUtils {
|
|||
}
|
||||
// encrypting with aes
|
||||
byteArrTemp = base64_signer_key.getBytes(StandardCharsets.US_ASCII);
|
||||
byte[] signerBytes = Base64.decodeBase64(byteArrTemp, 0, byteArrTemp.length);
|
||||
byte[] signerBytes = Base64.decodeBase64(byteArrTemp);
|
||||
|
||||
try {
|
||||
String CIPHER = "AES/CTR/NoPadding";
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
package io.supertokens.emailverification;
|
||||
|
||||
import io.supertokens.Main;
|
||||
import io.supertokens.ResourceDistributor;
|
||||
import io.supertokens.config.Config;
|
||||
import io.supertokens.emailverification.exception.EmailAlreadyVerifiedException;
|
||||
import io.supertokens.emailverification.exception.EmailVerificationInvalidTokenException;
|
||||
|
|
@ -44,7 +45,7 @@ public class EmailVerification {
|
|||
public static long getEmailVerificationTokenLifetimeForTests(Main main) {
|
||||
try {
|
||||
return getEmailVerificationTokenLifetime(
|
||||
new TenantIdentifier(null, null, null), main);
|
||||
ResourceDistributor.getAppForTesting(), main);
|
||||
} catch (TenantOrAppNotFoundException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
|
|
@ -62,7 +63,7 @@ public class EmailVerification {
|
|||
try {
|
||||
Storage storage = StorageLayer.getStorage(main);
|
||||
return generateEmailVerificationToken(
|
||||
new TenantIdentifier(null, null, null), storage,
|
||||
ResourceDistributor.getAppForTesting(), storage,
|
||||
main, userId, email);
|
||||
} catch (TenantOrAppNotFoundException e) {
|
||||
throw new IllegalStateException(e);
|
||||
|
|
@ -107,7 +108,7 @@ public class EmailVerification {
|
|||
EmailVerificationInvalidTokenException, NoSuchAlgorithmException, StorageTransactionLogicException {
|
||||
try {
|
||||
Storage storage = StorageLayer.getStorage(main);
|
||||
return verifyEmail(new TenantIdentifier(null, null, null), storage, token);
|
||||
return verifyEmail(ResourceDistributor.getAppForTesting(), storage, token);
|
||||
} catch (TenantOrAppNotFoundException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
|
|
@ -182,7 +183,7 @@ public class EmailVerification {
|
|||
public static boolean isEmailVerified(Main main, String userId,
|
||||
String email) throws StorageQueryException {
|
||||
Storage storage = StorageLayer.getStorage(main);
|
||||
return isEmailVerified(new AppIdentifier(null, null), storage,
|
||||
return isEmailVerified(ResourceDistributor.getAppForTesting().toAppIdentifier(), storage,
|
||||
userId, email);
|
||||
}
|
||||
|
||||
|
|
@ -196,7 +197,7 @@ public class EmailVerification {
|
|||
public static void revokeAllTokens(Main main, String userId,
|
||||
String email) throws StorageQueryException {
|
||||
Storage storage = StorageLayer.getStorage(main);
|
||||
revokeAllTokens(new TenantIdentifier(null, null, null), storage,
|
||||
revokeAllTokens(ResourceDistributor.getAppForTesting(), storage,
|
||||
userId, email);
|
||||
}
|
||||
|
||||
|
|
@ -211,7 +212,7 @@ public class EmailVerification {
|
|||
String email) throws StorageQueryException {
|
||||
try {
|
||||
Storage storage = StorageLayer.getStorage(main);
|
||||
unverifyEmail(new AppIdentifier(null, null), storage, userId, email);
|
||||
unverifyEmail(ResourceDistributor.getAppForTesting().toAppIdentifier(), storage, userId, email);
|
||||
} catch (TenantOrAppNotFoundException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
|
|
@ -249,7 +250,7 @@ public class EmailVerification {
|
|||
|
||||
try {
|
||||
StorageUtils.getEmailVerificationStorage(StorageLayer.getStorage(main))
|
||||
.addEmailVerificationToken(new TenantIdentifier(null, null, null),
|
||||
.addEmailVerificationToken(ResourceDistributor.getAppForTesting(),
|
||||
new EmailVerificationTokenInfo(userId, hashedToken,
|
||||
System.currentTimeMillis() +
|
||||
EmailVerification.getEmailVerificationTokenLifetimeForTests(main), email));
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ package io.supertokens.featureflag;
|
|||
|
||||
public enum EE_FEATURES {
|
||||
ACCOUNT_LINKING("account_linking"), MULTI_TENANCY("multi_tenancy"), TEST("test"),
|
||||
DASHBOARD_LOGIN("dashboard_login"), MFA("mfa"), SECURITY("security"), OAUTH("oauth");
|
||||
DASHBOARD_LOGIN("dashboard_login"), MFA("mfa"), SECURITY("security"), OAUTH("oauth"), SAML("saml");
|
||||
|
||||
private final String name;
|
||||
|
||||
|
|
|
|||
|
|
@ -108,7 +108,7 @@ public class FeatureFlag extends ResourceDistributor.SingletonResource {
|
|||
public static FeatureFlag getInstance(Main main) {
|
||||
try {
|
||||
return (FeatureFlag) main.getResourceDistributor()
|
||||
.getResource(new AppIdentifier(null, null), RESOURCE_KEY);
|
||||
.getResource(ResourceDistributor.getAppForTesting(), RESOURCE_KEY);
|
||||
} catch (TenantOrAppNotFoundException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -65,11 +65,16 @@ import io.supertokens.pluginInterface.oauth.OAuthLogoutChallenge;
|
|||
import io.supertokens.pluginInterface.oauth.OAuthStorage;
|
||||
import io.supertokens.pluginInterface.oauth.exception.DuplicateOAuthLogoutChallengeException;
|
||||
import io.supertokens.pluginInterface.oauth.exception.OAuthClientNotFoundException;
|
||||
import io.supertokens.pluginInterface.opentelemetry.OtelProvider;
|
||||
import io.supertokens.pluginInterface.passwordless.PasswordlessCode;
|
||||
import io.supertokens.pluginInterface.passwordless.PasswordlessDevice;
|
||||
import io.supertokens.pluginInterface.passwordless.PasswordlessImportUser;
|
||||
import io.supertokens.pluginInterface.passwordless.exception.*;
|
||||
import io.supertokens.pluginInterface.passwordless.sqlStorage.PasswordlessSQLStorage;
|
||||
import io.supertokens.pluginInterface.saml.SAMLClaimsInfo;
|
||||
import io.supertokens.pluginInterface.saml.SAMLClient;
|
||||
import io.supertokens.pluginInterface.saml.SAMLRelayStateInfo;
|
||||
import io.supertokens.pluginInterface.saml.SAMLStorage;
|
||||
import io.supertokens.pluginInterface.session.SessionInfo;
|
||||
import io.supertokens.pluginInterface.session.SessionStorage;
|
||||
import io.supertokens.pluginInterface.session.sqlStorage.SessionSQLStorage;
|
||||
|
|
@ -116,7 +121,8 @@ public class Start
|
|||
implements SessionSQLStorage, EmailPasswordSQLStorage, EmailVerificationSQLStorage, ThirdPartySQLStorage,
|
||||
JWTRecipeSQLStorage, PasswordlessSQLStorage, UserMetadataSQLStorage, UserRolesSQLStorage, UserIdMappingStorage,
|
||||
UserIdMappingSQLStorage, MultitenancyStorage, MultitenancySQLStorage, TOTPSQLStorage, ActiveUsersStorage,
|
||||
ActiveUsersSQLStorage, DashboardSQLStorage, AuthRecipeSQLStorage, OAuthStorage, WebAuthNSQLStorage {
|
||||
ActiveUsersSQLStorage, DashboardSQLStorage, AuthRecipeSQLStorage, OAuthStorage, WebAuthNSQLStorage,
|
||||
SAMLStorage {
|
||||
|
||||
private static final Object appenderLock = new Object();
|
||||
private static final String ACCESS_TOKEN_SIGNING_KEY_NAME = "access_token_signing_key";
|
||||
|
|
@ -202,7 +208,7 @@ public class Start
|
|||
}
|
||||
|
||||
@Override
|
||||
public void initFileLogging(String infoLogPath, String errorLogPath) {
|
||||
public void initFileLogging(String infoLogPath, String errorLogPath, OtelProvider otelProvider) {
|
||||
// no op
|
||||
}
|
||||
|
||||
|
|
@ -227,7 +233,7 @@ public class Start
|
|||
@Override
|
||||
public <T> T startTransaction(TransactionLogic<T> logic)
|
||||
throws StorageTransactionLogicException, StorageQueryException {
|
||||
return startTransaction(logic, TransactionIsolationLevel.SERIALIZABLE);
|
||||
return startTransaction(logic, TransactionIsolationLevel.READ_COMMITTED);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -619,6 +625,11 @@ public class Start
|
|||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateConfigJsonFromEnv(JsonObject configJson) {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isUserIdBeingUsedInNonAuthRecipe(AppIdentifier appIdentifier, String className, String userId)
|
||||
throws StorageQueryException {
|
||||
|
|
@ -759,6 +770,8 @@ public class Start
|
|||
//ignore
|
||||
} else if (className.equals(OAuthStorage.class.getName())) {
|
||||
/* Since OAuth tables store client-related data, we don't add user-specific data here */
|
||||
} else if (className.equals(SAMLStorage.class.getName())) {
|
||||
// no user specific data here
|
||||
} else if (className.equals(ActiveUsersStorage.class.getName())) {
|
||||
try {
|
||||
ActiveUsersQueries.updateUserLastActive(this, tenantIdentifier.toAppIdentifier(), userId);
|
||||
|
|
@ -1016,7 +1029,8 @@ public class Start
|
|||
|
||||
@Override
|
||||
public void updateMultipleIsEmailVerified_Transaction(AppIdentifier appIdentifier, TransactionConnection con,
|
||||
Map<String, String> emailToUserId, boolean isEmailVerified)
|
||||
Map<String, String> emailToUserId,
|
||||
boolean isEmailVerified)
|
||||
throws StorageQueryException, TenantOrAppNotFoundException {
|
||||
Connection sqlCon = (Connection) con.getConnection();
|
||||
try {
|
||||
|
|
@ -2748,7 +2762,7 @@ public class Start
|
|||
try {
|
||||
startTransaction(con -> {
|
||||
try {
|
||||
createDevice_Transaction(con, new AppIdentifier(null, null), device);
|
||||
createDevice_Transaction(con, appIdentifier, device);
|
||||
} catch (DeviceAlreadyExistsException | TenantOrAppNotFoundException e) {
|
||||
throw new StorageTransactionLogicException(e);
|
||||
}
|
||||
|
|
@ -3889,4 +3903,72 @@ public class Start
|
|||
throw new StorageQueryException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public SAMLClient createOrUpdateSAMLClient(TenantIdentifier tenantIdentifier, SAMLClient samlClient)
|
||||
throws StorageQueryException, io.supertokens.pluginInterface.saml.exception.DuplicateEntityIdException {
|
||||
try {
|
||||
return SAMLQueries.createOrUpdateSAMLClient(this, tenantIdentifier, samlClient.clientId, samlClient.clientSecret,
|
||||
samlClient.ssoLoginURL, samlClient.redirectURIs.toString(), samlClient.defaultRedirectURI,
|
||||
samlClient.idpEntityId, samlClient.idpSigningCertificate, samlClient.allowIDPInitiatedLogin,
|
||||
samlClient.enableRequestSigning);
|
||||
} catch (SQLException e) {
|
||||
String errorMessage = e.getMessage();
|
||||
String table = io.supertokens.inmemorydb.config.Config.getConfig(this).getSAMLClientsTable();
|
||||
if (isUniqueConstraintError(errorMessage, table, new String[]{"app_id", "tenant_id", "idp_entity_id"})) {
|
||||
throw new io.supertokens.pluginInterface.saml.exception.DuplicateEntityIdException();
|
||||
}
|
||||
throw new StorageQueryException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeSAMLClient(TenantIdentifier tenantIdentifier, String clientId) throws StorageQueryException {
|
||||
return SAMLQueries.removeSAMLClient(this, tenantIdentifier, clientId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SAMLClient getSAMLClient(TenantIdentifier tenantIdentifier, String clientId) throws StorageQueryException {
|
||||
return SAMLQueries.getSAMLClient(this, tenantIdentifier, clientId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SAMLClient getSAMLClientByIDPEntityId(TenantIdentifier tenantIdentifier, String idpEntityId) throws StorageQueryException {
|
||||
return SAMLQueries.getSAMLClientByIDPEntityId(this, tenantIdentifier, idpEntityId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<SAMLClient> getSAMLClients(TenantIdentifier tenantIdentifier) throws StorageQueryException {
|
||||
return SAMLQueries.getSAMLClients(this, tenantIdentifier);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveRelayStateInfo(TenantIdentifier tenantIdentifier, SAMLRelayStateInfo relayStateInfo, long relayStateValidity) throws StorageQueryException {
|
||||
SAMLQueries.saveRelayStateInfo(this, tenantIdentifier, relayStateInfo.relayState, relayStateInfo.clientId, relayStateInfo.state, relayStateInfo.redirectURI, relayStateValidity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SAMLRelayStateInfo getRelayStateInfo(TenantIdentifier tenantIdentifier, String relayState) throws StorageQueryException {
|
||||
return SAMLQueries.getRelayStateInfo(this, tenantIdentifier, relayState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveSAMLClaims(TenantIdentifier tenantIdentifier, String clientId, String code, JsonObject claims, long claimsValidity) throws StorageQueryException {
|
||||
SAMLQueries.saveSAMLClaims(this, tenantIdentifier, clientId, code, claims.toString(), claimsValidity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SAMLClaimsInfo getSAMLClaimsAndRemoveCode(TenantIdentifier tenantIdentifier, String code) throws StorageQueryException {
|
||||
return SAMLQueries.getSAMLClaimsAndRemoveCode(this, tenantIdentifier, code);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeExpiredSAMLCodesAndRelayStates() throws StorageQueryException {
|
||||
SAMLQueries.removeExpiredSAMLCodesAndRelayStates(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int countSAMLClients(TenantIdentifier tenantIdentifier) throws StorageQueryException {
|
||||
return SAMLQueries.countSAMLClients(this, tenantIdentifier);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -194,4 +194,10 @@ public class SQLiteConfig {
|
|||
public String getWebAuthNCredentialsTable() { return "webauthn_credentials"; }
|
||||
|
||||
public String getWebAuthNAccountRecoveryTokenTable() { return "webauthn_account_recovery_tokens"; }
|
||||
|
||||
public String getSAMLClientsTable() { return "saml_clients"; }
|
||||
|
||||
public String getSAMLRelayStateTable() { return "saml_relay_state"; }
|
||||
|
||||
public String getSAMLClaimsTable() { return "saml_claims"; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -516,6 +516,33 @@ public class GeneralQueries {
|
|||
//index
|
||||
update(start, WebAuthNQueries.getQueryToCreateWebAuthNCredentialsUserIdIndex(start), NO_OP_SETTER);
|
||||
}
|
||||
|
||||
// SAML tables
|
||||
if (!doesTableExists(start, Config.getConfig(start).getSAMLClientsTable())) {
|
||||
getInstance(main).addState(CREATING_NEW_TABLE, null);
|
||||
update(start, SAMLQueries.getQueryToCreateSAMLClientsTable(start), NO_OP_SETTER);
|
||||
|
||||
// indexes
|
||||
update(start, SAMLQueries.getQueryToCreateSAMLClientsAppIdTenantIdIndex(start), NO_OP_SETTER);
|
||||
}
|
||||
|
||||
if (!doesTableExists(start, Config.getConfig(start).getSAMLRelayStateTable())) {
|
||||
getInstance(main).addState(CREATING_NEW_TABLE, null);
|
||||
update(start, SAMLQueries.getQueryToCreateSAMLRelayStateTable(start), NO_OP_SETTER);
|
||||
|
||||
// indexes
|
||||
update(start, SAMLQueries.getQueryToCreateSAMLRelayStateAppIdTenantIdIndex(start), NO_OP_SETTER);
|
||||
update(start, SAMLQueries.getQueryToCreateSAMLRelayStateExpiresAtIndex(start), NO_OP_SETTER);
|
||||
}
|
||||
|
||||
if (!doesTableExists(start, Config.getConfig(start).getSAMLClaimsTable())) {
|
||||
getInstance(main).addState(CREATING_NEW_TABLE, null);
|
||||
update(start, SAMLQueries.getQueryToCreateSAMLClaimsTable(start), NO_OP_SETTER);
|
||||
|
||||
// indexes
|
||||
update(start, SAMLQueries.getQueryToCreateSAMLClaimsAppIdTenantIdIndex(start), NO_OP_SETTER);
|
||||
update(start, SAMLQueries.getQueryToCreateSAMLClaimsExpiresAtIndex(start), NO_OP_SETTER);
|
||||
}
|
||||
}
|
||||
|
||||
public static void setKeyValue_Transaction(Start start, Connection con, TenantIdentifier tenantIdentifier,
|
||||
|
|
@ -1275,7 +1302,8 @@ public class GeneralQueries {
|
|||
|
||||
userIds.addAll(ThirdPartyQueries.getPrimaryUserIdUsingEmail_Transaction(start, sqlCon, appIdentifier, email));
|
||||
|
||||
String webauthnUserId = WebAuthNQueries.getPrimaryUserIdUsingEmail_Transaction(start, sqlCon, appIdentifier, email);
|
||||
String webauthnUserId = WebAuthNQueries.getPrimaryUserIdForAppUsingEmail_Transaction(start, sqlCon,
|
||||
appIdentifier, email);
|
||||
if(webauthnUserId != null) {
|
||||
userIds.add(webauthnUserId);
|
||||
}
|
||||
|
|
@ -1312,7 +1340,7 @@ public class GeneralQueries {
|
|||
|
||||
userIds.addAll(ThirdPartyQueries.getPrimaryUserIdUsingEmail(start, tenantIdentifier, email));
|
||||
|
||||
String webauthnUserId = WebAuthNQueries.getPrimaryUserIdUsingEmail(start, tenantIdentifier.toAppIdentifier(), email);
|
||||
String webauthnUserId = WebAuthNQueries.getPrimaryUserIdForTenantUsingEmail(start, tenantIdentifier, email);
|
||||
if(webauthnUserId != null) {
|
||||
userIds.add(webauthnUserId);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,458 @@
|
|||
/*
|
||||
* Copyright (c) 2025, VRAI Labs and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* This software is licensed under the Apache License, Version 2.0 (the
|
||||
* "License") as published by the Apache Software Foundation.
|
||||
*
|
||||
* You may not use this file except in compliance with the License. You may
|
||||
* obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package io.supertokens.inmemorydb.queries;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Types;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
|
||||
import static io.supertokens.inmemorydb.QueryExecutorTemplate.execute;
|
||||
import static io.supertokens.inmemorydb.QueryExecutorTemplate.update;
|
||||
import io.supertokens.inmemorydb.Start;
|
||||
import io.supertokens.inmemorydb.config.Config;
|
||||
import io.supertokens.pluginInterface.exceptions.StorageQueryException;
|
||||
import io.supertokens.pluginInterface.multitenancy.TenantIdentifier;
|
||||
import io.supertokens.pluginInterface.saml.SAMLClaimsInfo;
|
||||
import io.supertokens.pluginInterface.saml.SAMLClient;
|
||||
import io.supertokens.pluginInterface.saml.SAMLRelayStateInfo;
|
||||
|
||||
public class SAMLQueries {
|
||||
public static String getQueryToCreateSAMLClientsTable(Start start) {
|
||||
String table = Config.getConfig(start).getSAMLClientsTable();
|
||||
String tenantsTable = Config.getConfig(start).getTenantsTable();
|
||||
// @formatter:off
|
||||
return "CREATE TABLE IF NOT EXISTS " + table + " ("
|
||||
+ "app_id VARCHAR(64) NOT NULL DEFAULT 'public',"
|
||||
+ "tenant_id VARCHAR(64) NOT NULL DEFAULT 'public',"
|
||||
+ "client_id VARCHAR(255) NOT NULL,"
|
||||
+ "client_secret TEXT,"
|
||||
+ "sso_login_url TEXT NOT NULL,"
|
||||
+ "redirect_uris TEXT NOT NULL," // store JsonArray.toString()
|
||||
+ "default_redirect_uri VARCHAR(1024) NOT NULL,"
|
||||
+ "idp_entity_id VARCHAR(1024),"
|
||||
+ "idp_signing_certificate TEXT,"
|
||||
+ "allow_idp_initiated_login BOOLEAN NOT NULL DEFAULT FALSE,"
|
||||
+ "enable_request_signing BOOLEAN NOT NULL DEFAULT TRUE,"
|
||||
+ "created_at BIGINT NOT NULL,"
|
||||
+ "updated_at BIGINT NOT NULL,"
|
||||
+ "UNIQUE (app_id, tenant_id, idp_entity_id),"
|
||||
+ "PRIMARY KEY (app_id, tenant_id, client_id),"
|
||||
+ "FOREIGN KEY (app_id, tenant_id) REFERENCES " + tenantsTable + " (app_id, tenant_id) ON DELETE CASCADE"
|
||||
+ ");";
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
public static String getQueryToCreateSAMLClientsAppIdTenantIdIndex(Start start) {
|
||||
String table = Config.getConfig(start).getSAMLClientsTable();
|
||||
return "CREATE INDEX IF NOT EXISTS saml_clients_app_tenant_index ON " + table + "(app_id, tenant_id);";
|
||||
}
|
||||
|
||||
public static String getQueryToCreateSAMLRelayStateTable(Start start) {
|
||||
String table = Config.getConfig(start).getSAMLRelayStateTable();
|
||||
String tenantsTable = Config.getConfig(start).getTenantsTable();
|
||||
// @formatter:off
|
||||
return "CREATE TABLE IF NOT EXISTS " + table + " ("
|
||||
+ "app_id VARCHAR(64) NOT NULL DEFAULT 'public',"
|
||||
+ "tenant_id VARCHAR(64) NOT NULL DEFAULT 'public',"
|
||||
+ "relay_state VARCHAR(255) NOT NULL,"
|
||||
+ "client_id VARCHAR(255) NOT NULL,"
|
||||
+ "state TEXT,"
|
||||
+ "redirect_uri VARCHAR(1024) NOT NULL,"
|
||||
+ "created_at BIGINT NOT NULL,"
|
||||
+ "expires_at BIGINT NOT NULL,"
|
||||
+ "PRIMARY KEY (relay_state)," // relayState must be unique
|
||||
+ "FOREIGN KEY (app_id, tenant_id) REFERENCES " + tenantsTable + " (app_id, tenant_id) ON DELETE CASCADE"
|
||||
+ ");";
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
public static String getQueryToCreateSAMLRelayStateAppIdTenantIdIndex(Start start) {
|
||||
String table = Config.getConfig(start).getSAMLRelayStateTable();
|
||||
return "CREATE INDEX IF NOT EXISTS saml_relay_state_app_tenant_index ON " + table + "(app_id, tenant_id);";
|
||||
}
|
||||
|
||||
public static String getQueryToCreateSAMLRelayStateExpiresAtIndex(Start start) {
|
||||
String table = Config.getConfig(start).getSAMLRelayStateTable();
|
||||
return "CREATE INDEX IF NOT EXISTS saml_relay_state_expires_at_index ON " + table + "(expires_at);";
|
||||
}
|
||||
|
||||
public static String getQueryToCreateSAMLClaimsTable(Start start) {
|
||||
String table = Config.getConfig(start).getSAMLClaimsTable();
|
||||
String tenantsTable = Config.getConfig(start).getTenantsTable();
|
||||
// @formatter:off
|
||||
return "CREATE TABLE IF NOT EXISTS " + table + " ("
|
||||
+ "app_id VARCHAR(64) NOT NULL DEFAULT 'public',"
|
||||
+ "tenant_id VARCHAR(64) NOT NULL DEFAULT 'public',"
|
||||
+ "client_id VARCHAR(255) NOT NULL,"
|
||||
+ "code VARCHAR(255) NOT NULL,"
|
||||
+ "claims TEXT NOT NULL,"
|
||||
+ "created_at BIGINT NOT NULL,"
|
||||
+ "expires_at BIGINT NOT NULL,"
|
||||
+ "PRIMARY KEY (code),"
|
||||
+ "FOREIGN KEY (app_id, tenant_id) REFERENCES " + tenantsTable + " (app_id, tenant_id) ON DELETE CASCADE"
|
||||
+ ");";
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
public static String getQueryToCreateSAMLClaimsAppIdTenantIdIndex(Start start) {
|
||||
String table = Config.getConfig(start).getSAMLClaimsTable();
|
||||
return "CREATE INDEX IF NOT EXISTS saml_claims_app_tenant_index ON " + table + "(app_id, tenant_id);";
|
||||
}
|
||||
|
||||
public static String getQueryToCreateSAMLClaimsExpiresAtIndex(Start start) {
|
||||
String table = Config.getConfig(start).getSAMLClaimsTable();
|
||||
return "CREATE INDEX IF NOT EXISTS saml_claims_expires_at_index ON " + table + "(expires_at);";
|
||||
}
|
||||
|
||||
public static void saveRelayStateInfo(Start start, TenantIdentifier tenantIdentifier,
|
||||
String relayState, String clientId, String state, String redirectURI, long relayStateValidity)
|
||||
throws StorageQueryException {
|
||||
String table = Config.getConfig(start).getSAMLRelayStateTable();
|
||||
String QUERY = "INSERT INTO " + table +
|
||||
" (app_id, tenant_id, relay_state, client_id, state, redirect_uri, created_at, expires_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)";
|
||||
|
||||
try {
|
||||
update(start, QUERY, pst -> {
|
||||
pst.setString(1, tenantIdentifier.getAppId());
|
||||
pst.setString(2, tenantIdentifier.getTenantId());
|
||||
pst.setString(3, relayState);
|
||||
pst.setString(4, clientId);
|
||||
if (state != null) {
|
||||
pst.setString(5, state);
|
||||
} else {
|
||||
pst.setNull(5, java.sql.Types.VARCHAR);
|
||||
}
|
||||
pst.setString(6, redirectURI);
|
||||
pst.setLong(7, System.currentTimeMillis());
|
||||
pst.setLong(8, System.currentTimeMillis() + relayStateValidity);
|
||||
});
|
||||
} catch (SQLException e) {
|
||||
throw new StorageQueryException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static SAMLRelayStateInfo getRelayStateInfo(Start start, TenantIdentifier tenantIdentifier, String relayState)
|
||||
throws StorageQueryException {
|
||||
String table = Config.getConfig(start).getSAMLRelayStateTable();
|
||||
String QUERY = "SELECT client_id, state, redirect_uri, expires_at FROM " + table
|
||||
+ " WHERE app_id = ? AND tenant_id = ? AND relay_state = ? AND expires_at >= ?";
|
||||
|
||||
try {
|
||||
return execute(start, QUERY, pst -> {
|
||||
pst.setString(1, tenantIdentifier.getAppId());
|
||||
pst.setString(2, tenantIdentifier.getTenantId());
|
||||
pst.setString(3, relayState);
|
||||
pst.setLong(4, System.currentTimeMillis());
|
||||
}, result -> {
|
||||
if (result.next()) {
|
||||
String clientId = result.getString("client_id");
|
||||
String state = result.getString("state"); // may be null
|
||||
String redirectURI = result.getString("redirect_uri");
|
||||
return new SAMLRelayStateInfo(relayState, clientId, state, redirectURI);
|
||||
}
|
||||
return null;
|
||||
});
|
||||
} catch (SQLException e) {
|
||||
throw new StorageQueryException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void saveSAMLClaims(Start start, TenantIdentifier tenantIdentifier, String clientId, String code, String claimsJson, long claimsValidity)
|
||||
throws StorageQueryException {
|
||||
String table = Config.getConfig(start).getSAMLClaimsTable();
|
||||
String QUERY = "INSERT INTO " + table +
|
||||
" (app_id, tenant_id, client_id, code, claims, created_at, expires_at) VALUES (?, ?, ?, ?, ?, ?, ?)";
|
||||
|
||||
try {
|
||||
update(start, QUERY, pst -> {
|
||||
pst.setString(1, tenantIdentifier.getAppId());
|
||||
pst.setString(2, tenantIdentifier.getTenantId());
|
||||
pst.setString(3, clientId);
|
||||
pst.setString(4, code);
|
||||
pst.setString(5, claimsJson);
|
||||
pst.setLong(6, System.currentTimeMillis());
|
||||
pst.setLong(7, System.currentTimeMillis() + claimsValidity);
|
||||
});
|
||||
} catch (SQLException e) {
|
||||
throw new StorageQueryException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static SAMLClaimsInfo getSAMLClaimsAndRemoveCode(Start start, TenantIdentifier tenantIdentifier, String code)
|
||||
throws StorageQueryException {
|
||||
String table = Config.getConfig(start).getSAMLClaimsTable();
|
||||
String QUERY = "SELECT client_id, claims FROM " + table + " WHERE app_id = ? AND tenant_id = ? AND code = ? AND expires_at >= ?";
|
||||
try {
|
||||
SAMLClaimsInfo claimsInfo = execute(start, QUERY, pst -> {
|
||||
pst.setString(1, tenantIdentifier.getAppId());
|
||||
pst.setString(2, tenantIdentifier.getTenantId());
|
||||
pst.setString(3, code);
|
||||
pst.setLong(4, System.currentTimeMillis());
|
||||
}, result -> {
|
||||
if (result.next()) {
|
||||
String clientId = result.getString("client_id");
|
||||
JsonObject claims = com.google.gson.JsonParser.parseString(result.getString("claims")).getAsJsonObject();
|
||||
return new SAMLClaimsInfo(clientId, claims);
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
||||
if (claimsInfo != null) {
|
||||
String DELETE = "DELETE FROM " + table + " WHERE app_id = ? AND tenant_id = ? AND code = ?";
|
||||
update(start, DELETE, pst -> {
|
||||
pst.setString(1, tenantIdentifier.getAppId());
|
||||
pst.setString(2, tenantIdentifier.getTenantId());
|
||||
pst.setString(3, code);
|
||||
});
|
||||
}
|
||||
return claimsInfo;
|
||||
} catch (SQLException e) {
|
||||
throw new StorageQueryException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static SAMLClient createOrUpdateSAMLClient(
|
||||
Start start,
|
||||
TenantIdentifier tenantIdentifier,
|
||||
String clientId,
|
||||
String clientSecret,
|
||||
String ssoLoginURL,
|
||||
String redirectURIsJson,
|
||||
String defaultRedirectURI,
|
||||
String idpEntityId,
|
||||
String idpSigningCertificate,
|
||||
boolean allowIDPInitiatedLogin,
|
||||
boolean enableRequestSigning)
|
||||
throws StorageQueryException, SQLException {
|
||||
String table = Config.getConfig(start).getSAMLClientsTable();
|
||||
String QUERY = "INSERT INTO " + table +
|
||||
" (app_id, tenant_id, client_id, client_secret, sso_login_url, redirect_uris, default_redirect_uri, idp_entity_id, idp_signing_certificate, allow_idp_initiated_login, enable_request_signing, created_at, updated_at) " +
|
||||
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) " +
|
||||
"ON CONFLICT (app_id, tenant_id, client_id) DO UPDATE SET " +
|
||||
"client_secret = ?, sso_login_url = ?, redirect_uris = ?, default_redirect_uri = ?, idp_entity_id = ?, idp_signing_certificate = ?, allow_idp_initiated_login = ?, enable_request_signing = ?, updated_at = ?";
|
||||
long now = System.currentTimeMillis();
|
||||
update(start, QUERY, pst -> {
|
||||
pst.setString(1, tenantIdentifier.getAppId());
|
||||
pst.setString(2, tenantIdentifier.getTenantId());
|
||||
pst.setString(3, clientId);
|
||||
if (clientSecret != null) {
|
||||
pst.setString(4, clientSecret);
|
||||
} else {
|
||||
pst.setNull(4, Types.VARCHAR);
|
||||
}
|
||||
pst.setString(5, ssoLoginURL);
|
||||
pst.setString(6, redirectURIsJson);
|
||||
pst.setString(7, defaultRedirectURI);
|
||||
if (idpEntityId != null) {
|
||||
pst.setString(8, idpEntityId);
|
||||
} else {
|
||||
pst.setNull(8, java.sql.Types.VARCHAR);
|
||||
}
|
||||
if (idpSigningCertificate != null) {
|
||||
pst.setString(9, idpSigningCertificate);
|
||||
} else {
|
||||
pst.setNull(9, Types.VARCHAR);
|
||||
}
|
||||
pst.setBoolean(10, allowIDPInitiatedLogin);
|
||||
pst.setBoolean(11, enableRequestSigning);
|
||||
pst.setLong(12, now);
|
||||
pst.setLong(13, now);
|
||||
|
||||
if (clientSecret != null) {
|
||||
pst.setString(14, clientSecret);
|
||||
} else {
|
||||
pst.setNull(14, Types.VARCHAR);
|
||||
}
|
||||
pst.setString(15, ssoLoginURL);
|
||||
pst.setString(16, redirectURIsJson);
|
||||
pst.setString(17, defaultRedirectURI);
|
||||
if (idpEntityId != null) {
|
||||
pst.setString(18, idpEntityId);
|
||||
} else {
|
||||
pst.setNull(18, java.sql.Types.VARCHAR);
|
||||
}
|
||||
if (idpSigningCertificate != null) {
|
||||
pst.setString(19, idpSigningCertificate);
|
||||
} else {
|
||||
pst.setNull(19, Types.VARCHAR);
|
||||
}
|
||||
pst.setBoolean(20, allowIDPInitiatedLogin);
|
||||
pst.setBoolean(21, enableRequestSigning);
|
||||
pst.setLong(22, now);
|
||||
});
|
||||
|
||||
return getSAMLClient(start, tenantIdentifier, clientId);
|
||||
}
|
||||
|
||||
public static SAMLClient getSAMLClient(Start start, TenantIdentifier tenantIdentifier, String clientId)
|
||||
throws StorageQueryException {
|
||||
String table = Config.getConfig(start).getSAMLClientsTable();
|
||||
String QUERY = "SELECT client_id, client_secret, sso_login_url, redirect_uris, default_redirect_uri, idp_entity_id, idp_signing_certificate, allow_idp_initiated_login, enable_request_signing FROM " + table
|
||||
+ " WHERE app_id = ? AND tenant_id = ? AND client_id = ?";
|
||||
|
||||
try {
|
||||
return execute(start, QUERY, pst -> {
|
||||
pst.setString(1, tenantIdentifier.getAppId());
|
||||
pst.setString(2, tenantIdentifier.getTenantId());
|
||||
pst.setString(3, clientId);
|
||||
}, result -> {
|
||||
if (result.next()) {
|
||||
String fetchedClientId = result.getString("client_id");
|
||||
String clientSecret = result.getString("client_secret");
|
||||
String ssoLoginURL = result.getString("sso_login_url");
|
||||
String redirectUrisJson = result.getString("redirect_uris");
|
||||
String defaultRedirectURI = result.getString("default_redirect_uri");
|
||||
String idpEntityId = result.getString("idp_entity_id");
|
||||
String idpSigningCertificate = result.getString("idp_signing_certificate");
|
||||
boolean allowIDPInitiatedLogin = result.getBoolean("allow_idp_initiated_login");
|
||||
boolean enableRequestSigning = result.getBoolean("enable_request_signing");
|
||||
|
||||
JsonArray redirectURIs = JsonParser.parseString(redirectUrisJson).getAsJsonArray();
|
||||
return new SAMLClient(fetchedClientId, clientSecret, ssoLoginURL, redirectURIs, defaultRedirectURI, idpEntityId, idpSigningCertificate, allowIDPInitiatedLogin, enableRequestSigning);
|
||||
}
|
||||
return null;
|
||||
});
|
||||
} catch (SQLException e) {
|
||||
throw new StorageQueryException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static SAMLClient getSAMLClientByIDPEntityId(Start start, TenantIdentifier tenantIdentifier, String idpEntityId) throws StorageQueryException {
|
||||
String table = Config.getConfig(start).getSAMLClientsTable();
|
||||
String QUERY = "SELECT client_id, client_secret, sso_login_url, redirect_uris, default_redirect_uri, idp_entity_id, idp_signing_certificate, allow_idp_initiated_login, enable_request_signing FROM " + table
|
||||
+ " WHERE app_id = ? AND tenant_id = ? AND idp_entity_id = ?";
|
||||
|
||||
try {
|
||||
return execute(start, QUERY, pst -> {
|
||||
pst.setString(1, tenantIdentifier.getAppId());
|
||||
pst.setString(2, tenantIdentifier.getTenantId());
|
||||
pst.setString(3, idpEntityId);
|
||||
}, result -> {
|
||||
if (result.next()) {
|
||||
String fetchedClientId = result.getString("client_id");
|
||||
String clientSecret = result.getString("client_secret");
|
||||
String ssoLoginURL = result.getString("sso_login_url");
|
||||
String redirectUrisJson = result.getString("redirect_uris");
|
||||
String defaultRedirectURI = result.getString("default_redirect_uri");
|
||||
String fetchedIdpEntityId = result.getString("idp_entity_id");
|
||||
String idpSigningCertificate = result.getString("idp_signing_certificate");
|
||||
boolean allowIDPInitiatedLogin = result.getBoolean("allow_idp_initiated_login");
|
||||
boolean enableRequestSigning = result.getBoolean("enable_request_signing");
|
||||
|
||||
JsonArray redirectURIs = JsonParser.parseString(redirectUrisJson).getAsJsonArray();
|
||||
return new SAMLClient(fetchedClientId, clientSecret, ssoLoginURL, redirectURIs, defaultRedirectURI, fetchedIdpEntityId, idpSigningCertificate, allowIDPInitiatedLogin, enableRequestSigning);
|
||||
}
|
||||
return null;
|
||||
});
|
||||
} catch (SQLException e) {
|
||||
throw new StorageQueryException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static List<SAMLClient> getSAMLClients(Start start, TenantIdentifier tenantIdentifier)
|
||||
throws StorageQueryException {
|
||||
String table = Config.getConfig(start).getSAMLClientsTable();
|
||||
String QUERY = "SELECT client_id, client_secret, sso_login_url, redirect_uris, default_redirect_uri, idp_entity_id, idp_signing_certificate, allow_idp_initiated_login, enable_request_signing FROM " + table
|
||||
+ " WHERE app_id = ? AND tenant_id = ?";
|
||||
|
||||
try {
|
||||
return execute(start, QUERY, pst -> {
|
||||
pst.setString(1, tenantIdentifier.getAppId());
|
||||
pst.setString(2, tenantIdentifier.getTenantId());
|
||||
}, result -> {
|
||||
List<SAMLClient> clients = new ArrayList<>();
|
||||
while (result.next()) {
|
||||
String fetchedClientId = result.getString("client_id");
|
||||
String clientSecret = result.getString("client_secret");
|
||||
String ssoLoginURL = result.getString("sso_login_url");
|
||||
String redirectUrisJson = result.getString("redirect_uris");
|
||||
String defaultRedirectURI = result.getString("default_redirect_uri");
|
||||
String idpEntityId = result.getString("idp_entity_id");
|
||||
String idpSigningCertificate = result.getString("idp_signing_certificate");
|
||||
boolean allowIDPInitiatedLogin = result.getBoolean("allow_idp_initiated_login");
|
||||
boolean enableRequestSigning = result.getBoolean("enable_request_signing");
|
||||
|
||||
JsonArray redirectURIs = JsonParser.parseString(redirectUrisJson).getAsJsonArray();
|
||||
clients.add(new SAMLClient(fetchedClientId, clientSecret, ssoLoginURL, redirectURIs, defaultRedirectURI, idpEntityId, idpSigningCertificate, allowIDPInitiatedLogin, enableRequestSigning));
|
||||
}
|
||||
return clients;
|
||||
});
|
||||
} catch (SQLException e) {
|
||||
throw new StorageQueryException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean removeSAMLClient(Start start, TenantIdentifier tenantIdentifier, String clientId)
|
||||
throws StorageQueryException {
|
||||
String table = Config.getConfig(start).getSAMLClientsTable();
|
||||
String QUERY = "DELETE FROM " + table + " WHERE app_id = ? AND tenant_id = ? AND client_id = ?";
|
||||
try {
|
||||
return update(start, QUERY, pst -> {
|
||||
pst.setString(1, tenantIdentifier.getAppId());
|
||||
pst.setString(2, tenantIdentifier.getTenantId());
|
||||
pst.setString(3, clientId);
|
||||
}) > 0;
|
||||
|
||||
} catch (SQLException e) {
|
||||
throw new StorageQueryException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void removeExpiredSAMLCodesAndRelayStates(Start start) throws StorageQueryException {
|
||||
try {
|
||||
{
|
||||
String QUERY = "DELETE FROM " + Config.getConfig(start).getSAMLClaimsTable() + " WHERE expires_at <= ?";
|
||||
update(start, QUERY, pst -> {
|
||||
pst.setLong(1, System.currentTimeMillis());
|
||||
});
|
||||
}
|
||||
{
|
||||
String QUERY = "DELETE FROM " + Config.getConfig(start).getSAMLRelayStateTable() + " WHERE expires_at <= ?";
|
||||
update(start, QUERY, pst -> {
|
||||
pst.setLong(1, System.currentTimeMillis());
|
||||
});
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
throw new StorageQueryException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static int countSAMLClients(Start start, TenantIdentifier tenantIdentifier) throws StorageQueryException {
|
||||
String table = Config.getConfig(start).getSAMLClientsTable();
|
||||
String QUERY = "SELECT COUNT(*) as c FROM " + table
|
||||
+ " WHERE app_id = ? AND tenant_id = ?";
|
||||
|
||||
try {
|
||||
return execute(start, QUERY, pst -> {
|
||||
pst.setString(1, tenantIdentifier.getAppId());
|
||||
pst.setString(2, tenantIdentifier.getTenantId());
|
||||
}, result -> {
|
||||
if (result.next()) {
|
||||
return result.getInt("c");
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
} catch (SQLException e) {
|
||||
throw new StorageQueryException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -376,13 +376,15 @@ public class WebAuthNQueries {
|
|||
return userInfo;
|
||||
}
|
||||
|
||||
public static String getPrimaryUserIdUsingEmail(Start start, AppIdentifier appIdentifier, String email)
|
||||
public static String getPrimaryUserIdForTenantUsingEmail(Start start, TenantIdentifier tenantIdentifier,
|
||||
String email)
|
||||
throws StorageQueryException {
|
||||
try {
|
||||
return start.startTransaction(con -> {
|
||||
try {
|
||||
Connection sqlConnection = (Connection) con.getConnection();
|
||||
return getPrimaryUserIdUsingEmail_Transaction(start, sqlConnection, appIdentifier, email);
|
||||
return getPrimaryUserIdForTenantUsingEmail_Transaction(start, sqlConnection, tenantIdentifier,
|
||||
email);
|
||||
} catch (SQLException e) {
|
||||
throw new StorageQueryException(e);
|
||||
}
|
||||
|
|
@ -392,7 +394,30 @@ public class WebAuthNQueries {
|
|||
}
|
||||
}
|
||||
|
||||
public static String getPrimaryUserIdUsingEmail_Transaction(Start start, Connection sqlConnection, AppIdentifier appIdentifier, String email)
|
||||
public static String getPrimaryUserIdForTenantUsingEmail_Transaction(Start start, Connection sqlConnection,
|
||||
TenantIdentifier tenantIdentifier,
|
||||
String email)
|
||||
throws SQLException, StorageQueryException {
|
||||
String QUERY = "SELECT DISTINCT all_users.primary_or_recipe_user_id AS user_id "
|
||||
+ "FROM " + getConfig(start).getWebAuthNUserToTenantTable() + " AS ep" +
|
||||
" JOIN " + getConfig(start).getUsersTable() + " AS all_users" +
|
||||
" ON ep.app_id = all_users.app_id AND ep.user_id = all_users.user_id" +
|
||||
" WHERE ep.app_id = ? AND ep.email = ? AND ep.tenant_id = ?";
|
||||
|
||||
return execute(sqlConnection, QUERY, pst -> {
|
||||
pst.setString(1, tenantIdentifier.getAppId());
|
||||
pst.setString(2, email);
|
||||
pst.setString(3, tenantIdentifier.getTenantId());
|
||||
}, result -> {
|
||||
if (result.next()) {
|
||||
return result.getString("user_id");
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
public static String getPrimaryUserIdForAppUsingEmail_Transaction(Start start, Connection sqlConnection,
|
||||
AppIdentifier appIdentifier, String email)
|
||||
throws SQLException, StorageQueryException {
|
||||
String QUERY = "SELECT DISTINCT all_users.primary_or_recipe_user_id AS user_id "
|
||||
+ "FROM " + getConfig(start).getWebAuthNUserToTenantTable() + " AS ep" +
|
||||
|
|
@ -405,7 +430,7 @@ public class WebAuthNQueries {
|
|||
pst.setString(2, email);
|
||||
}, result -> {
|
||||
if (result.next()) {
|
||||
return result.getString("user_id");
|
||||
return result.getString("user_id");
|
||||
}
|
||||
return null;
|
||||
});
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import com.google.gson.Gson;
|
|||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonObject;
|
||||
import io.supertokens.Main;
|
||||
import io.supertokens.ResourceDistributor;
|
||||
import io.supertokens.jwt.exceptions.UnsupportedJWTSigningAlgorithmException;
|
||||
import io.supertokens.pluginInterface.exceptions.StorageQueryException;
|
||||
import io.supertokens.pluginInterface.exceptions.StorageTransactionLogicException;
|
||||
|
|
@ -54,7 +55,7 @@ public class JWTSigningFunctions {
|
|||
throws StorageQueryException, StorageTransactionLogicException, NoSuchAlgorithmException,
|
||||
InvalidKeySpecException, JWTCreationException, UnsupportedJWTSigningAlgorithmException {
|
||||
try {
|
||||
return createJWTToken(new AppIdentifier(null, null), main, algorithm, payload, jwksDomain,
|
||||
return createJWTToken(ResourceDistributor.getAppForTesting().toAppIdentifier(), main, algorithm, payload, jwksDomain,
|
||||
jwtValidityInSeconds, useDynamicKey);
|
||||
} catch (TenantOrAppNotFoundException e) {
|
||||
throw new IllegalStateException(e);
|
||||
|
|
|
|||
|
|
@ -33,6 +33,8 @@ import io.supertokens.pluginInterface.exceptions.InvalidConfigException;
|
|||
import io.supertokens.pluginInterface.exceptions.StorageQueryException;
|
||||
import io.supertokens.pluginInterface.multitenancy.*;
|
||||
import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException;
|
||||
import io.supertokens.saml.SAMLCertificate;
|
||||
import io.supertokens.pluginInterface.opentelemetry.WithinOtelSpan;
|
||||
import io.supertokens.session.refreshToken.RefreshTokenKey;
|
||||
import io.supertokens.signingkeys.AccessTokenSigningKey;
|
||||
import io.supertokens.signingkeys.JWTSigningKey;
|
||||
|
|
@ -116,6 +118,7 @@ public class MultitenancyHelper extends ResourceDistributor.SingletonResource {
|
|||
return StorageLayer.getMultitenancyStorage(main).getAllTenants();
|
||||
}
|
||||
|
||||
@WithinOtelSpan
|
||||
public List<TenantIdentifier> refreshTenantsInCoreBasedOnChangesInCoreConfigOrIfTenantListChanged(
|
||||
boolean reloadAllResources) {
|
||||
try {
|
||||
|
|
@ -233,6 +236,7 @@ public class MultitenancyHelper extends ResourceDistributor.SingletonResource {
|
|||
}
|
||||
AccessTokenSigningKey.loadForAllTenants(main, apps, tenantsThatChanged);
|
||||
RefreshTokenKey.loadForAllTenants(main, apps, tenantsThatChanged);
|
||||
SAMLCertificate.loadForAllTenants(main, apps, tenantsThatChanged);
|
||||
JWTSigningKey.loadForAllTenants(main, apps, tenantsThatChanged);
|
||||
SigningKeys.loadForAllTenants(main, apps, tenantsThatChanged);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,8 +22,8 @@ public class HttpRequestForOAuthProvider {
|
|||
// case of errors, etc.
|
||||
// Left the original HttpRequest as is to avoid any issues with existing code.
|
||||
|
||||
private static final int CONNECTION_TIMEOUT = 5000;
|
||||
private static final int READ_TIMEOUT = 5000;
|
||||
private static final int CONNECTION_TIMEOUT = 15000;
|
||||
private static final int READ_TIMEOUT = 15000;
|
||||
|
||||
public static Response doGet(String url, Map<String, String> headers, Map<String, String> queryParams) throws IOException {
|
||||
if (queryParams == null) {
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
package io.supertokens.output;
|
||||
|
||||
import ch.qos.logback.classic.Level;
|
||||
import ch.qos.logback.classic.Logger;
|
||||
import ch.qos.logback.classic.LoggerContext;
|
||||
import ch.qos.logback.classic.spi.ILoggingEvent;
|
||||
|
|
@ -32,6 +33,7 @@ import io.supertokens.pluginInterface.Storage;
|
|||
import io.supertokens.pluginInterface.multitenancy.TenantIdentifier;
|
||||
import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException;
|
||||
import io.supertokens.storageLayer.StorageLayer;
|
||||
import io.supertokens.telemetry.TelemetryProvider;
|
||||
import io.supertokens.utils.Utils;
|
||||
import io.supertokens.version.Version;
|
||||
import io.supertokens.webserver.Webserver;
|
||||
|
|
@ -54,6 +56,12 @@ public class Logging extends ResourceDistributor.SingletonResource {
|
|||
public static final String ANSI_WHITE = "\u001B[37m";
|
||||
|
||||
private Logging(Main main) {
|
||||
// Set global logging level
|
||||
LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
|
||||
Logger rootLogger = loggerContext.getLogger(Logger.ROOT_LOGGER_NAME);
|
||||
Level newLevel = Level.toLevel(Config.getBaseConfig(main).getLogLevel(), Level.INFO); // Default to INFO if invalid
|
||||
rootLogger.setLevel(newLevel);
|
||||
|
||||
this.infoLogger = Config.getBaseConfig(main).getInfoLogPath(main).equals("null")
|
||||
? createLoggerForConsole(main, "io.supertokens.Info", LOG_LEVEL.INFO)
|
||||
: createLoggerForFile(main, Config.getBaseConfig(main).getInfoLogPath(main),
|
||||
|
|
@ -65,7 +73,7 @@ public class Logging extends ResourceDistributor.SingletonResource {
|
|||
Storage storage = StorageLayer.getBaseStorage(main);
|
||||
if (storage != null) {
|
||||
storage.initFileLogging(Config.getBaseConfig(main).getInfoLogPath(main),
|
||||
Config.getBaseConfig(main).getErrorLogPath(main));
|
||||
Config.getBaseConfig(main).getErrorLogPath(main), TelemetryProvider.getInstance(main));
|
||||
}
|
||||
try {
|
||||
// we wait here for a bit so that the loggers can be properly initialised..
|
||||
|
|
@ -111,7 +119,9 @@ public class Logging extends ResourceDistributor.SingletonResource {
|
|||
try {
|
||||
msg = msg.trim();
|
||||
if (getInstance(main) != null) {
|
||||
getInstance(main).infoLogger.debug(getFormattedMessage(tenantIdentifier, msg));
|
||||
String formattedMsg = getFormattedMessage(tenantIdentifier, msg);
|
||||
getInstance(main).infoLogger.debug(formattedMsg);
|
||||
TelemetryProvider.getInstance(main).createLogEvent(tenantIdentifier, formattedMsg, "debug");
|
||||
}
|
||||
} catch (NullPointerException e) {
|
||||
// sometimes logger.debug throws a null pointer exception...
|
||||
|
|
@ -162,6 +172,8 @@ public class Logging extends ResourceDistributor.SingletonResource {
|
|||
if (getInstance(main) != null) {
|
||||
getInstance(main).infoLogger.info(msg);
|
||||
}
|
||||
|
||||
TelemetryProvider.getInstance(main).createLogEvent(tenantIdentifier, msg, "info");
|
||||
} catch (NullPointerException ignored) {
|
||||
}
|
||||
}
|
||||
|
|
@ -175,6 +187,7 @@ public class Logging extends ResourceDistributor.SingletonResource {
|
|||
msg = getFormattedMessage(tenantIdentifier, msg);
|
||||
if (getInstance(main) != null) {
|
||||
getInstance(main).errorLogger.warn(msg);
|
||||
TelemetryProvider.getInstance(main).createLogEvent(tenantIdentifier, msg, "warn");
|
||||
}
|
||||
} catch (NullPointerException ignored) {
|
||||
}
|
||||
|
|
@ -194,7 +207,9 @@ public class Logging extends ResourceDistributor.SingletonResource {
|
|||
try {
|
||||
err = err.trim();
|
||||
if (getInstance(main) != null) {
|
||||
getInstance(main).errorLogger.error(getFormattedMessage(tenantIdentifier, err));
|
||||
String formattedMessage = getFormattedMessage(tenantIdentifier, err);
|
||||
getInstance(main).errorLogger.error(formattedMessage);
|
||||
TelemetryProvider.getInstance(main).createLogEvent(tenantIdentifier, formattedMessage, "error");
|
||||
}
|
||||
if (toConsoleAsWell || getInstance(main) == null) {
|
||||
systemErr(prependTenantIdentifierToMessage(tenantIdentifier, err));
|
||||
|
|
@ -228,6 +243,9 @@ public class Logging extends ResourceDistributor.SingletonResource {
|
|||
message = message.trim();
|
||||
if (getInstance(main) != null) {
|
||||
getInstance(main).errorLogger.error(getFormattedMessage(tenantIdentifier, message, e));
|
||||
TelemetryProvider.getInstance(main)
|
||||
.createLogEvent(tenantIdentifier, getFormattedMessage(tenantIdentifier, message, e),
|
||||
"error");
|
||||
}
|
||||
if (toConsoleAsWell || getInstance(main) == null) {
|
||||
systemErr(prependTenantIdentifierToMessage(tenantIdentifier, message));
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
package io.supertokens.passwordless;
|
||||
|
||||
import io.supertokens.Main;
|
||||
import io.supertokens.ResourceDistributor;
|
||||
import io.supertokens.authRecipe.AuthRecipe;
|
||||
import io.supertokens.config.Config;
|
||||
import io.supertokens.emailpassword.exceptions.EmailChangeNotAllowedException;
|
||||
|
|
@ -74,7 +75,7 @@ public class Passwordless {
|
|||
try {
|
||||
Storage storage = StorageLayer.getStorage(main);
|
||||
return createCode(
|
||||
new TenantIdentifier(null, null, null), storage,
|
||||
ResourceDistributor.getAppForTesting(), storage,
|
||||
main, email, phoneNumber, deviceId, userInputCode);
|
||||
} catch (TenantOrAppNotFoundException | BadPermissionException e) {
|
||||
throw new IllegalStateException(e);
|
||||
|
|
@ -161,7 +162,7 @@ public class Passwordless {
|
|||
NoSuchAlgorithmException, Base64EncodingException {
|
||||
Storage storage = StorageLayer.getStorage(main);
|
||||
return getDeviceWithCodesById(
|
||||
new TenantIdentifier(null, null, null), storage,
|
||||
ResourceDistributor.getAppForTesting(), storage,
|
||||
deviceId);
|
||||
}
|
||||
|
||||
|
|
@ -170,7 +171,7 @@ public class Passwordless {
|
|||
throws StorageQueryException {
|
||||
Storage storage = StorageLayer.getStorage(main);
|
||||
return getDeviceWithCodesByIdHash(
|
||||
new TenantIdentifier(null, null, null), storage,
|
||||
ResourceDistributor.getAppForTesting(), storage,
|
||||
deviceIdHash);
|
||||
}
|
||||
|
||||
|
|
@ -210,7 +211,7 @@ public class Passwordless {
|
|||
throws StorageQueryException {
|
||||
Storage storage = StorageLayer.getStorage(main);
|
||||
return getDevicesWithCodesByEmail(
|
||||
new TenantIdentifier(null, null, null), storage, email);
|
||||
ResourceDistributor.getAppForTesting(), storage, email);
|
||||
}
|
||||
|
||||
public static List<DeviceWithCodes> getDevicesWithCodesByPhoneNumber(
|
||||
|
|
@ -235,7 +236,7 @@ public class Passwordless {
|
|||
throws StorageQueryException {
|
||||
Storage storage = StorageLayer.getStorage(main);
|
||||
return getDevicesWithCodesByPhoneNumber(
|
||||
new TenantIdentifier(null, null, null), storage,
|
||||
ResourceDistributor.getAppForTesting(), storage,
|
||||
phoneNumber);
|
||||
}
|
||||
|
||||
|
|
@ -249,7 +250,7 @@ public class Passwordless {
|
|||
try {
|
||||
Storage storage = StorageLayer.getStorage(main);
|
||||
return consumeCode(
|
||||
new TenantIdentifier(null, null, null), storage,
|
||||
ResourceDistributor.getAppForTesting(), storage,
|
||||
main, deviceId, deviceIdHashFromUser, userInputCode, linkCode, false);
|
||||
} catch (TenantOrAppNotFoundException | BadPermissionException e) {
|
||||
throw new IllegalStateException(e);
|
||||
|
|
@ -266,7 +267,7 @@ public class Passwordless {
|
|||
try {
|
||||
Storage storage = StorageLayer.getStorage(main);
|
||||
return consumeCode(
|
||||
new TenantIdentifier(null, null, null), storage,
|
||||
ResourceDistributor.getAppForTesting(), storage,
|
||||
main, deviceId, deviceIdHashFromUser, userInputCode, linkCode, setEmailVerified);
|
||||
} catch (TenantOrAppNotFoundException | BadPermissionException e) {
|
||||
throw new IllegalStateException(e);
|
||||
|
|
@ -567,7 +568,7 @@ public class Passwordless {
|
|||
public static void removeCode(Main main, String codeId)
|
||||
throws StorageQueryException, StorageTransactionLogicException {
|
||||
Storage storage = StorageLayer.getStorage(main);
|
||||
removeCode(new TenantIdentifier(null, null, null), storage,
|
||||
removeCode(ResourceDistributor.getAppForTesting(), storage,
|
||||
codeId);
|
||||
}
|
||||
|
||||
|
|
@ -622,7 +623,7 @@ public class Passwordless {
|
|||
throws StorageQueryException, StorageTransactionLogicException {
|
||||
Storage storage = StorageLayer.getStorage(main);
|
||||
removeCodesByEmail(
|
||||
new TenantIdentifier(null, null, null), storage, email);
|
||||
ResourceDistributor.getAppForTesting(), storage, email);
|
||||
}
|
||||
|
||||
public static void removeCodesByEmail(TenantIdentifier tenantIdentifier, Storage storage, String email)
|
||||
|
|
@ -642,7 +643,7 @@ public class Passwordless {
|
|||
throws StorageQueryException, StorageTransactionLogicException {
|
||||
Storage storage = StorageLayer.getStorage(main);
|
||||
removeCodesByPhoneNumber(
|
||||
new TenantIdentifier(null, null, null), storage,
|
||||
ResourceDistributor.getAppForTesting(), storage,
|
||||
phoneNumber);
|
||||
}
|
||||
|
||||
|
|
@ -664,7 +665,7 @@ public class Passwordless {
|
|||
throws StorageQueryException {
|
||||
Storage storage = StorageLayer.getStorage(main);
|
||||
return getUserById(
|
||||
new AppIdentifier(null, null), storage, userId);
|
||||
ResourceDistributor.getAppForTesting().toAppIdentifier(), storage, userId);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
|
|
@ -689,7 +690,7 @@ public class Passwordless {
|
|||
String phoneNumber) throws StorageQueryException {
|
||||
Storage storage = StorageLayer.getStorage(main);
|
||||
return getUserByPhoneNumber(
|
||||
new TenantIdentifier(null, null, null), storage,
|
||||
ResourceDistributor.getAppForTesting(), storage,
|
||||
phoneNumber);
|
||||
}
|
||||
|
||||
|
|
@ -714,7 +715,7 @@ public class Passwordless {
|
|||
throws StorageQueryException {
|
||||
Storage storage = StorageLayer.getStorage(main);
|
||||
return getUserByEmail(
|
||||
new TenantIdentifier(null, null, null), storage, email);
|
||||
ResourceDistributor.getAppForTesting(), storage, email);
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
|
|
@ -740,7 +741,7 @@ public class Passwordless {
|
|||
DuplicatePhoneNumberException, UserWithoutContactInfoException, EmailChangeNotAllowedException,
|
||||
PhoneNumberChangeNotAllowedException {
|
||||
Storage storage = StorageLayer.getStorage(main);
|
||||
updateUser(new AppIdentifier(null, null), storage,
|
||||
updateUser(ResourceDistributor.getAppForTesting().toAppIdentifier(), storage,
|
||||
userId, emailUpdate, phoneNumberUpdate);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,690 @@
|
|||
/*
|
||||
* Copyright (c) 2025, VRAI Labs and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* This software is licensed under the Apache License, Version 2.0 (the
|
||||
* "License") as published by the Apache Software Foundation.
|
||||
*
|
||||
* You may not use this file except in compliance with the License. You may
|
||||
* obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package io.supertokens.saml;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.cert.CertificateEncodingException;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.zip.Deflater;
|
||||
import java.util.zip.DeflaterOutputStream;
|
||||
|
||||
import org.opensaml.core.xml.XMLObject;
|
||||
import org.opensaml.core.xml.XMLObjectBuilderFactory;
|
||||
import org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport;
|
||||
import org.opensaml.core.xml.io.UnmarshallingException;
|
||||
import org.opensaml.core.xml.util.XMLObjectSupport;
|
||||
import org.opensaml.saml.common.SAMLVersion;
|
||||
import org.opensaml.saml.common.xml.SAMLConstants;
|
||||
import org.opensaml.saml.saml2.core.Assertion;
|
||||
import org.opensaml.saml.saml2.core.Attribute;
|
||||
import org.opensaml.saml.saml2.core.AttributeStatement;
|
||||
import org.opensaml.saml.saml2.core.Audience;
|
||||
import org.opensaml.saml.saml2.core.AudienceRestriction;
|
||||
import org.opensaml.saml.saml2.core.AuthnContext;
|
||||
import org.opensaml.saml.saml2.core.AuthnContextClassRef;
|
||||
import org.opensaml.saml.saml2.core.AuthnRequest;
|
||||
import org.opensaml.saml.saml2.core.Conditions;
|
||||
import org.opensaml.saml.saml2.core.Issuer;
|
||||
import org.opensaml.saml.saml2.core.NameIDPolicy;
|
||||
import org.opensaml.saml.saml2.core.RequestedAuthnContext;
|
||||
import org.opensaml.saml.saml2.core.Response;
|
||||
import org.opensaml.saml.saml2.core.Subject;
|
||||
import org.opensaml.saml.saml2.metadata.EntityDescriptor;
|
||||
import org.opensaml.saml.saml2.metadata.IDPSSODescriptor;
|
||||
import org.opensaml.saml.saml2.metadata.SingleSignOnService;
|
||||
import org.opensaml.security.credential.Credential;
|
||||
import org.opensaml.security.credential.CredentialSupport;
|
||||
import org.opensaml.xmlsec.signature.KeyInfo;
|
||||
import org.opensaml.xmlsec.signature.Signature;
|
||||
import org.opensaml.xmlsec.signature.X509Data;
|
||||
import org.opensaml.xmlsec.signature.impl.KeyInfoBuilder;
|
||||
import org.opensaml.xmlsec.signature.impl.SignatureBuilder;
|
||||
import org.opensaml.xmlsec.signature.impl.X509DataBuilder;
|
||||
import org.opensaml.xmlsec.signature.support.SignatureConstants;
|
||||
import org.opensaml.xmlsec.signature.support.SignatureException;
|
||||
import org.opensaml.xmlsec.signature.support.SignatureValidator;
|
||||
import org.w3c.dom.Element;
|
||||
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
import io.supertokens.Main;
|
||||
import io.supertokens.config.Config;
|
||||
import io.supertokens.config.CoreConfig;
|
||||
import io.supertokens.featureflag.EE_FEATURES;
|
||||
import io.supertokens.featureflag.FeatureFlag;
|
||||
import io.supertokens.featureflag.exceptions.FeatureNotEnabledException;
|
||||
import io.supertokens.pluginInterface.Storage;
|
||||
import io.supertokens.pluginInterface.StorageUtils;
|
||||
import io.supertokens.pluginInterface.exceptions.StorageQueryException;
|
||||
import io.supertokens.pluginInterface.exceptions.StorageTransactionLogicException;
|
||||
import io.supertokens.pluginInterface.multitenancy.AppIdentifier;
|
||||
import io.supertokens.pluginInterface.multitenancy.TenantIdentifier;
|
||||
import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException;
|
||||
import io.supertokens.pluginInterface.saml.SAMLClaimsInfo;
|
||||
import io.supertokens.pluginInterface.saml.SAMLClient;
|
||||
import io.supertokens.pluginInterface.saml.SAMLRelayStateInfo;
|
||||
import io.supertokens.pluginInterface.saml.SAMLStorage;
|
||||
import io.supertokens.pluginInterface.saml.exception.DuplicateEntityIdException;
|
||||
import io.supertokens.saml.exceptions.IDPInitiatedLoginDisallowedException;
|
||||
import io.supertokens.saml.exceptions.InvalidClientException;
|
||||
import io.supertokens.saml.exceptions.InvalidCodeException;
|
||||
import io.supertokens.saml.exceptions.InvalidRelayStateException;
|
||||
import io.supertokens.saml.exceptions.MalformedSAMLMetadataXMLException;
|
||||
import io.supertokens.saml.exceptions.SAMLResponseVerificationFailedException;
|
||||
import net.shibboleth.utilities.java.support.xml.SerializeSupport;
|
||||
import net.shibboleth.utilities.java.support.xml.XMLParserException;
|
||||
|
||||
public class SAML {
|
||||
public static void checkForSAMLFeature(AppIdentifier appIdentifier, Main main)
|
||||
throws StorageQueryException, TenantOrAppNotFoundException, FeatureNotEnabledException {
|
||||
EE_FEATURES[] features = FeatureFlag.getInstance(main, appIdentifier).getEnabledFeatures();
|
||||
for (EE_FEATURES f : features) {
|
||||
if (f == EE_FEATURES.SAML) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
throw new FeatureNotEnabledException(
|
||||
"SAML feature is not enabled. Please subscribe to a SuperTokens core license key to enable this " +
|
||||
"feature.");
|
||||
}
|
||||
|
||||
public static SAMLClient createOrUpdateSAMLClient(
|
||||
Main main, TenantIdentifier tenantIdentifier, Storage storage,
|
||||
String clientId, String clientSecret, String defaultRedirectURI, JsonArray redirectURIs, String metadataXML, boolean allowIDPInitiatedLogin, boolean enableRequestSigning)
|
||||
throws MalformedSAMLMetadataXMLException, StorageQueryException, CertificateException,
|
||||
FeatureNotEnabledException, TenantOrAppNotFoundException, DuplicateEntityIdException {
|
||||
checkForSAMLFeature(tenantIdentifier.toAppIdentifier(), main);
|
||||
|
||||
SAMLStorage samlStorage = StorageUtils.getSAMLStorage(storage);
|
||||
|
||||
var metadata = loadIdpMetadata(metadataXML);
|
||||
String idpSsoUrl = null;
|
||||
for (var roleDescriptor : metadata.getRoleDescriptors()) {
|
||||
if (roleDescriptor instanceof IDPSSODescriptor) {
|
||||
IDPSSODescriptor idpDescriptor = (IDPSSODescriptor) roleDescriptor;
|
||||
for (SingleSignOnService ssoService : idpDescriptor.getSingleSignOnServices()) {
|
||||
if (SAMLConstants.SAML2_REDIRECT_BINDING_URI.equals(ssoService.getBinding())) {
|
||||
idpSsoUrl = ssoService.getLocation();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (idpSsoUrl == null) {
|
||||
throw new MalformedSAMLMetadataXMLException();
|
||||
}
|
||||
|
||||
String idpSigningCertificate = extractIdpSigningCertificate(metadata);
|
||||
getCertificateFromString(idpSigningCertificate); // checking validity
|
||||
|
||||
String idpEntityId = metadata.getEntityID();
|
||||
SAMLClient client = new SAMLClient(clientId, clientSecret, idpSsoUrl, redirectURIs, defaultRedirectURI, idpEntityId, idpSigningCertificate, allowIDPInitiatedLogin, enableRequestSigning);
|
||||
return samlStorage.createOrUpdateSAMLClient(tenantIdentifier, client);
|
||||
}
|
||||
|
||||
public static List<SAMLClient> getClients(TenantIdentifier tenantIdentifier, Storage storage) throws StorageQueryException {
|
||||
SAMLStorage samlStorage = StorageUtils.getSAMLStorage(storage);
|
||||
return samlStorage.getSAMLClients(tenantIdentifier);
|
||||
}
|
||||
|
||||
public static SAMLClient getClient(TenantIdentifier tenantIdentifier, Storage storage, String clientId) throws StorageQueryException {
|
||||
SAMLStorage samlStorage = StorageUtils.getSAMLStorage(storage);
|
||||
return samlStorage.getSAMLClient(tenantIdentifier, clientId);
|
||||
}
|
||||
|
||||
public static boolean removeSAMLClient(TenantIdentifier tenantIdentifier, Storage storage, String clientId) throws StorageQueryException {
|
||||
SAMLStorage samlStorage = StorageUtils.getSAMLStorage(storage);
|
||||
return samlStorage.removeSAMLClient(tenantIdentifier, clientId);
|
||||
}
|
||||
|
||||
private static String extractIdpSigningCertificate(EntityDescriptor idpMetadata) {
|
||||
for (var roleDescriptor : idpMetadata.getRoleDescriptors()) {
|
||||
if (roleDescriptor instanceof IDPSSODescriptor) {
|
||||
IDPSSODescriptor idpDescriptor = (IDPSSODescriptor) roleDescriptor;
|
||||
for (org.opensaml.saml.saml2.metadata.KeyDescriptor keyDescriptor : idpDescriptor.getKeyDescriptors()) {
|
||||
if (keyDescriptor.getUse() == null ||
|
||||
"SIGNING".equals(keyDescriptor.getUse().toString())) {
|
||||
org.opensaml.xmlsec.signature.KeyInfo keyInfo = keyDescriptor.getKeyInfo();
|
||||
if (keyInfo != null) {
|
||||
for (org.opensaml.xmlsec.signature.X509Data x509Data : keyInfo.getX509Datas()) {
|
||||
for (org.opensaml.xmlsec.signature.X509Certificate x509Cert : x509Data.getX509Certificates()) {
|
||||
try {
|
||||
String certString = x509Cert.getValue();
|
||||
if (certString != null && !certString.trim().isEmpty()) {
|
||||
certString = certString.replaceAll("\\s", "");
|
||||
return certString;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// Continue to next certificate if this one fails
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
public static String createRedirectURL(Main main, TenantIdentifier tenantIdentifier, Storage storage,
|
||||
String clientId, String redirectURI, String state, String acsURL)
|
||||
throws StorageQueryException, InvalidClientException, TenantOrAppNotFoundException,
|
||||
CertificateEncodingException, FeatureNotEnabledException {
|
||||
checkForSAMLFeature(tenantIdentifier.toAppIdentifier(), main);
|
||||
SAMLStorage samlStorage = StorageUtils.getSAMLStorage(storage);
|
||||
CoreConfig config = Config.getConfig(tenantIdentifier, main);
|
||||
|
||||
SAMLClient client = samlStorage.getSAMLClient(tenantIdentifier, clientId);
|
||||
|
||||
if (client == null) {
|
||||
throw new InvalidClientException();
|
||||
}
|
||||
|
||||
boolean redirectURIOk = false;
|
||||
for (JsonElement rUri : client.redirectURIs) {
|
||||
if (rUri.getAsString().equals(redirectURI)) {
|
||||
redirectURIOk = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!redirectURIOk) {
|
||||
throw new InvalidClientException();
|
||||
}
|
||||
|
||||
String idpSsoUrl = client.ssoLoginURL;
|
||||
AuthnRequest request = buildAuthnRequest(
|
||||
main,
|
||||
tenantIdentifier.toAppIdentifier(),
|
||||
idpSsoUrl,
|
||||
config.getSAMLSPEntityID(), acsURL,
|
||||
client.enableRequestSigning);
|
||||
String samlRequest = deflateAndBase64RedirectMessage(request);
|
||||
String relayState = UUID.randomUUID().toString();
|
||||
|
||||
samlStorage.saveRelayStateInfo(tenantIdentifier, new SAMLRelayStateInfo(relayState, clientId, state, redirectURI), config.getSAMLRelayStateValidity());
|
||||
|
||||
return idpSsoUrl + "?SAMLRequest=" + samlRequest + "&RelayState=" + URLEncoder.encode(relayState, StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
public static EntityDescriptor loadIdpMetadata(String metadataXML) throws MalformedSAMLMetadataXMLException {
|
||||
try {
|
||||
byte[] bytes = metadataXML.getBytes(StandardCharsets.UTF_8);
|
||||
try (InputStream inputStream = new java.io.ByteArrayInputStream(bytes)) {
|
||||
XMLObject xmlObject = XMLObjectSupport.unmarshallFromInputStream(
|
||||
XMLObjectProviderRegistrySupport.getParserPool(), inputStream);
|
||||
if (xmlObject instanceof EntityDescriptor) {
|
||||
return (EntityDescriptor) xmlObject;
|
||||
} else {
|
||||
throw new RuntimeException("Expected EntityDescriptor but got: " + xmlObject.getClass());
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new MalformedSAMLMetadataXMLException();
|
||||
}
|
||||
}
|
||||
|
||||
private static AuthnRequest buildAuthnRequest(Main main, AppIdentifier appIdentifier, String idpSsoUrl, String spEntityId, String acsUrl, boolean enableRequestSigning)
|
||||
throws TenantOrAppNotFoundException, StorageQueryException, CertificateEncodingException {
|
||||
XMLObjectBuilderFactory builders = XMLObjectProviderRegistrySupport.getBuilderFactory();
|
||||
|
||||
AuthnRequest authnRequest = (AuthnRequest) builders
|
||||
.<AuthnRequest>getBuilder(AuthnRequest.DEFAULT_ELEMENT_NAME)
|
||||
.buildObject(AuthnRequest.DEFAULT_ELEMENT_NAME);
|
||||
authnRequest.setID("_" + UUID.randomUUID());
|
||||
authnRequest.setIssueInstant(Instant.now());
|
||||
authnRequest.setVersion(SAMLVersion.VERSION_20);
|
||||
authnRequest.setDestination(idpSsoUrl);
|
||||
authnRequest.setProtocolBinding(SAMLConstants.SAML2_POST_BINDING_URI);
|
||||
|
||||
Issuer issuer = (Issuer) builders.getBuilder(Issuer.DEFAULT_ELEMENT_NAME)
|
||||
.buildObject(Issuer.DEFAULT_ELEMENT_NAME);
|
||||
issuer.setValue(spEntityId);
|
||||
authnRequest.setIssuer(issuer);
|
||||
|
||||
NameIDPolicy nameIDPolicy = (NameIDPolicy) builders.getBuilder(NameIDPolicy.DEFAULT_ELEMENT_NAME)
|
||||
.buildObject(NameIDPolicy.DEFAULT_ELEMENT_NAME);
|
||||
nameIDPolicy.setAllowCreate(true);
|
||||
authnRequest.setNameIDPolicy(nameIDPolicy);
|
||||
|
||||
RequestedAuthnContext rac = (RequestedAuthnContext) builders.getBuilder(RequestedAuthnContext.DEFAULT_ELEMENT_NAME)
|
||||
.buildObject(RequestedAuthnContext.DEFAULT_ELEMENT_NAME);
|
||||
rac.setComparison(org.opensaml.saml.saml2.core.AuthnContextComparisonTypeEnumeration.EXACT);
|
||||
AuthnContextClassRef classRef = (AuthnContextClassRef) builders.getBuilder(AuthnContextClassRef.DEFAULT_ELEMENT_NAME)
|
||||
.buildObject(AuthnContextClassRef.DEFAULT_ELEMENT_NAME);
|
||||
classRef.setURI(AuthnContext.PASSWORD_AUTHN_CTX);
|
||||
rac.getAuthnContextClassRefs().add(classRef);
|
||||
authnRequest.setRequestedAuthnContext(rac);
|
||||
|
||||
authnRequest.setAssertionConsumerServiceURL(acsUrl);
|
||||
|
||||
if (enableRequestSigning) {
|
||||
Signature signature = new SignatureBuilder().buildObject();
|
||||
signature.setSignatureAlgorithm(SignatureConstants.ALGO_ID_SIGNATURE_RSA_SHA256);
|
||||
signature.setCanonicalizationAlgorithm(SignatureConstants.ALGO_ID_C14N_EXCL_OMIT_COMMENTS);
|
||||
|
||||
// Create KeyInfo
|
||||
KeyInfo keyInfo = new KeyInfoBuilder().buildObject();
|
||||
X509Data x509Data = new X509DataBuilder().buildObject();
|
||||
org.opensaml.xmlsec.signature.X509Certificate x509CertElement = new org.opensaml.xmlsec.signature.impl.X509CertificateBuilder().buildObject();
|
||||
|
||||
X509Certificate spCertificate = SAMLCertificate.getInstance(appIdentifier, main).getCertificate();
|
||||
String certString = java.util.Base64.getEncoder().encodeToString(spCertificate.getEncoded());
|
||||
x509CertElement.setValue(certString);
|
||||
x509Data.getX509Certificates().add(x509CertElement);
|
||||
keyInfo.getX509Datas().add(x509Data);
|
||||
signature.setKeyInfo(keyInfo);
|
||||
|
||||
authnRequest.setSignature(signature);
|
||||
}
|
||||
|
||||
return authnRequest;
|
||||
}
|
||||
|
||||
private static String deflateAndBase64RedirectMessage(XMLObject xmlObject) {
|
||||
try {
|
||||
String xml = toXmlString(xmlObject);
|
||||
byte[] xmlBytes = xml.getBytes(StandardCharsets.UTF_8);
|
||||
|
||||
// DEFLATE compression as per SAML Redirect binding spec
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
DeflaterOutputStream dos = new DeflaterOutputStream(baos, new Deflater(Deflater.DEFLATED, true));
|
||||
dos.write(xmlBytes);
|
||||
dos.close();
|
||||
|
||||
byte[] deflated = baos.toByteArray();
|
||||
String base64 = java.util.Base64.getEncoder().encodeToString(deflated);
|
||||
return URLEncoder.encode(base64, StandardCharsets.UTF_8);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Failed to deflate SAML message", e);
|
||||
}
|
||||
}
|
||||
|
||||
private static String toXmlString(XMLObject xmlObject) {
|
||||
try {
|
||||
Element el = XMLObjectSupport.marshall(xmlObject);
|
||||
return SerializeSupport.nodeToString(el);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to serialize XML", e);
|
||||
}
|
||||
}
|
||||
|
||||
private static Response parseSamlResponse(String samlResponseBase64)
|
||||
throws IOException, XMLParserException, UnmarshallingException {
|
||||
byte[] decoded = java.util.Base64.getDecoder().decode(samlResponseBase64);
|
||||
String xml = new String(decoded, StandardCharsets.UTF_8);
|
||||
|
||||
try (InputStream inputStream = new ByteArrayInputStream(xml.getBytes(StandardCharsets.UTF_8))) {
|
||||
return (Response) XMLObjectSupport.unmarshallFromInputStream(
|
||||
XMLObjectProviderRegistrySupport.getParserPool(), inputStream);
|
||||
}
|
||||
}
|
||||
|
||||
private static void verifySamlResponseSignature(Response samlResponse, X509Certificate idpCertificate)
|
||||
throws SignatureException {
|
||||
Signature responseSignature = samlResponse.getSignature();
|
||||
if (responseSignature != null) {
|
||||
Credential credential = CredentialSupport.getSimpleCredential(idpCertificate, null);
|
||||
SignatureValidator.validate(responseSignature, credential);
|
||||
return;
|
||||
}
|
||||
|
||||
boolean foundSignedAssertion = false;
|
||||
for (Assertion assertion : samlResponse.getAssertions()) {
|
||||
Signature assertionSignature = assertion.getSignature();
|
||||
if (assertionSignature != null) {
|
||||
Credential credential = CredentialSupport.getSimpleCredential(idpCertificate, null);
|
||||
SignatureValidator.validate(assertionSignature, credential);
|
||||
foundSignedAssertion = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!foundSignedAssertion) {
|
||||
throw new RuntimeException("Neither SAML Response nor any Assertion is signed");
|
||||
}
|
||||
}
|
||||
|
||||
private static void validateSamlResponseTimestamps(Response samlResponse) throws SAMLResponseVerificationFailedException {
|
||||
Instant now = Instant.now();
|
||||
|
||||
// Validate response issue instant (should be recent)
|
||||
if (samlResponse.getIssueInstant() != null) {
|
||||
Instant responseTime = samlResponse.getIssueInstant();
|
||||
// Allow 5 minutes clock skew
|
||||
if (responseTime.isAfter(now.plusSeconds(300)) || responseTime.isBefore(now.minusSeconds(300))) {
|
||||
throw new SAMLResponseVerificationFailedException();
|
||||
}
|
||||
}
|
||||
|
||||
// Validate assertion timestamps
|
||||
for (Assertion assertion : samlResponse.getAssertions()) {
|
||||
// Check NotBefore
|
||||
if (assertion.getConditions() != null && assertion.getConditions().getNotBefore() != null) {
|
||||
if (now.isBefore(assertion.getConditions().getNotBefore())) {
|
||||
throw new SAMLResponseVerificationFailedException();
|
||||
}
|
||||
}
|
||||
|
||||
// Check NotOnOrAfter
|
||||
if (assertion.getConditions() != null && assertion.getConditions().getNotOnOrAfter() != null) {
|
||||
if (now.isAfter(assertion.getConditions().getNotOnOrAfter())) {
|
||||
throw new SAMLResponseVerificationFailedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static String handleCallback(Main main, TenantIdentifier tenantIdentifier, Storage storage, String samlResponse, String relayState)
|
||||
throws StorageQueryException, XMLParserException, IOException, UnmarshallingException,
|
||||
CertificateException, InvalidRelayStateException, SAMLResponseVerificationFailedException,
|
||||
InvalidClientException, IDPInitiatedLoginDisallowedException, TenantOrAppNotFoundException,
|
||||
FeatureNotEnabledException {
|
||||
checkForSAMLFeature(tenantIdentifier.toAppIdentifier(), main);
|
||||
|
||||
SAMLStorage samlStorage = StorageUtils.getSAMLStorage(storage);
|
||||
CoreConfig config = Config.getConfig(tenantIdentifier, main);
|
||||
|
||||
SAMLClient client = null;
|
||||
Response response = parseSamlResponse(samlResponse);
|
||||
String state = null;
|
||||
String redirectURI = null;
|
||||
|
||||
if (relayState != null && !relayState.isEmpty()) {
|
||||
// sp initiated
|
||||
var relayStateInfo = samlStorage.getRelayStateInfo(tenantIdentifier, relayState);
|
||||
if (relayStateInfo == null) {
|
||||
throw new InvalidRelayStateException();
|
||||
}
|
||||
|
||||
String clientId = relayStateInfo.clientId;
|
||||
client = samlStorage.getSAMLClient(tenantIdentifier, clientId);
|
||||
state = relayStateInfo.state;
|
||||
redirectURI = relayStateInfo.redirectURI;
|
||||
} else {
|
||||
// idp initiated
|
||||
String idpEntityId = response.getIssuer().getValue();
|
||||
client = samlStorage.getSAMLClientByIDPEntityId(tenantIdentifier, idpEntityId);
|
||||
redirectURI = client.defaultRedirectURI;
|
||||
|
||||
if (!client.allowIDPInitiatedLogin) {
|
||||
throw new IDPInitiatedLoginDisallowedException();
|
||||
}
|
||||
}
|
||||
|
||||
if (client == null) {
|
||||
throw new InvalidClientException();
|
||||
}
|
||||
|
||||
// SAML verification
|
||||
X509Certificate idpSigningCertificate = getCertificateFromString(client.idpSigningCertificate);
|
||||
try {
|
||||
verifySamlResponseSignature(response, idpSigningCertificate);
|
||||
} catch (SignatureException e) {
|
||||
throw new SAMLResponseVerificationFailedException();
|
||||
}
|
||||
validateSamlResponseTimestamps(response);
|
||||
validateSamlResponseAudience(response, config.getSAMLSPEntityID());
|
||||
|
||||
var claims = extractAllClaims(response);
|
||||
|
||||
String code = UUID.randomUUID().toString();
|
||||
samlStorage.saveSAMLClaims(tenantIdentifier, client.clientId, code, claims, config.getSAMLClaimsValidity());
|
||||
|
||||
try {
|
||||
java.net.URI uri = new java.net.URI(redirectURI);
|
||||
String query = uri.getQuery();
|
||||
StringBuilder newQuery = new StringBuilder();
|
||||
if (query != null && !query.isEmpty()) {
|
||||
newQuery.append(query).append("&");
|
||||
}
|
||||
newQuery.append("code=").append(java.net.URLEncoder.encode(code, java.nio.charset.StandardCharsets.UTF_8));
|
||||
if (state != null) {
|
||||
newQuery.append("&state=").append(java.net.URLEncoder.encode(state, java.nio.charset.StandardCharsets.UTF_8));
|
||||
}
|
||||
java.net.URI newUri = new java.net.URI(
|
||||
uri.getScheme(),
|
||||
uri.getAuthority(),
|
||||
uri.getPath(),
|
||||
newQuery.toString(),
|
||||
uri.getFragment()
|
||||
);
|
||||
return newUri.toString();
|
||||
} catch (URISyntaxException e) {
|
||||
throw new IllegalStateException("should never happen", e);
|
||||
}
|
||||
}
|
||||
|
||||
private static void validateSamlResponseAudience(Response samlResponse, String expectedAudience)
|
||||
throws SAMLResponseVerificationFailedException {
|
||||
boolean audienceMatched = false;
|
||||
|
||||
for (Assertion assertion : samlResponse.getAssertions()) {
|
||||
Conditions conditions = assertion.getConditions();
|
||||
if (conditions == null) {
|
||||
continue;
|
||||
}
|
||||
java.util.List<AudienceRestriction> restrictions = conditions.getAudienceRestrictions();
|
||||
if (restrictions == null || restrictions.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
for (AudienceRestriction ar : restrictions) {
|
||||
java.util.List<Audience> audiences = ar.getAudiences();
|
||||
if (audiences == null || audiences.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
for (Audience aud : audiences) {
|
||||
if (expectedAudience.equals(aud.getURI())) {
|
||||
audienceMatched = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (audienceMatched) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (audienceMatched) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!audienceMatched) {
|
||||
throw new SAMLResponseVerificationFailedException();
|
||||
}
|
||||
}
|
||||
|
||||
private static JsonObject extractAllClaims(Response samlResponse) {
|
||||
JsonObject claims = new JsonObject();
|
||||
|
||||
for (Assertion assertion : samlResponse.getAssertions()) {
|
||||
// Extract NameID as a claim
|
||||
Subject subject = assertion.getSubject();
|
||||
if (subject != null && subject.getNameID() != null) {
|
||||
String nameId = subject.getNameID().getValue();
|
||||
String nameIdFormat = subject.getNameID().getFormat();
|
||||
JsonArray nameIdArr = new JsonArray();
|
||||
nameIdArr.add(nameId);
|
||||
claims.add("NameID", nameIdArr);
|
||||
if (nameIdFormat != null) {
|
||||
JsonArray nameIdFormatArr = new JsonArray();
|
||||
nameIdFormatArr.add(nameIdFormat);
|
||||
claims.add("NameIDFormat", nameIdFormatArr);
|
||||
}
|
||||
}
|
||||
|
||||
// Extract all attributes from AttributeStatements
|
||||
for (AttributeStatement attributeStatement : assertion.getAttributeStatements()) {
|
||||
for (Attribute attribute : attributeStatement.getAttributes()) {
|
||||
String attributeName = attribute.getName();
|
||||
JsonArray attributeValues = new JsonArray();
|
||||
|
||||
for (XMLObject attributeValue : attribute.getAttributeValues()) {
|
||||
if (attributeValue instanceof org.opensaml.saml.saml2.core.AttributeValue) {
|
||||
org.opensaml.saml.saml2.core.AttributeValue attrValue =
|
||||
(org.opensaml.saml.saml2.core.AttributeValue) attributeValue;
|
||||
|
||||
if (attrValue.getDOM() != null) {
|
||||
String value = attrValue.getDOM().getTextContent();
|
||||
if (value != null && !value.trim().isEmpty()) {
|
||||
attributeValues.add(value.trim());
|
||||
}
|
||||
} else if (attrValue.getTextContent() != null) {
|
||||
String value = attrValue.getTextContent();
|
||||
if (!value.trim().isEmpty()) {
|
||||
attributeValues.add(value.trim());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!attributeValues.isEmpty()) {
|
||||
claims.add(attributeName, attributeValues);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return claims;
|
||||
}
|
||||
|
||||
private static X509Certificate getCertificateFromString(String certString) throws CertificateException {
|
||||
byte[] certBytes = java.util.Base64.getDecoder().decode(certString);
|
||||
java.security.cert.CertificateFactory certFactory =
|
||||
java.security.cert.CertificateFactory.getInstance("X.509");
|
||||
return (X509Certificate) certFactory.generateCertificate(
|
||||
new ByteArrayInputStream(certBytes));
|
||||
}
|
||||
|
||||
public static JsonObject getUserInfo(Main main, TenantIdentifier tenantIdentifier, Storage storage, String accessToken, String clientId, boolean isLegacy)
|
||||
throws TenantOrAppNotFoundException, StorageQueryException,
|
||||
StorageTransactionLogicException, InvalidCodeException, FeatureNotEnabledException {
|
||||
|
||||
checkForSAMLFeature(tenantIdentifier.toAppIdentifier(), main);
|
||||
|
||||
SAMLStorage samlStorage = StorageUtils.getSAMLStorage(storage);
|
||||
|
||||
SAMLClaimsInfo claimsInfo = samlStorage.getSAMLClaimsAndRemoveCode(tenantIdentifier, accessToken);
|
||||
if (claimsInfo == null) {
|
||||
throw new InvalidCodeException();
|
||||
}
|
||||
|
||||
if (clientId != null) {
|
||||
if (!clientId.equals(claimsInfo.clientId)) {
|
||||
throw new InvalidCodeException();
|
||||
}
|
||||
}
|
||||
|
||||
String sub = null;
|
||||
String email = null;
|
||||
|
||||
JsonObject claims = claimsInfo.claims;
|
||||
|
||||
if (claims.has("NameID")) {
|
||||
sub = claims.getAsJsonArray("NameID").get(0).getAsString();
|
||||
} else if (claims.has("http://schemas.microsoft.com/identity/claims/objectidentifier")) {
|
||||
sub = claims.getAsJsonArray("http://schemas.microsoft.com/identity/claims/objectidentifier")
|
||||
.get(0).getAsString();
|
||||
} else if (claims.has("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name")) {
|
||||
sub = claims.getAsJsonArray("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name")
|
||||
.get(0).getAsString();
|
||||
}
|
||||
|
||||
if (claims.has("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress")) {
|
||||
email = claims.getAsJsonArray("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress")
|
||||
.get(0).getAsString();
|
||||
} else if (claims.has("NameID")) {
|
||||
String nameIdValue = claims.getAsJsonArray("NameID").get(0).getAsString();
|
||||
if (nameIdValue.contains("@")) {
|
||||
email = nameIdValue;
|
||||
}
|
||||
}
|
||||
|
||||
JsonObject payload = new JsonObject();
|
||||
payload.add("claims", claims);
|
||||
payload.addProperty(isLegacy ? "id" : "sub", sub);
|
||||
payload.addProperty("email", email);
|
||||
payload.addProperty("aud", claimsInfo.clientId);
|
||||
|
||||
return payload;
|
||||
}
|
||||
|
||||
public static String getLegacyACSURL(Main main, AppIdentifier appIdentifier) throws TenantOrAppNotFoundException {
|
||||
CoreConfig config = Config.getConfig(appIdentifier.getAsPublicTenantIdentifier(), main);
|
||||
return config.getSAMLLegacyACSURL();
|
||||
}
|
||||
|
||||
public static String getMetadataXML(Main main, TenantIdentifier tenantIdentifier)
|
||||
throws TenantOrAppNotFoundException, StorageQueryException, FeatureNotEnabledException {
|
||||
checkForSAMLFeature(tenantIdentifier.toAppIdentifier(), main);
|
||||
|
||||
SAMLCertificate certificate = SAMLCertificate.getInstance(tenantIdentifier.toAppIdentifier(), main);
|
||||
CoreConfig config = Config.getConfig(tenantIdentifier, main);
|
||||
String spEntityId = config.getSAMLSPEntityID();
|
||||
try {
|
||||
X509Certificate cert = certificate.getCertificate();
|
||||
String certString = java.util.Base64.getEncoder().encodeToString(cert.getEncoded());
|
||||
|
||||
String validUntil = java.time.format.DateTimeFormatter.ISO_INSTANT.format(cert.getNotAfter().toInstant());
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>");
|
||||
sb.append("<md:EntityDescriptor xmlns:md=\"urn:oasis:names:tc:SAML:2.0:metadata\" entityID=\"")
|
||||
.append(escapeXml(spEntityId)).append("\" validUntil=\"")
|
||||
.append(escapeXml(validUntil)).append("\">");
|
||||
sb.append("<md:SPSSODescriptor protocolSupportEnumeration=\"urn:oasis:names:tc:SAML:2.0:protocol\">");
|
||||
sb.append("<md:KeyDescriptor use=\"signing\">");
|
||||
sb.append("<ds:KeyInfo xmlns:ds=\"http://www.w3.org/2000/09/xmldsig#\">");
|
||||
sb.append("<ds:X509Data>");
|
||||
sb.append("<ds:X509Certificate>").append(certString).append("</ds:X509Certificate>");
|
||||
sb.append("</ds:X509Data>");
|
||||
sb.append("</ds:KeyInfo>");
|
||||
sb.append("</md:KeyDescriptor>");
|
||||
sb.append("<md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</md:NameIDFormat>");
|
||||
sb.append("</md:SPSSODescriptor>");
|
||||
sb.append("</md:EntityDescriptor>");
|
||||
|
||||
return sb.toString();
|
||||
} catch (Exception e) {
|
||||
throw new IllegalStateException("Failed to generate SP metadata", e);
|
||||
}
|
||||
}
|
||||
|
||||
private static String escapeXml(String input) {
|
||||
if (input == null) {
|
||||
return "";
|
||||
}
|
||||
String result = input;
|
||||
result = result.replace("&", "&");
|
||||
result = result.replace("\"", """);
|
||||
result = result.replace("<", "<");
|
||||
result = result.replace(">", ">");
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* Copyright (c) 2025, VRAI Labs and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* This software is licensed under the Apache License, Version 2.0 (the
|
||||
* "License") as published by the Apache Software Foundation.
|
||||
*
|
||||
* You may not use this file except in compliance with the License. You may
|
||||
* obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package io.supertokens.saml;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.opensaml.core.config.InitializationException;
|
||||
import org.opensaml.core.config.InitializationService;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import ch.qos.logback.classic.Level;
|
||||
import ch.qos.logback.classic.Logger;
|
||||
|
||||
public class SAMLBootstrap {
|
||||
private static volatile boolean initialized = false;
|
||||
|
||||
private SAMLBootstrap() {}
|
||||
|
||||
public static void initialize() {
|
||||
if (initialized) {
|
||||
return;
|
||||
}
|
||||
synchronized (SAMLBootstrap.class) {
|
||||
if (initialized) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
InitializationService.initialize();
|
||||
initialized = true;
|
||||
} catch (InitializationException e) {
|
||||
throw new RuntimeException("Failed to initialize OpenSAML", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,315 @@
|
|||
/*
|
||||
* Copyright (c) 2025, VRAI Labs and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* This software is licensed under the Apache License, Version 2.0 (the
|
||||
* "License") as published by the Apache Software Foundation.
|
||||
*
|
||||
* You may not use this file except in compliance with the License. You may
|
||||
* obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package io.supertokens.saml;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.security.KeyFactory;
|
||||
import java.security.KeyPair;
|
||||
import java.security.KeyPairGenerator;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
import java.security.SecureRandom;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.CertificateFactory;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.security.spec.PKCS8EncodedKeySpec;
|
||||
import java.security.spec.X509EncodedKeySpec;
|
||||
import java.util.Base64;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.bouncycastle.asn1.x500.X500Name;
|
||||
import org.bouncycastle.asn1.x509.BasicConstraints;
|
||||
import org.bouncycastle.asn1.x509.Extension;
|
||||
import org.bouncycastle.asn1.x509.KeyUsage;
|
||||
import org.bouncycastle.cert.CertIOException;
|
||||
import org.bouncycastle.cert.X509CertificateHolder;
|
||||
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
|
||||
import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
|
||||
import org.bouncycastle.operator.ContentSigner;
|
||||
import org.bouncycastle.operator.OperatorCreationException;
|
||||
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
|
||||
|
||||
import io.supertokens.Main;
|
||||
import io.supertokens.ResourceDistributor;
|
||||
import io.supertokens.output.Logging;
|
||||
import io.supertokens.pluginInterface.KeyValueInfo;
|
||||
import io.supertokens.pluginInterface.exceptions.StorageQueryException;
|
||||
import io.supertokens.pluginInterface.exceptions.StorageTransactionLogicException;
|
||||
import io.supertokens.pluginInterface.multitenancy.AppIdentifier;
|
||||
import io.supertokens.pluginInterface.multitenancy.TenantIdentifier;
|
||||
import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException;
|
||||
import io.supertokens.pluginInterface.sqlStorage.SQLStorage;
|
||||
import io.supertokens.storageLayer.StorageLayer;
|
||||
|
||||
public class SAMLCertificate extends ResourceDistributor.SingletonResource {
|
||||
private static final String RESOURCE_KEY = "io.supertokens.saml.SAMLCertificate";
|
||||
private final Main main;
|
||||
private final AppIdentifier appIdentifier;
|
||||
|
||||
private static final String SAML_KEY_PAIR_NAME = "saml_key_pair";
|
||||
private static final String SAML_CERTIFICATE_NAME = "saml_certificate";
|
||||
|
||||
private KeyPair spKeyPair = null;
|
||||
private X509Certificate spCertificate = null;
|
||||
|
||||
private SAMLCertificate(AppIdentifier appIdentifier, Main main) throws
|
||||
TenantOrAppNotFoundException {
|
||||
this.main = main;
|
||||
this.appIdentifier = appIdentifier;
|
||||
try {
|
||||
if (!Main.isTesting) {
|
||||
// Creation of new certificate is slow, not really necessary to create one for each test
|
||||
this.getCertificate();
|
||||
}
|
||||
} catch (StorageQueryException | TenantOrAppNotFoundException e) {
|
||||
Logging.error(main, appIdentifier.getAsPublicTenantIdentifier(), "Error while fetching SAML key and certificate",
|
||||
false, e);
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized X509Certificate getCertificate()
|
||||
throws StorageQueryException, TenantOrAppNotFoundException {
|
||||
if (this.spCertificate == null || this.spCertificate.getNotAfter().before(new Date())) {
|
||||
maybeGenerateNewCertificateAndUpdateInDb();
|
||||
}
|
||||
|
||||
return this.spCertificate;
|
||||
}
|
||||
|
||||
private void maybeGenerateNewCertificateAndUpdateInDb() throws TenantOrAppNotFoundException {
|
||||
SQLStorage storage = (SQLStorage) StorageLayer.getStorage(
|
||||
this.appIdentifier.getAsPublicTenantIdentifier(), main);
|
||||
|
||||
try {
|
||||
storage.startTransaction(con -> {
|
||||
KeyValueInfo keyPairInfo = storage.getKeyValue_Transaction(this.appIdentifier.getAsPublicTenantIdentifier(), con, SAML_KEY_PAIR_NAME);
|
||||
KeyValueInfo certInfo = storage.getKeyValue_Transaction(this.appIdentifier.getAsPublicTenantIdentifier(), con, SAML_CERTIFICATE_NAME);
|
||||
|
||||
if (keyPairInfo == null || certInfo == null) {
|
||||
try {
|
||||
generateNewCertificate();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
try {
|
||||
String keyPairStr = serializeKeyPair(spKeyPair);
|
||||
String certStr = serializeCertificate(spCertificate);
|
||||
keyPairInfo = new KeyValueInfo(keyPairStr);
|
||||
certInfo = new KeyValueInfo(certStr);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Failed to serialize key pair or certificate", e);
|
||||
}
|
||||
storage.setKeyValue_Transaction(this.appIdentifier.getAsPublicTenantIdentifier(), con, SAML_KEY_PAIR_NAME, keyPairInfo);
|
||||
storage.setKeyValue_Transaction(this.appIdentifier.getAsPublicTenantIdentifier(), con, SAML_CERTIFICATE_NAME, certInfo);
|
||||
}
|
||||
|
||||
String keyPairStr = keyPairInfo.value;
|
||||
String certStr = certInfo.value;
|
||||
|
||||
try {
|
||||
this.spKeyPair = deserializeKeyPair(keyPairStr);
|
||||
this.spCertificate = deserializeCertificate(certStr);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to deserialize key pair or certificate", e);
|
||||
}
|
||||
|
||||
// If the certificate has expired, generate and persist a new one
|
||||
if (this.spCertificate.getNotAfter().before(new Date())) {
|
||||
try {
|
||||
generateNewCertificate();
|
||||
String newKeyPairStr = serializeKeyPair(spKeyPair);
|
||||
String newCertStr = serializeCertificate(spCertificate);
|
||||
KeyValueInfo newKeyPairInfo = new KeyValueInfo(newKeyPairStr);
|
||||
KeyValueInfo newCertInfo = new KeyValueInfo(newCertStr);
|
||||
storage.setKeyValue_Transaction(this.appIdentifier.getAsPublicTenantIdentifier(), con, SAML_KEY_PAIR_NAME, newKeyPairInfo);
|
||||
storage.setKeyValue_Transaction(this.appIdentifier.getAsPublicTenantIdentifier(), con, SAML_CERTIFICATE_NAME, newCertInfo);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to regenerate expired certificate", e);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
} catch (StorageTransactionLogicException | StorageQueryException e) {
|
||||
throw new RuntimeException("Storage error", e);
|
||||
}
|
||||
}
|
||||
|
||||
void generateNewCertificate()
|
||||
throws NoSuchAlgorithmException, CertificateException, OperatorCreationException, CertIOException {
|
||||
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
|
||||
keyGen.initialize(4096);
|
||||
spKeyPair = keyGen.generateKeyPair();
|
||||
spCertificate = generateSelfSignedCertificate();
|
||||
}
|
||||
|
||||
private X509Certificate generateSelfSignedCertificate()
|
||||
throws CertIOException, OperatorCreationException, CertificateException {
|
||||
// Create a production-ready self-signed X.509 certificate using BouncyCastle
|
||||
Date notBefore = new Date();
|
||||
Date notAfter = new Date(notBefore.getTime() + 10 * 365L * 24 * 60 * 60 * 1000); // 10 year validity
|
||||
|
||||
// Create the certificate subject and issuer (same for self-signed)
|
||||
X500Name subject = new X500Name("CN=SAML-SP, O=SuperTokens, C=US");
|
||||
X500Name issuer = subject; // Self-signed
|
||||
|
||||
// Generate a random serial number (128 bits for good uniqueness)
|
||||
SecureRandom random = new SecureRandom();
|
||||
java.math.BigInteger serialNumber = new java.math.BigInteger(128, random);
|
||||
|
||||
// Create the certificate builder
|
||||
JcaX509v3CertificateBuilder certBuilder = new JcaX509v3CertificateBuilder(
|
||||
issuer,
|
||||
serialNumber,
|
||||
notBefore,
|
||||
notAfter,
|
||||
subject,
|
||||
spKeyPair.getPublic()
|
||||
);
|
||||
|
||||
// Add extensions for proper SAML usage
|
||||
// Key Usage: digitalSignature and keyEncipherment
|
||||
KeyUsage keyUsage = new KeyUsage(KeyUsage.digitalSignature | KeyUsage.keyEncipherment);
|
||||
certBuilder.addExtension(Extension.keyUsage, true, keyUsage);
|
||||
|
||||
// Basic Constraints: not a CA
|
||||
BasicConstraints basicConstraints = new BasicConstraints(false);
|
||||
certBuilder.addExtension(Extension.basicConstraints, true, basicConstraints);
|
||||
|
||||
// Create the content signer
|
||||
ContentSigner contentSigner = new JcaContentSignerBuilder("SHA256withRSA")
|
||||
.build(spKeyPair.getPrivate());
|
||||
|
||||
// Build the certificate
|
||||
X509CertificateHolder certHolder = certBuilder.build(contentSigner);
|
||||
|
||||
// Convert to standard X509Certificate
|
||||
JcaX509CertificateConverter converter = new JcaX509CertificateConverter();
|
||||
return converter.getCertificate(certHolder);
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes a KeyPair to a Base64 encoded string format
|
||||
*/
|
||||
private String serializeKeyPair(KeyPair keyPair) throws IOException {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
|
||||
// Write private key
|
||||
byte[] privateKeyBytes = keyPair.getPrivate().getEncoded();
|
||||
baos.write(Base64.getEncoder().encode(privateKeyBytes));
|
||||
baos.write('\n');
|
||||
|
||||
// Write public key
|
||||
byte[] publicKeyBytes = keyPair.getPublic().getEncoded();
|
||||
baos.write(Base64.getEncoder().encode(publicKeyBytes));
|
||||
|
||||
return baos.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserializes a KeyPair from a Base64 encoded string format
|
||||
*/
|
||||
private KeyPair deserializeKeyPair(String keyPairStr) throws Exception {
|
||||
String[] parts = keyPairStr.split("\n");
|
||||
if (parts.length != 2) {
|
||||
throw new IllegalArgumentException("Invalid key pair string format");
|
||||
}
|
||||
|
||||
// Decode private key
|
||||
byte[] privateKeyBytes = Base64.getDecoder().decode(parts[0]);
|
||||
PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(privateKeyBytes);
|
||||
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
|
||||
PrivateKey privateKey = keyFactory.generatePrivate(privateKeySpec);
|
||||
|
||||
// Decode public key
|
||||
byte[] publicKeyBytes = Base64.getDecoder().decode(parts[1]);
|
||||
X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(publicKeyBytes);
|
||||
PublicKey publicKey = keyFactory.generatePublic(publicKeySpec);
|
||||
|
||||
return new KeyPair(publicKey, privateKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes an X509Certificate to a Base64 encoded string format
|
||||
*/
|
||||
private String serializeCertificate(X509Certificate certificate) throws IOException {
|
||||
try {
|
||||
byte[] certBytes = certificate.getEncoded();
|
||||
return Base64.getEncoder().encodeToString(certBytes);
|
||||
} catch (CertificateException e) {
|
||||
throw new IOException("Failed to encode certificate", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserializes an X509Certificate from a Base64 encoded string format
|
||||
*/
|
||||
private X509Certificate deserializeCertificate(String certStr) throws Exception {
|
||||
try {
|
||||
byte[] certBytes = Base64.getDecoder().decode(certStr);
|
||||
CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
|
||||
ByteArrayInputStream bais = new ByteArrayInputStream(certBytes);
|
||||
return (X509Certificate) certFactory.generateCertificate(bais);
|
||||
} catch (CertificateException e) {
|
||||
throw new Exception("Failed to decode certificate", e);
|
||||
}
|
||||
}
|
||||
|
||||
public static SAMLCertificate getInstance(AppIdentifier appIdentifier, Main main)
|
||||
throws TenantOrAppNotFoundException {
|
||||
return (SAMLCertificate) main.getResourceDistributor()
|
||||
.getResource(appIdentifier, RESOURCE_KEY);
|
||||
}
|
||||
|
||||
public static void loadForAllTenants(Main main, List<AppIdentifier> apps,
|
||||
List<TenantIdentifier> tenantsThatChanged) {
|
||||
try {
|
||||
main.getResourceDistributor().withResourceDistributorLock(() -> {
|
||||
Map<ResourceDistributor.KeyClass, ResourceDistributor.SingletonResource> existingResources =
|
||||
main.getResourceDistributor()
|
||||
.getAllResourcesWithResourceKey(RESOURCE_KEY);
|
||||
main.getResourceDistributor().clearAllResourcesWithResourceKey(RESOURCE_KEY);
|
||||
for (AppIdentifier app : apps) {
|
||||
ResourceDistributor.SingletonResource resource = existingResources.get(
|
||||
new ResourceDistributor.KeyClass(app, RESOURCE_KEY));
|
||||
if (resource != null && !tenantsThatChanged.contains(app.getAsPublicTenantIdentifier())) {
|
||||
main.getResourceDistributor().setResource(app, RESOURCE_KEY,
|
||||
resource);
|
||||
} else {
|
||||
try {
|
||||
main.getResourceDistributor()
|
||||
.setResource(app, RESOURCE_KEY,
|
||||
new SAMLCertificate(app, main));
|
||||
} catch (TenantOrAppNotFoundException e) {
|
||||
Logging.error(main, app.getAsPublicTenantIdentifier(), e.getMessage(), false);
|
||||
// continue loading other resources
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
});
|
||||
} catch (ResourceDistributor.FuncException e) {
|
||||
throw new IllegalStateException("should never happen", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
package io.supertokens.saml.exceptions;
|
||||
|
||||
public class IDPInitiatedLoginDisallowedException extends Exception {
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* Copyright (c) 2025, VRAI Labs and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* This software is licensed under the Apache License, Version 2.0 (the
|
||||
* "License") as published by the Apache Software Foundation.
|
||||
*
|
||||
* You may not use this file except in compliance with the License. You may
|
||||
* obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package io.supertokens.saml.exceptions;
|
||||
|
||||
public class InvalidClientException extends Exception {
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
package io.supertokens.saml.exceptions;
|
||||
|
||||
public class InvalidCodeException extends Exception {
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
package io.supertokens.saml.exceptions;
|
||||
|
||||
public class InvalidRelayStateException extends Exception {
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* Copyright (c) 2025, VRAI Labs and/or its affiliates. All rights reserved.
|
||||
*
|
||||
* This software is licensed under the Apache License, Version 2.0 (the
|
||||
* "License") as published by the Apache Software Foundation.
|
||||
*
|
||||
* You may not use this file except in compliance with the License. You may
|
||||
* obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package io.supertokens.saml.exceptions;
|
||||
|
||||
public class MalformedSAMLMetadataXMLException extends Exception {
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
package io.supertokens.saml.exceptions;
|
||||
|
||||
public class SAMLResponseVerificationFailedException extends Exception {
|
||||
|
||||
}
|
||||
|
|
@ -19,6 +19,7 @@ package io.supertokens.session;
|
|||
import com.google.gson.JsonObject;
|
||||
import io.supertokens.Main;
|
||||
import io.supertokens.ProcessState;
|
||||
import io.supertokens.ResourceDistributor;
|
||||
import io.supertokens.config.Config;
|
||||
import io.supertokens.config.CoreConfig;
|
||||
import io.supertokens.exceptions.AccessTokenPayloadError;
|
||||
|
|
@ -100,7 +101,7 @@ public class Session {
|
|||
Storage storage = StorageLayer.getStorage(main);
|
||||
try {
|
||||
return createNewSession(
|
||||
new TenantIdentifier(null, null, null), storage, main,
|
||||
ResourceDistributor.getAppForTesting(), storage, main,
|
||||
recipeUserId, userDataInJWT, userDataInDatabase, false, AccessToken.getLatestVersion(), false);
|
||||
} catch (TenantOrAppNotFoundException e) {
|
||||
throw new IllegalStateException(e);
|
||||
|
|
@ -120,7 +121,7 @@ public class Session {
|
|||
Storage storage = StorageLayer.getStorage(main);
|
||||
try {
|
||||
return createNewSession(
|
||||
new TenantIdentifier(null, null, null), storage, main,
|
||||
ResourceDistributor.getAppForTesting(), storage, main,
|
||||
recipeUserId, userDataInJWT, userDataInDatabase, enableAntiCsrf, version, useStaticKey);
|
||||
} catch (TenantOrAppNotFoundException e) {
|
||||
throw new IllegalStateException(e);
|
||||
|
|
@ -200,7 +201,7 @@ public class Session {
|
|||
InvalidKeyException, JWT.JWTException,
|
||||
UnsupportedJWTSigningAlgorithmException, AccessTokenPayloadError, TryRefreshTokenException {
|
||||
try {
|
||||
return regenerateToken(new AppIdentifier(null, null), main, token, userDataInJWT);
|
||||
return regenerateToken(ResourceDistributor.getAppForTesting().toAppIdentifier(), main, token, userDataInJWT);
|
||||
} catch (TenantOrAppNotFoundException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
|
|
@ -326,7 +327,7 @@ public class Session {
|
|||
StorageTransactionLogicException, TryRefreshTokenException, UnauthorisedException,
|
||||
UnsupportedJWTSigningAlgorithmException, AccessTokenPayloadError {
|
||||
try {
|
||||
return getSession(new AppIdentifier(null, null), main, token, antiCsrfToken, enableAntiCsrf,
|
||||
return getSession(ResourceDistributor.getAppForTesting().toAppIdentifier(), main, token, antiCsrfToken, enableAntiCsrf,
|
||||
doAntiCsrfCheck, checkDatabase);
|
||||
} catch (TenantOrAppNotFoundException e) {
|
||||
throw new IllegalStateException(e);
|
||||
|
|
@ -532,7 +533,7 @@ public class Session {
|
|||
UnauthorisedException, StorageQueryException, TokenTheftDetectedException,
|
||||
UnsupportedJWTSigningAlgorithmException, AccessTokenPayloadError {
|
||||
try {
|
||||
return refreshSession(new AppIdentifier(null, null), main, refreshToken, antiCsrfToken,
|
||||
return refreshSession(ResourceDistributor.getAppForTesting().toAppIdentifier(), main, refreshToken, antiCsrfToken,
|
||||
enableAntiCsrf, accessTokenVersion, null);
|
||||
} catch (TenantOrAppNotFoundException e) {
|
||||
throw new IllegalStateException(e);
|
||||
|
|
@ -768,7 +769,7 @@ public class Session {
|
|||
throws StorageQueryException {
|
||||
Storage storage = StorageLayer.getStorage(main);
|
||||
return revokeSessionUsingSessionHandles(main,
|
||||
new AppIdentifier(null, null), storage,
|
||||
ResourceDistributor.getAppForTesting().toAppIdentifier(), storage,
|
||||
sessionHandles);
|
||||
}
|
||||
|
||||
|
|
@ -860,7 +861,7 @@ public class Session {
|
|||
public static String[] revokeAllSessionsForUser(Main main, String userId) throws StorageQueryException {
|
||||
Storage storage = StorageLayer.getStorage(main);
|
||||
return revokeAllSessionsForUser(main,
|
||||
new AppIdentifier(null, null), storage, userId, true);
|
||||
ResourceDistributor.getAppForTesting().toAppIdentifier(), storage, userId, true);
|
||||
}
|
||||
|
||||
public static String[] revokeAllSessionsForUser(Main main, AppIdentifier appIdentifier,
|
||||
|
|
@ -886,7 +887,7 @@ public class Session {
|
|||
throws StorageQueryException {
|
||||
Storage storage = StorageLayer.getStorage(main);
|
||||
return getAllNonExpiredSessionHandlesForUser(main,
|
||||
new AppIdentifier(null, null), storage, userId, true);
|
||||
ResourceDistributor.getAppForTesting().toAppIdentifier(), storage, userId, true);
|
||||
}
|
||||
|
||||
public static String[] getAllNonExpiredSessionHandlesForUser(
|
||||
|
|
@ -957,7 +958,7 @@ public class Session {
|
|||
throws StorageQueryException, UnauthorisedException {
|
||||
Storage storage = StorageLayer.getStorage(main);
|
||||
return getSessionData(
|
||||
new TenantIdentifier(null, null, null), storage,
|
||||
ResourceDistributor.getAppForTesting(), storage,
|
||||
sessionHandle);
|
||||
}
|
||||
|
||||
|
|
@ -978,7 +979,7 @@ public class Session {
|
|||
throws StorageQueryException, UnauthorisedException {
|
||||
Storage storage = StorageLayer.getStorage(main);
|
||||
return getJWTData(
|
||||
new TenantIdentifier(null, null, null), storage,
|
||||
ResourceDistributor.getAppForTesting(), storage,
|
||||
sessionHandle);
|
||||
}
|
||||
|
||||
|
|
@ -998,7 +999,7 @@ public class Session {
|
|||
throws StorageQueryException, UnauthorisedException {
|
||||
Storage storage = StorageLayer.getStorage(main);
|
||||
return getSession(
|
||||
new TenantIdentifier(null, null, null), storage,
|
||||
ResourceDistributor.getAppForTesting(), storage,
|
||||
sessionHandle);
|
||||
}
|
||||
|
||||
|
|
@ -1028,7 +1029,7 @@ public class Session {
|
|||
AccessToken.VERSION version)
|
||||
throws StorageQueryException, UnauthorisedException, AccessTokenPayloadError {
|
||||
Storage storage = StorageLayer.getStorage(main);
|
||||
updateSession(new TenantIdentifier(null, null, null), storage,
|
||||
updateSession(ResourceDistributor.getAppForTesting(), storage,
|
||||
sessionHandle, sessionData, jwtData, version);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import com.google.gson.JsonObject;
|
|||
import io.supertokens.Main;
|
||||
import io.supertokens.ProcessState;
|
||||
import io.supertokens.ProcessState.PROCESS_STATE;
|
||||
import io.supertokens.ResourceDistributor;
|
||||
import io.supertokens.config.Config;
|
||||
import io.supertokens.exceptions.AccessTokenPayloadError;
|
||||
import io.supertokens.exceptions.TryRefreshTokenException;
|
||||
|
|
@ -175,7 +176,7 @@ public class AccessToken {
|
|||
throws StorageQueryException, StorageTransactionLogicException, TryRefreshTokenException,
|
||||
UnsupportedJWTSigningAlgorithmException {
|
||||
try {
|
||||
return getInfoFromAccessToken(new AppIdentifier(null, null), main, token, doAntiCsrfCheck);
|
||||
return getInfoFromAccessToken(ResourceDistributor.getAppForTesting().toAppIdentifier(), main, token, doAntiCsrfCheck);
|
||||
} catch (TenantOrAppNotFoundException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
|
|
@ -186,7 +187,7 @@ public class AccessToken {
|
|||
public static AccessTokenInfo getInfoFromAccessTokenWithoutVerifying(@Nonnull String token)
|
||||
throws JWTException, TryRefreshTokenException {
|
||||
return getInfoFromAccessTokenWithoutVerifying(
|
||||
new AppIdentifier(null, null), token);
|
||||
ResourceDistributor.getAppForTesting().toAppIdentifier(), token);
|
||||
}
|
||||
|
||||
public static AccessTokenInfo getInfoFromAccessTokenWithoutVerifying(AppIdentifier appIdentifier,
|
||||
|
|
@ -209,7 +210,7 @@ public class AccessToken {
|
|||
NoSuchAlgorithmException, InvalidKeySpecException, SignatureException,
|
||||
UnsupportedJWTSigningAlgorithmException, AccessTokenPayloadError {
|
||||
try {
|
||||
return createNewAccessToken(new TenantIdentifier(null, null, null), main, sessionHandle, userId, userId,
|
||||
return createNewAccessToken(ResourceDistributor.getAppForTesting(), main, sessionHandle, userId, userId,
|
||||
refreshTokenHash1,
|
||||
parentRefreshTokenHash1,
|
||||
userData, antiCsrfToken, expiryTime, version, useStaticKey);
|
||||
|
|
@ -275,7 +276,7 @@ public class AccessToken {
|
|||
NoSuchAlgorithmException, InvalidKeySpecException, SignatureException,
|
||||
UnsupportedJWTSigningAlgorithmException, AccessTokenPayloadError {
|
||||
try {
|
||||
return createNewAccessTokenV1(new TenantIdentifier(null, null, null), main, sessionHandle, userId,
|
||||
return createNewAccessTokenV1(ResourceDistributor.getAppForTesting(), main, sessionHandle, userId,
|
||||
refreshTokenHash1,
|
||||
parentRefreshTokenHash1, userData, antiCsrfToken);
|
||||
} catch (TenantOrAppNotFoundException e) {
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ package io.supertokens.session.refreshToken;
|
|||
|
||||
import com.google.gson.Gson;
|
||||
import io.supertokens.Main;
|
||||
import io.supertokens.ResourceDistributor;
|
||||
import io.supertokens.config.Config;
|
||||
import io.supertokens.exceptions.UnauthorisedException;
|
||||
import io.supertokens.pluginInterface.exceptions.StorageQueryException;
|
||||
|
|
@ -46,7 +47,7 @@ public class RefreshToken {
|
|||
public static RefreshTokenInfo getInfoFromRefreshToken(@Nonnull Main main, @Nonnull String token)
|
||||
throws UnauthorisedException, StorageQueryException, StorageTransactionLogicException {
|
||||
try {
|
||||
return getInfoFromRefreshToken(new AppIdentifier(null, null), main, token);
|
||||
return getInfoFromRefreshToken(ResourceDistributor.getAppForTesting().toAppIdentifier(), main, token);
|
||||
} catch (TenantOrAppNotFoundException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
|
|
@ -92,7 +93,7 @@ public class RefreshToken {
|
|||
IllegalBlockSizeException, BadPaddingException, StorageTransactionLogicException,
|
||||
InvalidAlgorithmParameterException, InvalidKeySpecException {
|
||||
try {
|
||||
return createNewRefreshToken(new TenantIdentifier(null, null, null), main, sessionHandle, userId,
|
||||
return createNewRefreshToken(ResourceDistributor.getAppForTesting(), main, sessionHandle, userId,
|
||||
parentRefreshTokenHash1,
|
||||
antiCsrfToken);
|
||||
} catch (TenantOrAppNotFoundException e) {
|
||||
|
|
|
|||
|
|
@ -85,7 +85,7 @@ public class AccessTokenSigningKey extends ResourceDistributor.SingletonResource
|
|||
@TestOnly
|
||||
public static AccessTokenSigningKey getInstance(Main main) {
|
||||
try {
|
||||
return getInstance(new AppIdentifier(null, null), main);
|
||||
return getInstance(ResourceDistributor.getAppForTesting().toAppIdentifier(), main);
|
||||
} catch (TenantOrAppNotFoundException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ public class SigningKeys extends ResourceDistributor.SingletonResource {
|
|||
@TestOnly
|
||||
public static SigningKeys getInstance(Main main) {
|
||||
try {
|
||||
return getInstance(new AppIdentifier(null, null), main);
|
||||
return getInstance(ResourceDistributor.getAppForTesting().toAppIdentifier(), main);
|
||||
} catch (TenantOrAppNotFoundException e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,7 +36,9 @@ import io.supertokens.pluginInterface.multitenancy.MultitenancyStorage;
|
|||
import io.supertokens.pluginInterface.multitenancy.TenantConfig;
|
||||
import io.supertokens.pluginInterface.multitenancy.TenantIdentifier;
|
||||
import io.supertokens.pluginInterface.multitenancy.exceptions.TenantOrAppNotFoundException;
|
||||
import io.supertokens.pluginInterface.opentelemetry.WithinOtelSpan;
|
||||
import io.supertokens.pluginInterface.useridmapping.UserIdMapping;
|
||||
import io.supertokens.telemetry.TelemetryProvider;
|
||||
import io.supertokens.useridmapping.UserIdType;
|
||||
import jakarta.servlet.ServletException;
|
||||
import org.jetbrains.annotations.TestOnly;
|
||||
|
|
@ -48,11 +50,13 @@ import java.net.URL;
|
|||
import java.net.URLClassLoader;
|
||||
import java.util.*;
|
||||
|
||||
@WithinOtelSpan
|
||||
public class StorageLayer extends ResourceDistributor.SingletonResource {
|
||||
|
||||
public static final String RESOURCE_KEY = "io.supertokens.storageLayer.StorageLayer";
|
||||
private final Storage storage;
|
||||
private static URLClassLoader ucl = null;
|
||||
private static Storage storageInstanceForEnv = null;
|
||||
|
||||
public Storage getUnderlyingStorage() {
|
||||
return storage;
|
||||
|
|
@ -66,6 +70,37 @@ public class StorageLayer extends ResourceDistributor.SingletonResource {
|
|||
return getNewInstance(main, config, tenantIdentifier, doNotLog, true);
|
||||
}
|
||||
|
||||
public static void updateConfigJsonFromEnv(Main main, JsonObject configJson) {
|
||||
if (storageInstanceForEnv == null) {
|
||||
Storage result;
|
||||
if (StorageLayer.ucl == null) {
|
||||
result = new Start(main);
|
||||
} else {
|
||||
Storage storageLayer = null;
|
||||
ServiceLoader<Storage> sl = ServiceLoader.load(Storage.class, ucl);
|
||||
for (Storage plugin : sl) {
|
||||
if (storageLayer == null) {
|
||||
storageLayer = plugin;
|
||||
} else {
|
||||
throw new QuitProgramException(
|
||||
"Multiple database plugins found. Please make sure that just one plugin is in the "
|
||||
+ "/plugin" + " "
|
||||
+ "folder of the installation. Alternatively, please redownload and install "
|
||||
+ "SuperTokens" + ".");
|
||||
}
|
||||
}
|
||||
if (storageLayer != null) {
|
||||
result = storageLayer;
|
||||
} else {
|
||||
result = new Start(main);
|
||||
}
|
||||
}
|
||||
storageInstanceForEnv = result;
|
||||
}
|
||||
|
||||
storageInstanceForEnv.updateConfigJsonFromEnv(configJson);
|
||||
}
|
||||
|
||||
private static Storage getNewInstance(Main main, JsonObject config, TenantIdentifier tenantIdentifier, boolean doNotLog, boolean isBulkImportProxy) throws InvalidConfigException {
|
||||
Storage result;
|
||||
if (StorageLayer.ucl == null) {
|
||||
|
|
@ -85,7 +120,7 @@ public class StorageLayer extends ResourceDistributor.SingletonResource {
|
|||
}
|
||||
}
|
||||
if (storageLayer != null && !main.isForceInMemoryDB()
|
||||
&& (storageLayer.canBeUsed(config) || CLIOptions.get(main).isForceNoInMemoryDB())) {
|
||||
&& (storageLayer. canBeUsed(config) || CLIOptions.get(main).isForceNoInMemoryDB())) {
|
||||
if (isBulkImportProxy) {
|
||||
result = storageLayer.createBulkImportProxyStorageInstance();
|
||||
} else {
|
||||
|
|
@ -115,9 +150,7 @@ public class StorageLayer extends ResourceDistributor.SingletonResource {
|
|||
this.storage = storage;
|
||||
}
|
||||
|
||||
private StorageLayer(Main main, String pluginFolderPath, JsonObject configJson, TenantIdentifier tenantIdentifier)
|
||||
throws MalformedURLException, InvalidConfigException {
|
||||
Logging.info(main, tenantIdentifier, "Loading storage layer.", true);
|
||||
public static void loadStorageUCL(String pluginFolderPath) throws MalformedURLException {
|
||||
File loc = new File(pluginFolderPath);
|
||||
|
||||
File[] flist = loc.listFiles(file -> file.getPath().toLowerCase().endsWith(".jar"));
|
||||
|
|
@ -136,6 +169,12 @@ public class StorageLayer extends ResourceDistributor.SingletonResource {
|
|||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private StorageLayer(Main main, JsonObject configJson, TenantIdentifier tenantIdentifier)
|
||||
throws InvalidConfigException {
|
||||
Logging.info(main, tenantIdentifier, "Loading storage layer.", true);
|
||||
|
||||
this.storage = getNewStorageInstance(main, configJson, tenantIdentifier, false);
|
||||
|
||||
if (this.storage instanceof Start) {
|
||||
|
|
@ -203,10 +242,10 @@ public class StorageLayer extends ResourceDistributor.SingletonResource {
|
|||
return (StorageLayer) main.getResourceDistributor().getResource(tenantIdentifier, RESOURCE_KEY);
|
||||
}
|
||||
|
||||
public static void initPrimary(Main main, String pluginFolderPath, JsonObject configJson)
|
||||
public static void initPrimary(Main main, JsonObject configJson)
|
||||
throws MalformedURLException, InvalidConfigException {
|
||||
main.getResourceDistributor().setResource(new TenantIdentifier(null, null, null), RESOURCE_KEY,
|
||||
new StorageLayer(main, pluginFolderPath, configJson, TenantIdentifier.BASE_TENANT));
|
||||
new StorageLayer(main, configJson, TenantIdentifier.BASE_TENANT));
|
||||
}
|
||||
|
||||
public static void loadAllTenantStorage(Main main, TenantConfig[] tenants)
|
||||
|
|
@ -321,7 +360,8 @@ public class StorageLayer extends ResourceDistributor.SingletonResource {
|
|||
new ArrayList<>(storageToTenantIdentifiersMap.get(((StorageLayer) resource).storage)));
|
||||
((StorageLayer) resource).storage.initFileLogging(
|
||||
Config.getBaseConfig(main).getInfoLogPath(main),
|
||||
Config.getBaseConfig(main).getErrorLogPath(main));
|
||||
Config.getBaseConfig(main).getErrorLogPath(main),
|
||||
TelemetryProvider.getInstance(main));
|
||||
} catch (DbInitException e) {
|
||||
|
||||
Logging.error(main, TenantIdentifier.BASE_TENANT, e.getMessage(), false, e);
|
||||
|
|
@ -348,6 +388,7 @@ public class StorageLayer extends ResourceDistributor.SingletonResource {
|
|||
}
|
||||
}
|
||||
|
||||
@WithinOtelSpan
|
||||
public static Storage getStorage(TenantIdentifier tenantIdentifier, Main main)
|
||||
throws TenantOrAppNotFoundException {
|
||||
return getInstance(tenantIdentifier, main).storage;
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue