License key introduction and removing community edition package (#232)
This commit is contained in:
parent
8dc3153fde
commit
d58a1cbb58
12
.github/workflows/diff.yaml
vendored
12
.github/workflows/diff.yaml
vendored
@ -14,6 +14,8 @@ jobs:
|
||||
runs-on: [self-hosted, Linux, X64, Diff]
|
||||
env:
|
||||
THREADS: 24
|
||||
MEMGRAPH_ENTERPRISE_LICENSE: ${{ secrets.MEMGRAPH_ENTERPRISE_LICENSE }}
|
||||
MEMGRAPH_ORGANIZATION_NAME: ${{ secrets.MEMGRAPH_ORGANIZATION_NAME }}
|
||||
|
||||
steps:
|
||||
- name: Set up repository
|
||||
@ -76,6 +78,8 @@ jobs:
|
||||
runs-on: [self-hosted, Linux, X64, Diff]
|
||||
env:
|
||||
THREADS: 24
|
||||
MEMGRAPH_ENTERPRISE_LICENSE: ${{ secrets.MEMGRAPH_ENTERPRISE_LICENSE }}
|
||||
MEMGRAPH_ORGANIZATION_NAME: ${{ secrets.MEMGRAPH_ORGANIZATION_NAME }}
|
||||
|
||||
steps:
|
||||
- name: Set up repository
|
||||
@ -140,6 +144,8 @@ jobs:
|
||||
runs-on: [self-hosted, Linux, X64, Diff]
|
||||
env:
|
||||
THREADS: 24
|
||||
MEMGRAPH_ENTERPRISE_LICENSE: ${{ secrets.MEMGRAPH_ENTERPRISE_LICENSE }}
|
||||
MEMGRAPH_ORGANIZATION_NAME: ${{ secrets.MEMGRAPH_ORGANIZATION_NAME }}
|
||||
|
||||
steps:
|
||||
- name: Set up repository
|
||||
@ -214,6 +220,8 @@ jobs:
|
||||
runs-on: [self-hosted, Linux, X64, Diff]
|
||||
env:
|
||||
THREADS: 24
|
||||
MEMGRAPH_ENTERPRISE_LICENSE: ${{ secrets.MEMGRAPH_ENTERPRISE_LICENSE }}
|
||||
MEMGRAPH_ORGANIZATION_NAME: ${{ secrets.MEMGRAPH_ORGANIZATION_NAME }}
|
||||
|
||||
steps:
|
||||
- name: Set up repository
|
||||
@ -310,6 +318,8 @@ jobs:
|
||||
#continue-on-error: true
|
||||
env:
|
||||
THREADS: 24
|
||||
MEMGRAPH_ENTERPRISE_LICENSE: ${{ secrets.MEMGRAPH_ENTERPRISE_LICENSE }}
|
||||
MEMGRAPH_ORGANIZATION_NAME: ${{ secrets.MEMGRAPH_ORGANIZATION_NAME }}
|
||||
|
||||
steps:
|
||||
- name: Set up repository
|
||||
@ -349,6 +359,8 @@ jobs:
|
||||
runs-on: [self-hosted, Linux, X64, Diff, Gen7]
|
||||
env:
|
||||
THREADS: 24
|
||||
MEMGRAPH_ENTERPRISE_LICENSE: ${{ secrets.MEMGRAPH_ENTERPRISE_LICENSE }}
|
||||
MEMGRAPH_ORGANIZATION_NAME: ${{ secrets.MEMGRAPH_ORGANIZATION_NAME }}
|
||||
|
||||
steps:
|
||||
- name: Set up repository
|
||||
|
28
.github/workflows/package_all.yaml
vendored
28
.github/workflows/package_all.yaml
vendored
@ -15,7 +15,7 @@ jobs:
|
||||
fetch-depth: 0 # Required because of release/get_version.py
|
||||
- name: "Build package"
|
||||
run: |
|
||||
./release/package/run.sh package community centos-7
|
||||
./release/package/run.sh package centos-7
|
||||
- name: "Upload package"
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
@ -32,7 +32,7 @@ jobs:
|
||||
fetch-depth: 0 # Required because of release/get_version.py
|
||||
- name: "Build package"
|
||||
run: |
|
||||
./release/package/run.sh package community centos-8
|
||||
./release/package/run.sh package centos-8
|
||||
- name: "Upload package"
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
@ -49,7 +49,7 @@ jobs:
|
||||
fetch-depth: 0 # Required because of release/get_version.py
|
||||
- name: "Build package"
|
||||
run: |
|
||||
./release/package/run.sh package community debian-9
|
||||
./release/package/run.sh package debian-9
|
||||
- name: "Upload package"
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
@ -66,7 +66,7 @@ jobs:
|
||||
fetch-depth: 0 # Required because of release/get_version.py
|
||||
- name: "Build package"
|
||||
run: |
|
||||
./release/package/run.sh package community debian-10
|
||||
./release/package/run.sh package debian-10
|
||||
- name: "Upload package"
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
@ -84,7 +84,7 @@ jobs:
|
||||
- name: "Build package"
|
||||
run: |
|
||||
cd release/package
|
||||
./run.sh package community debian-10 --for-docker
|
||||
./run.sh package debian-10 --for-docker
|
||||
./run.sh docker
|
||||
- name: "Upload package"
|
||||
uses: actions/upload-artifact@v2
|
||||
@ -102,7 +102,7 @@ jobs:
|
||||
fetch-depth: 0 # Required because of release/get_version.py
|
||||
- name: "Build package"
|
||||
run: |
|
||||
./release/package/run.sh package community ubuntu-18.04
|
||||
./release/package/run.sh package ubuntu-18.04
|
||||
- name: "Upload package"
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
@ -119,7 +119,7 @@ jobs:
|
||||
fetch-depth: 0 # Required because of release/get_version.py
|
||||
- name: "Build package"
|
||||
run: |
|
||||
./release/package/run.sh package community ubuntu-20.04
|
||||
./release/package/run.sh package ubuntu-20.04
|
||||
- name: "Upload package"
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
@ -136,7 +136,7 @@ jobs:
|
||||
fetch-depth: 0 # Required because of release/get_version.py
|
||||
- name: "Build package"
|
||||
run: |
|
||||
./release/package/run.sh package enterprise centos-7
|
||||
./release/package/run.sh package centos-7
|
||||
- name: "Upload package"
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
@ -153,7 +153,7 @@ jobs:
|
||||
fetch-depth: 0 # Required because of release/get_version.py
|
||||
- name: "Build package"
|
||||
run: |
|
||||
./release/package/run.sh package enterprise centos-8
|
||||
./release/package/run.sh package centos-8
|
||||
- name: "Upload package"
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
@ -170,7 +170,7 @@ jobs:
|
||||
fetch-depth: 0 # Required because of release/get_version.py
|
||||
- name: "Build package"
|
||||
run: |
|
||||
./release/package/run.sh package enterprise debian-9
|
||||
./release/package/run.sh package debian-9
|
||||
- name: "Upload package"
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
@ -187,7 +187,7 @@ jobs:
|
||||
fetch-depth: 0 # Required because of release/get_version.py
|
||||
- name: "Build package"
|
||||
run: |
|
||||
./release/package/run.sh package enterprise debian-10
|
||||
./release/package/run.sh package debian-10
|
||||
- name: "Upload package"
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
@ -205,7 +205,7 @@ jobs:
|
||||
- name: "Build package"
|
||||
run: |
|
||||
cd release/package
|
||||
./run.sh package enterprise debian-10 --for-docker
|
||||
./run.sh package debian-10 --for-docker
|
||||
./run.sh docker
|
||||
- name: "Upload package"
|
||||
uses: actions/upload-artifact@v2
|
||||
@ -223,7 +223,7 @@ jobs:
|
||||
fetch-depth: 0 # Required because of release/get_version.py
|
||||
- name: "Build package"
|
||||
run: |
|
||||
./release/package/run.sh package enterprise ubuntu-18.04
|
||||
./release/package/run.sh package ubuntu-18.04
|
||||
- name: "Upload package"
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
@ -240,7 +240,7 @@ jobs:
|
||||
fetch-depth: 0 # Required because of release/get_version.py
|
||||
- name: "Build package"
|
||||
run: |
|
||||
./release/package/run.sh package enterprise ubuntu-20.04
|
||||
./release/package/run.sh package ubuntu-20.04
|
||||
- name: "Upload package"
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
|
@ -61,9 +61,9 @@ set(MEMGRAPH_OVERRIDE_VERSION_SUFFIX "")
|
||||
|
||||
# Variables used to generate the versions.
|
||||
if (MG_ENTERPRISE)
|
||||
set(get_version_enterprise "--enterprise")
|
||||
set(get_version_offering "")
|
||||
else()
|
||||
set(get_version_enterprise "")
|
||||
set(get_version_offering "--open-source")
|
||||
endif()
|
||||
set(get_version_script "${CMAKE_SOURCE_DIR}/release/get_version.py")
|
||||
|
||||
@ -71,7 +71,7 @@ set(get_version_script "${CMAKE_SOURCE_DIR}/release/get_version.py")
|
||||
execute_process(
|
||||
OUTPUT_VARIABLE MEMGRAPH_VERSION
|
||||
RESULT_VARIABLE MEMGRAPH_VERSION_RESULT
|
||||
COMMAND "${get_version_script}" ${get_version_enterprise}
|
||||
COMMAND "${get_version_script}" ${get_version_offering}
|
||||
"${MEMGRAPH_OVERRIDE_VERSION}"
|
||||
"${MEMGRAPH_OVERRIDE_VERSION_SUFFIX}"
|
||||
"--memgraph-root-dir"
|
||||
@ -87,7 +87,7 @@ endif()
|
||||
execute_process(
|
||||
OUTPUT_VARIABLE MEMGRAPH_VERSION_DEB
|
||||
RESULT_VARIABLE MEMGRAPH_VERSION_DEB_RESULT
|
||||
COMMAND "${get_version_script}" ${get_version_enterprise}
|
||||
COMMAND "${get_version_script}" ${get_version_offering}
|
||||
--variant deb
|
||||
"${MEMGRAPH_OVERRIDE_VERSION}"
|
||||
"${MEMGRAPH_OVERRIDE_VERSION_SUFFIX}"
|
||||
@ -104,7 +104,7 @@ endif()
|
||||
execute_process(
|
||||
OUTPUT_VARIABLE MEMGRAPH_VERSION_RPM
|
||||
RESULT_VARIABLE MEMGRAPH_VERSION_RPM_RESULT
|
||||
COMMAND "${get_version_script}" ${get_version_enterprise}
|
||||
COMMAND "${get_version_script}" ${get_version_offering}
|
||||
--variant rpm
|
||||
"${MEMGRAPH_OVERRIDE_VERSION}"
|
||||
"${MEMGRAPH_OVERRIDE_VERSION_SUFFIX}"
|
||||
|
@ -101,3 +101,5 @@ undocumented:
|
||||
- "help"
|
||||
- "help_xml"
|
||||
- "version"
|
||||
- "organization_name"
|
||||
- "license_key"
|
||||
|
@ -1,11 +1,6 @@
|
||||
# Install the license file.
|
||||
if (MG_ENTERPRISE)
|
||||
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/LICENSE_ENTERPRISE.md
|
||||
DESTINATION share/doc/memgraph RENAME copyright)
|
||||
else()
|
||||
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/LICENSE_COMMUNITY.md
|
||||
DESTINATION share/doc/memgraph RENAME copyright)
|
||||
endif()
|
||||
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/LICENSE.md
|
||||
DESTINATION share/doc/memgraph RENAME copyright)
|
||||
|
||||
install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/third-party-licenses
|
||||
DESTINATION share/doc/memgraph)
|
||||
@ -30,23 +25,13 @@ set(CPACK_DEBIAN_PACKAGE_SECTION non-free/database)
|
||||
set(CPACK_DEBIAN_PACKAGE_HOMEPAGE https://memgraph.com)
|
||||
set(CPACK_DEBIAN_PACKAGE_VERSION "${MEMGRAPH_VERSION_DEB}")
|
||||
set(CPACK_DEBIAN_FILE_NAME "memgraph_${MEMGRAPH_VERSION_DEB}_amd64.deb")
|
||||
if (MG_ENTERPRISE)
|
||||
set(CPACK_DEBIAN_PACKAGE_CONTROL_EXTRA
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/debian/enterprise/conffiles;"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/debian/enterprise/copyright;"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/debian/preinst;"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/debian/prerm;"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/debian/postrm;"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/debian/postinst;")
|
||||
else()
|
||||
set(CPACK_DEBIAN_PACKAGE_CONTROL_EXTRA
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/debian/community/conffiles;"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/debian/community/copyright;"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/debian/preinst;"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/debian/prerm;"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/debian/postrm;"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/debian/postinst;")
|
||||
endif()
|
||||
set(CPACK_DEBIAN_PACKAGE_CONTROL_EXTRA
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/debian/conffiles;"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/debian/copyright;"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/debian/preinst;"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/debian/prerm;"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/debian/postrm;"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/debian/postinst;")
|
||||
set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS ON)
|
||||
# Description formatting is important, summary must be followed with a newline and 1 space.
|
||||
set(CPACK_DEBIAN_PACKAGE_DESCRIPTION "${CPACK_PACKAGE_DESCRIPTION_SUMMARY}
|
||||
@ -65,13 +50,8 @@ set(CPACK_RPM_EXCLUDE_FROM_AUTO_FILELIST_ADDITION
|
||||
/var /var/lib /var/log /etc/logrotate.d
|
||||
/lib /lib/systemd /lib/systemd/system /lib/systemd/system/memgraph.service)
|
||||
set(CPACK_RPM_PACKAGE_REQUIRES_PRE "shadow-utils")
|
||||
if (MG_ENTERPRISE)
|
||||
set(CPACK_RPM_USER_BINARY_SPECFILE "${CMAKE_CURRENT_SOURCE_DIR}/rpm/enterprise/memgraph.spec.in")
|
||||
set(CPACK_RPM_PACKAGE_LICENSE "Memgraph Enterprise Trial License")
|
||||
else()
|
||||
set(CPACK_RPM_USER_BINARY_SPECFILE "${CMAKE_CURRENT_SOURCE_DIR}/rpm/community/memgraph.spec.in")
|
||||
set(CPACK_RPM_PACKAGE_LICENSE "Memgraph Community License")
|
||||
endif()
|
||||
set(CPACK_RPM_USER_BINARY_SPECFILE "${CMAKE_CURRENT_SOURCE_DIR}/rpm/memgraph.spec.in")
|
||||
set(CPACK_RPM_PACKAGE_LICENSE "Memgraph License")
|
||||
# Description formatting is important, no line must be greater than 80 characters.
|
||||
set(CPACK_RPM_PACKAGE_DESCRIPTION "Contains Memgraph, the graph database.
|
||||
It aims to deliver developers the speed, simplicity and scale required to build
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Memgraph Enterprise - Software Subscription Agreement
|
||||
# Memgraph - Software Subscription Agreement
|
||||
|
||||
Memgraph Limited is registered in England under registration 10195084 and has
|
||||
its registered office at Suite 4, Ironstone House, Ironstone Way, Brixworth,
|
@ -1,98 +0,0 @@
|
||||
# Memgraph Community User License Agreement
|
||||
|
||||
This License Agreement governs your use of the Memgraph Community Release (the
|
||||
"Software") and documentation ("Documentation").
|
||||
|
||||
BY DOWNLOADING AND/OR ACCESSING THIS SOFTWARE, YOU ("LICENSEE") AGREE TO THESE
|
||||
TERMS.
|
||||
|
||||
1. License Grant
|
||||
|
||||
The Software and Documentation are provided to Licensee at no charge and are
|
||||
licensed, not sold to Licensee. No ownership of any part of the Software and
|
||||
Documentation is hereby transferred to Licensee. Subject to (i) the terms and
|
||||
conditions of this License Agreement, and (ii) any additional license
|
||||
restrictions and parameters contained on Licensor’s quotation, website, or
|
||||
order form, Licensor hereby grants Licensee a personal, non-assignable,
|
||||
non-transferable and non-exclusive license to install, access and use the
|
||||
Software (in object code form only) and Documentation for Licensee’s internal
|
||||
business purposes (including for use in a production environment) only. All
|
||||
rights relating to the Software and Documentation that are not expressly
|
||||
licensed in this License Agreement, whether now existing or which may hereafter
|
||||
come into existence are reserved for Licensor. Licensee shall not remove,
|
||||
obscure, or alter any proprietary rights notices (including without limitation
|
||||
copyright and trademark notices), which may be affixed to or contained within
|
||||
the Software or Documentation.
|
||||
|
||||
Licensor may terminate this License Agreement with immediate effect upon
|
||||
written notice to the Licensee. Upon termination Licensee shall delete all
|
||||
electronic copies of all or any part of the Software and/or the Documentation
|
||||
resident in its systems or elsewhere.
|
||||
|
||||
2. Restrictions
|
||||
|
||||
Licensee will not, directly or indirectly, (a) copy the Software or
|
||||
Documentation in any manner or for any purpose; (b) install, access or use any
|
||||
component of the Software or Documentation for any purpose not expressly
|
||||
granted in Section 1 above; (c) resell, distribute, publicly display or
|
||||
publicly perform the Software or Documentation or any component thereof, by
|
||||
transfer, lease, loan or any other means, or make it available for use by
|
||||
others in any time-sharing, service bureau or similar arrangement; (d)
|
||||
disassemble, decrypt, extract, reverse engineer or reverse compile the
|
||||
Software, or otherwise attempt to discover the source code, confidential
|
||||
algorithms or techniques incorporated in the Software; (e) export the Software
|
||||
or Documentation in violation of any applicable laws or regulations; (f)
|
||||
modify, translate, adapt, or create derivative works from the Software or
|
||||
Documentation; (g) circumvent, disable or otherwise interfere with
|
||||
security-related features of the Software or Documentation; (h) use the
|
||||
Software or Documentation for any illegal purpose, in any manner that is
|
||||
inconsistent with the terms of this License Agreement, or to engage in illegal
|
||||
activity; (i) remove or alter any trademark, logo, copyright or other
|
||||
proprietary notices, legends, symbols or labels on, or embedded in, the
|
||||
Software or Documentation; or (j) provide access to the Software or
|
||||
Documentation to third parties.
|
||||
|
||||
3. Warranty Disclaimer
|
||||
|
||||
THE SOFTWARE AND DOCUMENTATION ARE PROVIDED "AS IS" AND LICENSOR MAKES NO
|
||||
WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE OR NON
|
||||
INFRINGEMENT OF THIRD PARTIES’ INTELLECTUAL PROPERTY RIGHTS OR OTHER
|
||||
PROPRIETARY RIGHTS. NEITHER THIS LICENSE AGREEMENT NOR ANY DOCUMENTATION
|
||||
FURNISHED UNDER IT IS INTENDED TO EXPRESS OR IMPLY ANY WARRANTY THAT THE
|
||||
OPERATION OF THE SOFTWARE WILL BE UNINTERRUPTED, TIMELY, OR ERROR-FREE.
|
||||
|
||||
4. Limitation of Liability
|
||||
|
||||
Licensor shall not in any circumstances be liable, whether in tort (including
|
||||
for negligence or breach of statutory duty howsoever arising), contract,
|
||||
misrepresentation (whether innocent or negligent) or otherwise for: loss of
|
||||
profits, loss of business, depletion of goodwill or similar losses, loss of
|
||||
anticipated savings, loss of goods, loss or corruption of data or computer
|
||||
downtime, or any special, indirect, consequential or pure economic loss, costs,
|
||||
damages, charges or expenses.
|
||||
|
||||
Licensor's total aggregate liability in contract, tort (including without
|
||||
limitation negligence or breach of statutory duty howsoever arising),
|
||||
misrepresentation (whether innocent or negligent), restitution or otherwise,
|
||||
arising in connection with the performance or contemplated performance of this
|
||||
License Agreement shall in all circumstances be limited to GBP10.00 (ten pounds
|
||||
sterling).
|
||||
|
||||
Nothing in this License Agreement shall limit Licensor’s liability in the case
|
||||
of death or personal injury caused by negligence, fraud, or fraudulent
|
||||
misrepresentation, or where it otherwise cannot be limited by law.
|
||||
|
||||
5. Technical Data
|
||||
|
||||
Licensor may collect and use technical information (such as usage patterns)
|
||||
gathered when the Licensee downloads and uses the Software. This is generally
|
||||
statistical data which does not identify an identified or identifiable
|
||||
individual. It may also include Licensee’s IP address which is personal data
|
||||
and is processed in accordance with our Privacy Policy. We only use this
|
||||
technical information to improve our products.
|
||||
|
||||
6. Law and Jurisdiction
|
||||
|
||||
This License Agreement is governed by the laws of England and is subject to the
|
||||
non-exclusive jurisdiction of the courts of England.
|
@ -1,2 +0,0 @@
|
||||
/etc/memgraph/memgraph.conf
|
||||
/etc/logrotate.d/memgraph
|
@ -1 +0,0 @@
|
||||
../../LICENSE_COMMUNITY.md
|
1
release/debian/copyright
Symbolic link
1
release/debian/copyright
Symbolic link
@ -0,0 +1 @@
|
||||
../LICENSE.md
|
@ -1 +0,0 @@
|
||||
../../LICENSE_ENTERPRISE.md
|
@ -7,7 +7,7 @@ set -e
|
||||
|
||||
# Manage (remove) /etc/logrotate.d/memgraph_audit file because the whole
|
||||
# logrotate config is moved to /etc/logrotate.d/memgraph since v1.2.0.
|
||||
# Note: Only used to manage Memgraph Enterprise config but packaged into the
|
||||
# Note: Only used to manage Memgraph config but it was packaged into the
|
||||
# Memgraph Community as well.
|
||||
if dpkg-maintscript-helper supports rm_conffile 2>/dev/null; then
|
||||
# 1.1.999 is chosen because it's high enough version number. It's highly
|
||||
|
@ -1,30 +0,0 @@
|
||||
FROM debian:buster
|
||||
# NOTE: If you change the base distro update release/package as well.
|
||||
|
||||
ARG deb_release
|
||||
|
||||
RUN apt-get update && apt-get install -y \
|
||||
openssl libcurl4 libssl1.1 python3 libpython3.7 python3-pip \
|
||||
--no-install-recommends \
|
||||
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
|
||||
|
||||
RUN pip3 install networkx==2.4 numpy==1.19.2 scipy==1.5.2
|
||||
|
||||
COPY ${deb_release} /
|
||||
|
||||
# Install memgraph package
|
||||
RUN dpkg -i ${deb_release}
|
||||
|
||||
# Memgraph listens for Bolt Protocol on this port by default.
|
||||
EXPOSE 7687
|
||||
# Snapshots and logging volumes
|
||||
VOLUME /var/log/memgraph
|
||||
VOLUME /var/lib/memgraph
|
||||
# Configuration volume
|
||||
VOLUME /etc/memgraph
|
||||
|
||||
USER memgraph
|
||||
WORKDIR /usr/lib/memgraph
|
||||
|
||||
ENTRYPOINT ["/usr/lib/memgraph/memgraph"]
|
||||
CMD [""]
|
@ -49,13 +49,13 @@ import os
|
||||
# <VERSION>+<DISTANCE>~<SHORTHASH>-<OFFERING>[-<SUFFIX>]
|
||||
# Examples:
|
||||
# Release version:
|
||||
# 0.50.1-community
|
||||
# 0.50.1-enterprise
|
||||
# 0.50.1-enterprise-veryimportantcustomer
|
||||
# 0.50.1-open-source
|
||||
# 0.50.1
|
||||
# 0.50.1-veryimportantcustomer
|
||||
# Development version (master, 12 commits after release/0.50):
|
||||
# 0.50.0+12~7e1eef94-community
|
||||
# 0.50.0+12~7e1eef94-enterprise
|
||||
# 0.50.0+12~7e1eef94-enterprise-veryimportantcustomer
|
||||
# 0.50.0+12~7e1eef94-open-source
|
||||
# 0.50.0+12~7e1eef94
|
||||
# 0.50.0+12~7e1eef94-veryimportantcustomer
|
||||
#
|
||||
# The DEB package version is determined using the following two templates:
|
||||
# Release version:
|
||||
@ -64,13 +64,13 @@ import os
|
||||
# <VERSION>+<DISTANCE>~<SHORTHASH>-<OFFERING>[-<SUFFIX>]-1
|
||||
# Examples:
|
||||
# Release version:
|
||||
# 0.50.1-community-1
|
||||
# 0.50.1-enterprise-1
|
||||
# 0.50.1-enterprise-veryimportantcustomer-1
|
||||
# 0.50.1-open-source-1
|
||||
# 0.50.1-1
|
||||
# 0.50.1-veryimportantcustomer-1
|
||||
# Development version (master, 12 commits after release/0.50):
|
||||
# 0.50.0+12~7e1eef94-community-1
|
||||
# 0.50.0+12~7e1eef94-enterprise-1
|
||||
# 0.50.0+12~7e1eef94-enterprise-veryimportantcustomer-1
|
||||
# 0.50.0+12~7e1eef94-open-source-1
|
||||
# 0.50.0+12~7e1eef94-1
|
||||
# 0.50.0+12~7e1eef94-veryimportantcustomer-1
|
||||
# For more documentation about the DEB package naming conventions see:
|
||||
# https://www.debian.org/doc/debian-policy/ch-controlfields.html#version
|
||||
#
|
||||
@ -81,13 +81,13 @@ import os
|
||||
# <VERSION>_0.<DISTANCE>.<SHORTHASH>.<OFFERING>[.<SUFFIX>]
|
||||
# Examples:
|
||||
# Release version:
|
||||
# 0.50.1_1.community
|
||||
# 0.50.1_1.enterprise
|
||||
# 0.50.1_1.enterprise.veryimportantcustomer
|
||||
# 0.50.1_1.open-source
|
||||
# 0.50.1_1
|
||||
# 0.50.1_1.veryimportantcustomer
|
||||
# Development version:
|
||||
# 0.50.0_0.12.7e1eef94.community
|
||||
# 0.50.0_0.12.7e1eef94.enterprise
|
||||
# 0.50.0_0.12.7e1eef94.enterprise.veryimportantcustomer
|
||||
# 0.50.0_0.12.7e1eef94.open-source
|
||||
# 0.50.0_0.12.7e1eef94
|
||||
# 0.50.0_0.12.7e1eef94.veryimportantcustomer
|
||||
# For more documentation about the RPM package naming conventions see:
|
||||
# https://docs.fedoraproject.org/en-US/packaging-guidelines/Versioning/
|
||||
# https://fedoraproject.org/wiki/Package_Versioning_Examples
|
||||
@ -107,20 +107,20 @@ def format_version(variant, version, offering, distance=None, shorthash=None,
|
||||
# This is a release version.
|
||||
if variant == "deb":
|
||||
# <VERSION>-<OFFERING>[-<SUFFIX>]-1
|
||||
ret = "{}-{}".format(version, offering)
|
||||
ret = "{}{}".format(version, f"-{offering}" if offering else "")
|
||||
if suffix:
|
||||
ret += "-" + suffix
|
||||
ret += "-1"
|
||||
return ret
|
||||
elif variant == "rpm":
|
||||
# <VERSION>_1.<OFFERING>[.<SUFFIX>]
|
||||
ret = "{}_1.{}".format(version, offering)
|
||||
ret = "{}_1{}".format(version, f".{offering}" if offering else "")
|
||||
if suffix:
|
||||
ret += "." + suffix
|
||||
return ret
|
||||
else:
|
||||
# <VERSION>-<OFFERING>[-<SUFFIX>]
|
||||
ret = "{}-{}".format(version, offering)
|
||||
ret = "{}{}".format(version, f"-{offering}" if offering else "")
|
||||
if suffix:
|
||||
ret += "-" + suffix
|
||||
return ret
|
||||
@ -128,21 +128,21 @@ def format_version(variant, version, offering, distance=None, shorthash=None,
|
||||
# This is a development version.
|
||||
if variant == "deb":
|
||||
# <VERSION>+<DISTANCE>~<SHORTHASH>-<OFFERING>[-<SUFFIX>]-1
|
||||
ret = "{}+{}~{}-{}".format(version, distance, shorthash, offering)
|
||||
ret = "{}+{}~{}{}".format(version, distance, shorthash, f"-{offering}" if offering else "")
|
||||
if suffix:
|
||||
ret += "-" + suffix
|
||||
ret += "-1"
|
||||
return ret
|
||||
elif variant == "rpm":
|
||||
# <VERSION>_0.<DISTANCE>.<SHORTHASH>.<OFFERING>[.<SUFFIX>]
|
||||
ret = "{}_0.{}.{}.{}".format(
|
||||
version, distance, shorthash, offering)
|
||||
ret = "{}_0.{}.{}{}".format(
|
||||
version, distance, shorthash, f".{offering}" if offering else "")
|
||||
if suffix:
|
||||
ret += "." + suffix
|
||||
return ret
|
||||
else:
|
||||
# <VERSION>+<DISTANCE>~<SHORTHASH>-<OFFERING>[-<SUFFIX>]
|
||||
ret = "{}+{}~{}-{}".format(version, distance, shorthash, offering)
|
||||
ret = "{}+{}~{}{}".format(version, distance, shorthash, f"-{offering}" if offering else "")
|
||||
if suffix:
|
||||
ret += "-" + suffix
|
||||
return ret
|
||||
@ -152,8 +152,8 @@ def format_version(variant, version, offering, distance=None, shorthash=None,
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Get the current version of Memgraph.")
|
||||
parser.add_argument(
|
||||
"--enterprise", action="store_true",
|
||||
help="set the current offering to enterprise (default 'community')")
|
||||
"--open-source", action="store_true",
|
||||
help="set the current offering to 'open-source'")
|
||||
parser.add_argument(
|
||||
"version", help="manual version override, if supplied the version isn't "
|
||||
"determined using git")
|
||||
@ -173,7 +173,7 @@ if not os.path.isdir(args.memgraph_root_dir):
|
||||
|
||||
os.chdir(args.memgraph_root_dir)
|
||||
|
||||
offering = "enterprise" if args.enterprise else "community"
|
||||
offering = "open-source" if args.open_source else None
|
||||
|
||||
# Check whether the version was manually supplied.
|
||||
if args.version:
|
||||
|
@ -1,7 +1,7 @@
|
||||
# logrotate configuration for Memgraph Enterprise
|
||||
# see "man logrotate" for details
|
||||
|
||||
/var/lib/memgraph/durability/audit/audit.log {
|
||||
/var/lib/memgraph/audit/audit.log {
|
||||
# rotate log files daily
|
||||
daily
|
||||
# keep one year worth of audit logs
|
@ -1,2 +0,0 @@
|
||||
# logrotate configuration for Memgraph Community
|
||||
# see "man logrotate" for details
|
@ -3,7 +3,6 @@
|
||||
set -Eeuo pipefail
|
||||
|
||||
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||
SUPPORTED_OFFERING=(community enterprise)
|
||||
SUPPORTED_OS=(centos-7 centos-8 debian-9 debian-10 ubuntu-18.04 ubuntu-20.04)
|
||||
PROJECT_ROOT="$SCRIPT_DIR/../.."
|
||||
TOOLCHAIN_VERSION="toolchain-v3"
|
||||
@ -11,23 +10,14 @@ ACTIVATE_TOOLCHAIN="source /opt/${TOOLCHAIN_VERSION}/activate"
|
||||
HOST_OUTPUT_DIR="$PROJECT_ROOT/build/output"
|
||||
|
||||
print_help () {
|
||||
echo "$0 init|package {offering} {os} [--for-docker]|docker|test"
|
||||
echo "$0 init|package {os} [--for-docker]|docker|test"
|
||||
echo ""
|
||||
echo " offerings: ${SUPPORTED_OFFERING[*]}"
|
||||
echo " OSs: ${SUPPORTED_OS[*]}"
|
||||
exit 1
|
||||
}
|
||||
|
||||
make_package () {
|
||||
offering="$1"
|
||||
offering_flag=" -DMG_ENTERPRISE=OFF "
|
||||
if [[ "$offering" == "enterprise" ]]; then
|
||||
offering_flag=" -DMG_ENTERPRISE=ON "
|
||||
fi
|
||||
if [[ "$offering" == "community" ]]; then
|
||||
offering_flag=" -DMG_ENTERPRISE=OFF "
|
||||
fi
|
||||
os="$2"
|
||||
os="$1"
|
||||
package_command=""
|
||||
if [[ "$os" =~ ^"centos".* ]]; then
|
||||
package_command=" cpack -G RPM --config ../CPackConfig.cmake && rpmlint memgraph*.rpm "
|
||||
@ -45,7 +35,7 @@ make_package () {
|
||||
fi
|
||||
fi
|
||||
build_container="mgbuild_$os"
|
||||
echo "Building Memgraph $offering for $os on $build_container..."
|
||||
echo "Building Memgraph for $os on $build_container..."
|
||||
|
||||
echo "Copying project files..."
|
||||
# If master is not the current branch, fetch it, because the get_version
|
||||
@ -74,7 +64,7 @@ make_package () {
|
||||
echo "Building targeted package..."
|
||||
docker exec "$build_container" bash -c "cd /memgraph && ./init"
|
||||
docker exec "$build_container" bash -c "cd $container_build_dir && rm -rf ./*"
|
||||
docker exec "$build_container" bash -c "cd $container_build_dir && $ACTIVATE_TOOLCHAIN && cmake -DCMAKE_BUILD_TYPE=release $offering_flag $docker_flag .."
|
||||
docker exec "$build_container" bash -c "cd $container_build_dir && $ACTIVATE_TOOLCHAIN && cmake -DCMAKE_BUILD_TYPE=release $docker_flag .."
|
||||
# ' is used instead of " because we need to run make within the allowed
|
||||
# container resources.
|
||||
# shellcheck disable=SC2016
|
||||
@ -121,14 +111,6 @@ case "$1" in
|
||||
if [[ "$#" -lt 2 ]]; then
|
||||
print_help
|
||||
fi
|
||||
offering="$1"
|
||||
shift 1
|
||||
is_offering_ok=false
|
||||
for supported_offering in "${SUPPORTED_OFFERING[@]}"; do
|
||||
if [[ "$supported_offering" == "${offering}" ]]; then
|
||||
is_offering_ok=true
|
||||
fi
|
||||
done
|
||||
os="$1"
|
||||
shift 1
|
||||
is_os_ok=false
|
||||
@ -137,8 +119,8 @@ case "$1" in
|
||||
is_os_ok=true
|
||||
fi
|
||||
done
|
||||
if [[ "$is_offering_ok" == true ]] && [[ "$is_os_ok" == true ]]; then
|
||||
make_package "$offering" "$os" "$@"
|
||||
if [[ "$is_os_ok" == true ]]; then
|
||||
make_package "$os" "$@"
|
||||
else
|
||||
print_help
|
||||
fi
|
||||
|
@ -1,138 +0,0 @@
|
||||
# -*- rpm-spec -*-
|
||||
BuildRoot: %_topdir/@CPACK_PACKAGE_FILE_NAME@@CPACK_RPM_PACKAGE_COMPONENT_PART_PATH@
|
||||
Summary: @CPACK_RPM_PACKAGE_SUMMARY@
|
||||
Name: @CPACK_RPM_PACKAGE_NAME@
|
||||
Version: @CPACK_RPM_PACKAGE_VERSION@
|
||||
Release: @CPACK_RPM_PACKAGE_RELEASE@
|
||||
License: @CPACK_RPM_PACKAGE_LICENSE@
|
||||
# Group field is deprecated
|
||||
# Group: @CPACK_RPM_PACKAGE_GROUP@
|
||||
Vendor: @CPACK_RPM_PACKAGE_VENDOR@
|
||||
BuildRequires: systemd
|
||||
|
||||
@TMP_RPM_URL@
|
||||
@TMP_RPM_REQUIRES@
|
||||
@TMP_RPM_REQUIRES_PRE@
|
||||
@TMP_RPM_REQUIRES_POST@
|
||||
@TMP_RPM_REQUIRES_PREUN@
|
||||
@TMP_RPM_REQUIRES_POSTUN@
|
||||
@TMP_RPM_PROVIDES@
|
||||
@TMP_RPM_OBSOLETES@
|
||||
@TMP_RPM_CONFLICTS@
|
||||
@TMP_RPM_AUTOPROV@
|
||||
@TMP_RPM_AUTOREQ@
|
||||
@TMP_RPM_AUTOREQPROV@
|
||||
@TMP_RPM_BUILDARCH@
|
||||
@TMP_RPM_PREFIXES@
|
||||
|
||||
@TMP_RPM_DEBUGINFO@
|
||||
|
||||
# This is needed to prevent Python compilation errors when building the RPM
|
||||
# package
|
||||
# https://github.com/scylladb/scylla/issues/2235
|
||||
%if 0%{?rhel} < 8
|
||||
%global __os_install_post \
|
||||
/usr/lib/rpm/redhat/brp-compress \
|
||||
%{!?__debug_package:\
|
||||
/usr/lib/rpm/redhat/brp-strip %{__strip} \
|
||||
/usr/lib/rpm/redhat/brp-strip-comment-note %{__strip} %{__objdump} \
|
||||
} \
|
||||
/usr/lib/rpm/redhat/brp-strip-static-archive %{__strip} \
|
||||
%{!?__jar_repack:/usr/lib/rpm/redhat/brp-java-repack-jars} \
|
||||
%{nil}
|
||||
%else
|
||||
%global __os_install_post \
|
||||
/usr/lib/rpm/brp-compress \
|
||||
%{!?__debug_package:\
|
||||
/usr/lib/rpm/brp-strip %{__strip} \
|
||||
/usr/lib/rpm/brp-strip-comment-note %{__strip} %{__objdump} \
|
||||
} \
|
||||
/usr/lib/rpm/brp-strip-static-archive %{__strip} \
|
||||
%{nil}
|
||||
%endif
|
||||
|
||||
%define _rpmdir %_topdir/RPMS
|
||||
%define _srcrpmdir %_topdir/SRPMS
|
||||
@FILE_NAME_DEFINE@
|
||||
%define _unpackaged_files_terminate_build 0
|
||||
@TMP_RPM_SPEC_INSTALL_POST@
|
||||
@CPACK_RPM_SPEC_MORE_DEFINE@
|
||||
@CPACK_RPM_COMPRESSION_TYPE_TMP@
|
||||
|
||||
%description
|
||||
@CPACK_RPM_PACKAGE_DESCRIPTION@
|
||||
|
||||
# This is a shortcutted spec file generated by CMake RPM generator
|
||||
# we skip _install step because CPack does that for us.
|
||||
# We do only save CPack installed tree in _prepr
|
||||
# and then restore it in build.
|
||||
%prep
|
||||
# Put the systemd unit where it is expected on this system
|
||||
mkdir -p $RPM_BUILD_ROOT/%{_unitdir}
|
||||
mv $RPM_BUILD_ROOT/lib/systemd/system/memgraph.service $RPM_BUILD_ROOT/%{_unitdir}
|
||||
rm -rf $RPM_BUILD_ROOT/lib
|
||||
# Fix the incorrect directory permissions set by cpack (this is fixed in CMake 3.11)
|
||||
find $RPM_BUILD_ROOT -type d | xargs chmod 755
|
||||
# After setting up custom prep, continue with CMake's default
|
||||
mv $RPM_BUILD_ROOT %_topdir/tmpBBroot
|
||||
|
||||
%install
|
||||
if [ -e $RPM_BUILD_ROOT ];
|
||||
then
|
||||
rm -rf $RPM_BUILD_ROOT
|
||||
fi
|
||||
mv %_topdir/tmpBBroot $RPM_BUILD_ROOT
|
||||
|
||||
@TMP_RPM_DEBUGINFO_INSTALL@
|
||||
|
||||
%clean
|
||||
|
||||
%post
|
||||
# memgraph user and group must be set in preinst
|
||||
chown memgraph:memgraph /var/lib/memgraph || exit 1
|
||||
chmod 750 /var/lib/memgraph || exit 1
|
||||
chown memgraph:adm /var/log/memgraph || exit 1
|
||||
chmod 750 /var/log/memgraph || exit 1
|
||||
|
||||
# Generate SSL certificates
|
||||
if [ ! -d /etc/memgraph/ssl ]; then
|
||||
mkdir /etc/memgraph/ssl || exit 1
|
||||
openssl req -x509 -newkey rsa:4096 -days 3650 -nodes \
|
||||
-keyout /etc/memgraph/ssl/key.pem -out /etc/memgraph/ssl/cert.pem \
|
||||
-subj "/C=GB/ST=London/L=London/O=Memgraph Ltd./CN=Memgraph DB" || exit 1
|
||||
chown memgraph:memgraph /etc/memgraph/ssl/* || exit 1
|
||||
chmod 400 /etc/memgraph/ssl/* || exit 1
|
||||
fi
|
||||
@RPM_SYMLINK_POSTINSTALL@
|
||||
@CPACK_RPM_SPEC_POSTINSTALL@
|
||||
|
||||
%postun
|
||||
@CPACK_RPM_SPEC_POSTUNINSTALL@
|
||||
|
||||
%pre
|
||||
# Add the 'memgraph' user and group
|
||||
getent group memgraph >/dev/null || groupadd -r memgraph || exit 1
|
||||
getent passwd memgraph >/dev/null || \
|
||||
useradd -r -g memgraph -d /var/lib/memgraph -s /bin/bash memgraph || exit 1
|
||||
echo "Don't forget to switch to the 'memgraph' user to use Memgraph" || exit 1
|
||||
@CPACK_RPM_SPEC_PREINSTALL@
|
||||
|
||||
%preun
|
||||
@CPACK_RPM_SPEC_PREUNINSTALL@
|
||||
|
||||
%files
|
||||
%defattr(@TMP_DEFAULT_FILE_PERMISSIONS@,@TMP_DEFAULT_USER@,@TMP_DEFAULT_GROUP@,@TMP_DEFAULT_DIR_PERMISSIONS@)
|
||||
@CPACK_RPM_INSTALL_FILES@
|
||||
# Since we moved the memgraph.service file, declare it explicitly here.
|
||||
# NOTE: memgraph.service must not be marked as configuration file.
|
||||
%{_unitdir}/memgraph.service
|
||||
|
||||
# Override CPACK_RPM_ABSOLUTE_INSTALL_FILES with our %config(noreplace), cpack
|
||||
# uses plain %config.
|
||||
%config(noreplace) "/etc/memgraph/memgraph.conf"
|
||||
%config(noreplace) "/etc/logrotate.d/memgraph"
|
||||
|
||||
@CPACK_RPM_USER_INSTALL_FILES@
|
||||
|
||||
%changelog
|
||||
@CPACK_RPM_SPEC_CHANGELOG@
|
@ -14,9 +14,10 @@ add_subdirectory(integrations)
|
||||
add_subdirectory(query)
|
||||
add_subdirectory(slk)
|
||||
add_subdirectory(rpc)
|
||||
add_subdirectory(auth)
|
||||
|
||||
if (MG_ENTERPRISE)
|
||||
add_subdirectory(audit)
|
||||
add_subdirectory(auth)
|
||||
endif()
|
||||
|
||||
string(TOLOWER ${CMAKE_BUILD_TYPE} lower_build_type)
|
||||
@ -32,18 +33,14 @@ include_directories(${CMAKE_CURRENT_BINARY_DIR})
|
||||
set(mg_single_node_v2_sources
|
||||
glue/communication.cpp
|
||||
memgraph.cpp
|
||||
glue/auth.cpp
|
||||
)
|
||||
if (MG_ENTERPRISE)
|
||||
set(mg_single_node_v2_sources
|
||||
${mg_single_node_v2_sources}
|
||||
glue/auth.cpp)
|
||||
endif()
|
||||
|
||||
set(mg_single_node_v2_libs stdc++fs Threads::Threads
|
||||
telemetry_lib mg-query mg-communication mg-memory mg-utils)
|
||||
telemetry_lib mg-query mg-communication mg-memory mg-utils mg-auth)
|
||||
if (MG_ENTERPRISE)
|
||||
# These are enterprise subsystems
|
||||
set(mg_single_node_v2_libs ${mg_single_node_v2_libs} mg-auth mg-audit)
|
||||
set(mg_single_node_v2_libs ${mg_single_node_v2_libs} mg-audit)
|
||||
endif()
|
||||
|
||||
# memgraph main executable
|
||||
@ -109,13 +106,9 @@ install(FILES ${CMAKE_SOURCE_DIR}/include/mg_procedure.h
|
||||
install(FILES ${CMAKE_BINARY_DIR}/config/memgraph.conf
|
||||
DESTINATION /etc/memgraph RENAME memgraph.conf)
|
||||
# Install logrotate configuration (must use absolute path).
|
||||
if (MG_ENTERPRISE)
|
||||
install(FILES ${CMAKE_SOURCE_DIR}/release/logrotate_enterprise.conf
|
||||
install(FILES ${CMAKE_SOURCE_DIR}/release/logrotate.conf
|
||||
DESTINATION /etc/logrotate.d RENAME memgraph)
|
||||
else()
|
||||
install(FILES ${CMAKE_SOURCE_DIR}/release/logrotate_community.conf
|
||||
DESTINATION /etc/logrotate.d RENAME memgraph)
|
||||
endif()
|
||||
|
||||
# Create empty directories for default location of lib and log.
|
||||
install(CODE "file(MAKE_DIRECTORY \$ENV{DESTDIR}/var/log/memgraph
|
||||
\$ENV{DESTDIR}/var/lib/memgraph)")
|
||||
|
@ -9,7 +9,9 @@
|
||||
|
||||
#include "auth/exceptions.hpp"
|
||||
#include "utils/flag_validation.hpp"
|
||||
#include "utils/license.hpp"
|
||||
#include "utils/logging.hpp"
|
||||
#include "utils/settings.hpp"
|
||||
#include "utils/string.hpp"
|
||||
|
||||
DEFINE_VALIDATED_string(auth_module_executable, "", "Absolute path to the auth module executable that should be used.",
|
||||
@ -58,6 +60,13 @@ Auth::Auth(const std::string &storage_directory) : storage_(storage_directory),
|
||||
|
||||
std::optional<User> Auth::Authenticate(const std::string &username, const std::string &password) {
|
||||
if (module_.IsUsed()) {
|
||||
const auto license_check_result = utils::license::global_license_checker.IsValidLicense(utils::global_settings);
|
||||
if (license_check_result.HasError()) {
|
||||
spdlog::warn(
|
||||
utils::license::LicenseCheckErrorToString(license_check_result.GetError(), "authentication modules"));
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
nlohmann::json params = nlohmann::json::object();
|
||||
params["username"] = username;
|
||||
params["password"] = password;
|
||||
|
@ -8,6 +8,7 @@
|
||||
#include "auth/models.hpp"
|
||||
#include "auth/module.hpp"
|
||||
#include "kvstore/kvstore.hpp"
|
||||
#include "utils/settings.hpp"
|
||||
|
||||
namespace auth {
|
||||
|
||||
@ -20,7 +21,7 @@ namespace auth {
|
||||
*/
|
||||
class Auth final {
|
||||
public:
|
||||
Auth(const std::string &storage_directory);
|
||||
explicit Auth(const std::string &storage_directory);
|
||||
|
||||
/**
|
||||
* Authenticates a user using his username and password.
|
||||
|
@ -7,11 +7,16 @@
|
||||
#include "auth/crypto.hpp"
|
||||
#include "auth/exceptions.hpp"
|
||||
#include "utils/cast.hpp"
|
||||
#include "utils/license.hpp"
|
||||
#include "utils/settings.hpp"
|
||||
#include "utils/string.hpp"
|
||||
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
DEFINE_bool(auth_password_permit_null, true, "Set to false to disable null passwords.");
|
||||
|
||||
DEFINE_string(auth_password_strength_regex, ".+",
|
||||
constexpr std::string_view default_password_regex = ".+";
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
DEFINE_string(auth_password_strength_regex, default_password_regex.data(),
|
||||
"The regular expression that should be used to match the entire "
|
||||
"entered password to ensure its strength.");
|
||||
|
||||
@ -80,7 +85,8 @@ PermissionLevel Permissions::Has(Permission permission) const {
|
||||
// Check for the deny first because it has greater priority than a grant.
|
||||
if (denies_ & utils::UnderlyingCast(permission)) {
|
||||
return PermissionLevel::DENY;
|
||||
} else if (grants_ & utils::UnderlyingCast(permission)) {
|
||||
}
|
||||
if (grants_ & utils::UnderlyingCast(permission)) {
|
||||
return PermissionLevel::GRANT;
|
||||
}
|
||||
return PermissionLevel::NEUTRAL;
|
||||
@ -190,26 +196,38 @@ User::User(const std::string &username, const std::string &password_hash, const
|
||||
: username_(utils::ToLowerCase(username)), password_hash_(password_hash), permissions_(permissions) {}
|
||||
|
||||
bool User::CheckPassword(const std::string &password) {
|
||||
if (password_hash_ == "") return true;
|
||||
if (password_hash_.empty()) return true;
|
||||
return VerifyPassword(password, password_hash_);
|
||||
}
|
||||
|
||||
void User::UpdatePassword(const std::optional<std::string> &password) {
|
||||
if (password) {
|
||||
std::regex re(FLAGS_auth_password_strength_regex);
|
||||
if (!std::regex_match(*password, re)) {
|
||||
throw AuthException(
|
||||
"The user password doesn't conform to the required strength! Regex: "
|
||||
"{}",
|
||||
FLAGS_auth_password_strength_regex);
|
||||
}
|
||||
password_hash_ = EncryptPassword(*password);
|
||||
} else {
|
||||
if (!password) {
|
||||
if (!FLAGS_auth_password_permit_null) {
|
||||
throw AuthException("Null passwords aren't permitted!");
|
||||
}
|
||||
password_hash_ = "";
|
||||
return;
|
||||
}
|
||||
|
||||
if (FLAGS_auth_password_strength_regex != default_password_regex) {
|
||||
if (const auto license_check_result = utils::license::global_license_checker.IsValidLicense(utils::global_settings);
|
||||
license_check_result.HasError()) {
|
||||
throw AuthException(
|
||||
"Custom password regex is a Memgraph Enterprise feature. Please set the config "
|
||||
"(\"--auth-password-strength-regex\") to its default value (\"{}\") or remove the flag.\n{}",
|
||||
default_password_regex,
|
||||
utils::license::LicenseCheckErrorToString(license_check_result.GetError(), "password regex"));
|
||||
}
|
||||
}
|
||||
std::regex re(FLAGS_auth_password_strength_regex);
|
||||
if (!std::regex_match(*password, re)) {
|
||||
throw AuthException(
|
||||
"The user password doesn't conform to the required strength! Regex: "
|
||||
"\"{}\"",
|
||||
FLAGS_auth_password_strength_regex);
|
||||
}
|
||||
|
||||
password_hash_ = EncryptPassword(*password);
|
||||
}
|
||||
|
||||
void User::SetRole(const Role &role) { role_.emplace(role); }
|
||||
|
173
src/memgraph.cpp
173
src/memgraph.cpp
@ -26,6 +26,7 @@
|
||||
#include "query/auth_checker.hpp"
|
||||
#include "query/discard_value_stream.hpp"
|
||||
#include "query/exceptions.hpp"
|
||||
#include "query/frontend/ast/ast.hpp"
|
||||
#include "query/interpreter.hpp"
|
||||
#include "query/plan/operator.hpp"
|
||||
#include "query/procedure/module.hpp"
|
||||
@ -38,10 +39,12 @@
|
||||
#include "utils/event_counter.hpp"
|
||||
#include "utils/file.hpp"
|
||||
#include "utils/flag_validation.hpp"
|
||||
#include "utils/license.hpp"
|
||||
#include "utils/logging.hpp"
|
||||
#include "utils/memory_tracker.hpp"
|
||||
#include "utils/readable_size.hpp"
|
||||
#include "utils/rw_lock.hpp"
|
||||
#include "utils/settings.hpp"
|
||||
#include "utils/signals.hpp"
|
||||
#include "utils/string.hpp"
|
||||
#include "utils/synchronized.hpp"
|
||||
@ -67,10 +70,11 @@
|
||||
#include "communication/session.hpp"
|
||||
#include "glue/communication.hpp"
|
||||
|
||||
#ifdef MG_ENTERPRISE
|
||||
#include "audit/log.hpp"
|
||||
#include "auth/auth.hpp"
|
||||
#include "glue/auth.hpp"
|
||||
|
||||
#ifdef MG_ENTERPRISE
|
||||
#include "audit/log.hpp"
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
@ -351,12 +355,18 @@ void ConfigureLogging() {
|
||||
}
|
||||
} // namespace
|
||||
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
DEFINE_string(license_key, "", "License key for Memgraph Enterprise.");
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
DEFINE_string(organization_name, "", "Organization name.");
|
||||
|
||||
/// Encapsulates Dbms and Interpreter that are passed through the network server
|
||||
/// and worker to the session.
|
||||
#ifdef MG_ENTERPRISE
|
||||
struct SessionData {
|
||||
// Explicit constructor here to ensure that pointers to all objects are
|
||||
// supplied.
|
||||
#if MG_ENTERPRISE
|
||||
|
||||
SessionData(storage::Storage *db, query::InterpreterContext *interpreter_context,
|
||||
utils::Synchronized<auth::Auth, utils::WritePrioritizedRWLock> *auth, audit::Log *audit_log)
|
||||
: db(db), interpreter_context(interpreter_context), auth(auth), audit_log(audit_log) {}
|
||||
@ -364,26 +374,62 @@ struct SessionData {
|
||||
query::InterpreterContext *interpreter_context;
|
||||
utils::Synchronized<auth::Auth, utils::WritePrioritizedRWLock> *auth;
|
||||
audit::Log *audit_log;
|
||||
|
||||
#else
|
||||
|
||||
SessionData(storage::Storage *db, query::InterpreterContext *interpreter_context,
|
||||
utils::Synchronized<auth::Auth, utils::WritePrioritizedRWLock> *auth)
|
||||
: db(db), interpreter_context(interpreter_context), auth(auth) {}
|
||||
storage::Storage *db;
|
||||
query::InterpreterContext *interpreter_context;
|
||||
utils::Synchronized<auth::Auth, utils::WritePrioritizedRWLock> *auth;
|
||||
|
||||
#endif
|
||||
};
|
||||
|
||||
DEFINE_string(auth_user_or_role_name_regex, "[a-zA-Z0-9_.+-@]+",
|
||||
constexpr std::string_view default_user_role_regex = "[a-zA-Z0-9_.+-@]+";
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
DEFINE_string(auth_user_or_role_name_regex, default_user_role_regex.data(),
|
||||
"Set to the regular expression that each user or role name must fulfill.");
|
||||
|
||||
class AuthQueryHandler final : public query::AuthQueryHandler {
|
||||
utils::Synchronized<auth::Auth, utils::WritePrioritizedRWLock> *auth_;
|
||||
std::string name_regex_string_;
|
||||
std::regex name_regex_;
|
||||
|
||||
public:
|
||||
AuthQueryHandler(utils::Synchronized<auth::Auth, utils::WritePrioritizedRWLock> *auth, const std::regex &name_regex)
|
||||
: auth_(auth), name_regex_(name_regex) {}
|
||||
AuthQueryHandler(utils::Synchronized<auth::Auth, utils::WritePrioritizedRWLock> *auth, std::string name_regex_string)
|
||||
: auth_(auth), name_regex_string_(std::move(name_regex_string)), name_regex_(name_regex_string_) {}
|
||||
|
||||
bool CreateUser(const std::string &username, const std::optional<std::string> &password) override {
|
||||
if (name_regex_string_ != default_user_role_regex) {
|
||||
if (const auto license_check_result =
|
||||
utils::license::global_license_checker.IsValidLicense(utils::global_settings);
|
||||
license_check_result.HasError()) {
|
||||
throw auth::AuthException(
|
||||
"Custom user/role regex is a Memgraph Enterprise feature. Please set the config "
|
||||
"(\"--auth-user-or-role-name-regex\") to its default value (\"{}\") or remove the flag.\n{}",
|
||||
default_user_role_regex,
|
||||
utils::license::LicenseCheckErrorToString(license_check_result.GetError(), "user/role regex"));
|
||||
}
|
||||
}
|
||||
if (!std::regex_match(username, name_regex_)) {
|
||||
throw query::QueryRuntimeException("Invalid user name.");
|
||||
}
|
||||
try {
|
||||
auto locked_auth = auth_->Lock();
|
||||
return locked_auth->AddUser(username, password).has_value();
|
||||
const auto [first_user, user_added] = std::invoke([&, this] {
|
||||
auto locked_auth = auth_->Lock();
|
||||
const auto first_user = !locked_auth->HasUsers();
|
||||
const auto user_added = locked_auth->AddUser(username, password).has_value();
|
||||
return std::make_pair(first_user, user_added);
|
||||
});
|
||||
|
||||
if (first_user) {
|
||||
spdlog::info("{} is first created user. Granting all privileges.", username);
|
||||
GrantPrivilege(username, query::kPrivilegesAll);
|
||||
}
|
||||
|
||||
return user_added;
|
||||
} catch (const auth::AuthException &e) {
|
||||
throw query::QueryRuntimeException(e.what());
|
||||
}
|
||||
@ -663,12 +709,12 @@ class AuthQueryHandler final : public query::AuthQueryHandler {
|
||||
throw query::QueryRuntimeException("Invalid user or role name.");
|
||||
}
|
||||
try {
|
||||
auto locked_auth = auth_->Lock();
|
||||
std::vector<auth::Permission> permissions;
|
||||
permissions.reserve(privileges.size());
|
||||
for (const auto &privilege : privileges) {
|
||||
permissions.push_back(glue::PrivilegeToPermission(privilege));
|
||||
}
|
||||
auto locked_auth = auth_->Lock();
|
||||
auto user = locked_auth->GetUser(user_or_role);
|
||||
auto role = locked_auth->GetRole(user_or_role);
|
||||
if (!user && !role) {
|
||||
@ -722,64 +768,6 @@ class AuthChecker final : public query::AuthChecker {
|
||||
utils::Synchronized<auth::Auth, utils::WritePrioritizedRWLock> *auth_;
|
||||
};
|
||||
|
||||
#else
|
||||
|
||||
struct SessionData {
|
||||
// Explicit constructor here to ensure that pointers to all objects are
|
||||
// supplied.
|
||||
SessionData(storage::Storage *db, query::InterpreterContext *interpreter_context)
|
||||
: db(db), interpreter_context(interpreter_context) {}
|
||||
storage::Storage *db;
|
||||
query::InterpreterContext *interpreter_context;
|
||||
};
|
||||
|
||||
class NoAuthInCommunity : public query::QueryRuntimeException {
|
||||
public:
|
||||
NoAuthInCommunity()
|
||||
: query::QueryRuntimeException::QueryRuntimeException("Auth is not supported in Memgraph Community!") {}
|
||||
};
|
||||
|
||||
class AuthQueryHandler final : public query::AuthQueryHandler {
|
||||
public:
|
||||
bool CreateUser(const std::string &, const std::optional<std::string> &) override { throw NoAuthInCommunity(); }
|
||||
|
||||
bool DropUser(const std::string &) override { throw NoAuthInCommunity(); }
|
||||
|
||||
void SetPassword(const std::string &, const std::optional<std::string> &) override { throw NoAuthInCommunity(); }
|
||||
|
||||
bool CreateRole(const std::string &) override { throw NoAuthInCommunity(); }
|
||||
|
||||
bool DropRole(const std::string &) override { throw NoAuthInCommunity(); }
|
||||
|
||||
std::vector<query::TypedValue> GetUsernames() override { throw NoAuthInCommunity(); }
|
||||
|
||||
std::vector<query::TypedValue> GetRolenames() override { throw NoAuthInCommunity(); }
|
||||
|
||||
std::optional<std::string> GetRolenameForUser(const std::string &) override { throw NoAuthInCommunity(); }
|
||||
|
||||
std::vector<query::TypedValue> GetUsernamesForRole(const std::string &) override { throw NoAuthInCommunity(); }
|
||||
|
||||
void SetRole(const std::string &, const std::string &) override { throw NoAuthInCommunity(); }
|
||||
|
||||
void ClearRole(const std::string &) override { throw NoAuthInCommunity(); }
|
||||
|
||||
std::vector<std::vector<query::TypedValue>> GetPrivileges(const std::string &) override { throw NoAuthInCommunity(); }
|
||||
|
||||
void GrantPrivilege(const std::string &, const std::vector<query::AuthQuery::Privilege> &) override {
|
||||
throw NoAuthInCommunity();
|
||||
}
|
||||
|
||||
void DenyPrivilege(const std::string &, const std::vector<query::AuthQuery::Privilege> &) override {
|
||||
throw NoAuthInCommunity();
|
||||
}
|
||||
|
||||
void RevokePrivilege(const std::string &, const std::vector<query::AuthQuery::Privilege> &) override {
|
||||
throw NoAuthInCommunity();
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
class BoltSession final : public communication::bolt::Session<communication::InputStream, communication::OutputStream> {
|
||||
public:
|
||||
BoltSession(SessionData *data, const io::network::Endpoint &endpoint, communication::InputStream *input_stream,
|
||||
@ -788,8 +776,8 @@ class BoltSession final : public communication::bolt::Session<communication::Inp
|
||||
output_stream),
|
||||
db_(data->db),
|
||||
interpreter_(data->interpreter_context),
|
||||
#ifdef MG_ENTERPRISE
|
||||
auth_(data->auth),
|
||||
#if MG_ENTERPRISE
|
||||
audit_log_(data->audit_log),
|
||||
#endif
|
||||
endpoint_(endpoint) {
|
||||
@ -808,22 +796,22 @@ class BoltSession final : public communication::bolt::Session<communication::Inp
|
||||
std::map<std::string, storage::PropertyValue> params_pv;
|
||||
for (const auto &kv : params) params_pv.emplace(kv.first, glue::ToPropertyValue(kv.second));
|
||||
const std::string *username{nullptr};
|
||||
#ifdef MG_ENTERPRISE
|
||||
if (user_) {
|
||||
username = &user_->username();
|
||||
}
|
||||
audit_log_->Record(endpoint_.address, user_ ? *username : "", query, storage::PropertyValue(params_pv));
|
||||
#ifdef MG_ENTERPRISE
|
||||
if (utils::license::global_license_checker.IsValidLicenseFast()) {
|
||||
audit_log_->Record(endpoint_.address, user_ ? *username : "", query, storage::PropertyValue(params_pv));
|
||||
}
|
||||
#endif
|
||||
try {
|
||||
auto result = interpreter_.Prepare(query, params_pv, username);
|
||||
#ifdef MG_ENTERPRISE
|
||||
if (user_ && !AuthChecker::IsUserAuthorized(*user_, result.privileges)) {
|
||||
interpreter_.Abort();
|
||||
throw communication::bolt::ClientError(
|
||||
"You are not authorized to execute this query! Please contact "
|
||||
"your database administrator.");
|
||||
}
|
||||
#endif
|
||||
return {result.headers, result.qid};
|
||||
|
||||
} catch (const query::QueryException &e) {
|
||||
@ -847,16 +835,12 @@ class BoltSession final : public communication::bolt::Session<communication::Inp
|
||||
void Abort() override { interpreter_.Abort(); }
|
||||
|
||||
bool Authenticate(const std::string &username, const std::string &password) override {
|
||||
#ifdef MG_ENTERPRISE
|
||||
auto locked_auth = auth_->Lock();
|
||||
if (!locked_auth->HasUsers()) {
|
||||
return true;
|
||||
}
|
||||
user_ = locked_auth->Authenticate(username, password);
|
||||
return user_.has_value();
|
||||
#else
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
std::optional<std::string> GetServerNameForInit() override {
|
||||
@ -930,9 +914,9 @@ class BoltSession final : public communication::bolt::Session<communication::Inp
|
||||
// NOTE: Needed only for ToBoltValue conversions
|
||||
const storage::Storage *db_;
|
||||
query::Interpreter interpreter_;
|
||||
#ifdef MG_ENTERPRISE
|
||||
utils::Synchronized<auth::Auth, utils::WritePrioritizedRWLock> *auth_;
|
||||
std::optional<auth::User> user_;
|
||||
#ifdef MG_ENTERPRISE
|
||||
audit::Log *audit_log_;
|
||||
#endif
|
||||
io::network::Endpoint endpoint_;
|
||||
@ -1041,7 +1025,25 @@ int main(int argc, char **argv) {
|
||||
|
||||
auto data_directory = std::filesystem::path(FLAGS_data_directory);
|
||||
|
||||
#ifdef MG_ENTERPRISE
|
||||
const auto memory_limit = GetMemoryLimit();
|
||||
// NOLINTNEXTLINE(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions)
|
||||
spdlog::info("Memory limit in config is set to {}", utils::GetReadableSize(memory_limit));
|
||||
utils::total_memory_tracker.SetMaximumHardLimit(memory_limit);
|
||||
utils::total_memory_tracker.SetHardLimit(memory_limit);
|
||||
|
||||
utils::global_settings.Initialize(data_directory / "settings");
|
||||
utils::OnScopeExit settings_finalizer([&] { utils::global_settings.Finalize(); });
|
||||
|
||||
// register all runtime settings
|
||||
utils::license::RegisterLicenseSettings(utils::license::global_license_checker, utils::global_settings);
|
||||
|
||||
utils::license::global_license_checker.CheckEnvLicense();
|
||||
if (!FLAGS_organization_name.empty() && !FLAGS_license_key.empty()) {
|
||||
utils::license::global_license_checker.SetLicenseInfoOverride(FLAGS_license_key, FLAGS_organization_name);
|
||||
}
|
||||
|
||||
utils::license::global_license_checker.StartBackgroundLicenseChecker(utils::global_settings);
|
||||
|
||||
// All enterprise features should be constructed before the main database
|
||||
// storage. This will cause them to be destructed *after* the main database
|
||||
// storage. That way any errors that happen during enterprise features
|
||||
@ -1056,6 +1058,7 @@ int main(int argc, char **argv) {
|
||||
// Auth
|
||||
utils::Synchronized<auth::Auth, utils::WritePrioritizedRWLock> auth{data_directory / "auth"};
|
||||
|
||||
#ifdef MG_ENTERPRISE
|
||||
// Audit log
|
||||
audit::Log audit_log{data_directory / "audit", FLAGS_audit_buffer_size, FLAGS_audit_buffer_flush_interval_ms};
|
||||
// Start the log if enabled.
|
||||
@ -1070,10 +1073,6 @@ int main(int argc, char **argv) {
|
||||
// End enterprise features initialization
|
||||
#endif
|
||||
|
||||
const auto memory_limit = GetMemoryLimit();
|
||||
spdlog::info("Memory limit set to {}", utils::GetReadableSize(memory_limit));
|
||||
utils::total_memory_tracker.SetHardLimit(memory_limit);
|
||||
|
||||
// Main storage and execution engines initialization
|
||||
storage::Config db_config{
|
||||
.gc = {.type = storage::Config::Gc::Type::PERIODIC, .interval = std::chrono::seconds(FLAGS_storage_gc_cycle_sec)},
|
||||
@ -1102,6 +1101,7 @@ int main(int argc, char **argv) {
|
||||
db_config.durability.snapshot_interval = std::chrono::seconds(FLAGS_storage_snapshot_interval_sec);
|
||||
}
|
||||
storage::Storage db(db_config);
|
||||
|
||||
query::InterpreterContext interpreter_context{
|
||||
&db,
|
||||
{.query = {.allow_load_csv = FLAGS_allow_load_csv}, .execution_timeout_sec = FLAGS_query_execution_timeout_sec},
|
||||
@ -1110,7 +1110,7 @@ int main(int argc, char **argv) {
|
||||
#ifdef MG_ENTERPRISE
|
||||
SessionData session_data{&db, &interpreter_context, &auth, &audit_log};
|
||||
#else
|
||||
SessionData session_data{&db, &interpreter_context};
|
||||
SessionData session_data{&db, &interpreter_context, &auth};
|
||||
#endif
|
||||
|
||||
query::procedure::gModuleRegistry.SetModulesDirectory(query_modules_directories);
|
||||
@ -1119,13 +1119,8 @@ int main(int argc, char **argv) {
|
||||
// As the Stream transformations are using modules, they have to be restored after the query modules are loaded.
|
||||
interpreter_context.streams.RestoreStreams();
|
||||
|
||||
#ifdef MG_ENTERPRISE
|
||||
AuthQueryHandler auth_handler(&auth, std::regex(FLAGS_auth_user_or_role_name_regex));
|
||||
AuthQueryHandler auth_handler(&auth, FLAGS_auth_user_or_role_name_regex);
|
||||
AuthChecker auth_checker{&auth};
|
||||
#else
|
||||
AuthQueryHandler auth_handler;
|
||||
query::AllowEverythingAuthChecker auth_checker{};
|
||||
#endif
|
||||
interpreter_context.auth = &auth_handler;
|
||||
interpreter_context.auth_checker = &auth_checker;
|
||||
|
||||
|
@ -188,4 +188,10 @@ class CreateSnapshotInMulticommandTxException final : public QueryException {
|
||||
CreateSnapshotInMulticommandTxException()
|
||||
: QueryException("Snapshot cannot be created in multicommand transactions.") {}
|
||||
};
|
||||
|
||||
class SettingConfigInMulticommandTxException final : public QueryException {
|
||||
public:
|
||||
SettingConfigInMulticommandTxException()
|
||||
: QueryException("Settings cannot be changed or fetched in multicommand transactions.") {}
|
||||
};
|
||||
} // namespace query
|
||||
|
@ -2521,4 +2521,25 @@ cpp<#
|
||||
(:serialize (:slk))
|
||||
(:clone))
|
||||
|
||||
(lcp:define-class setting-query (query)
|
||||
((action "Action" :scope :public)
|
||||
(setting_name "Expression *" :initval "nullptr" :scope :public)
|
||||
(setting_value "Expression *" :initval "nullptr" :scope :public))
|
||||
|
||||
(:public
|
||||
(lcp:define-enum action
|
||||
(show-setting show-all-settings set-setting)
|
||||
(:serialize))
|
||||
#>cpp
|
||||
SettingQuery() = default;
|
||||
|
||||
DEFVISITABLE(QueryVisitor<void>);
|
||||
cpp<#)
|
||||
(:private
|
||||
#>cpp
|
||||
friend class AstStorage;
|
||||
cpp<#)
|
||||
(:serialize (:slk))
|
||||
(:clone))
|
||||
|
||||
(lcp:pop-namespace) ;; namespace query
|
||||
|
@ -80,6 +80,7 @@ class TriggerQuery;
|
||||
class IsolationLevelQuery;
|
||||
class CreateSnapshotQuery;
|
||||
class StreamQuery;
|
||||
class SettingQuery;
|
||||
|
||||
using TreeCompositeVisitor = ::utils::CompositeVisitor<
|
||||
SingleQuery, CypherUnion, NamedExpression, OrOperator, XorOperator, AndOperator, NotOperator, AdditionOperator,
|
||||
@ -114,6 +115,6 @@ template <class TResult>
|
||||
class QueryVisitor
|
||||
: public ::utils::Visitor<TResult, CypherQuery, ExplainQuery, ProfileQuery, IndexQuery, AuthQuery, InfoQuery,
|
||||
ConstraintQuery, DumpQuery, ReplicationQuery, LockPathQuery, FreeMemoryQuery,
|
||||
TriggerQuery, IsolationLevelQuery, CreateSnapshotQuery, StreamQuery> {};
|
||||
TriggerQuery, IsolationLevelQuery, CreateSnapshotQuery, StreamQuery, SettingQuery> {};
|
||||
|
||||
} // namespace query
|
||||
|
@ -575,6 +575,53 @@ antlrcpp::Any CypherMainVisitor::visitCheckStream(MemgraphCypher::CheckStreamCon
|
||||
return stream_query;
|
||||
}
|
||||
|
||||
antlrcpp::Any CypherMainVisitor::visitSettingQuery(MemgraphCypher::SettingQueryContext *ctx) {
|
||||
MG_ASSERT(ctx->children.size() == 1, "SettingQuery should have exactly one child!");
|
||||
auto *setting_query = ctx->children[0]->accept(this).as<SettingQuery *>();
|
||||
query_ = setting_query;
|
||||
return setting_query;
|
||||
}
|
||||
|
||||
antlrcpp::Any CypherMainVisitor::visitSetSetting(MemgraphCypher::SetSettingContext *ctx) {
|
||||
auto *setting_query = storage_->Create<SettingQuery>();
|
||||
setting_query->action_ = SettingQuery::Action::SET_SETTING;
|
||||
|
||||
if (!ctx->settingName()->literal()->StringLiteral()) {
|
||||
throw SemanticException("Setting name should be a string literal");
|
||||
}
|
||||
|
||||
if (!ctx->settingValue()->literal()->StringLiteral()) {
|
||||
throw SemanticException("Setting value should be a string literal");
|
||||
}
|
||||
|
||||
setting_query->setting_name_ = ctx->settingName()->accept(this);
|
||||
MG_ASSERT(setting_query->setting_name_);
|
||||
|
||||
setting_query->setting_value_ = ctx->settingValue()->accept(this);
|
||||
MG_ASSERT(setting_query->setting_value_);
|
||||
return setting_query;
|
||||
}
|
||||
|
||||
antlrcpp::Any CypherMainVisitor::visitShowSetting(MemgraphCypher::ShowSettingContext *ctx) {
|
||||
auto *setting_query = storage_->Create<SettingQuery>();
|
||||
setting_query->action_ = SettingQuery::Action::SHOW_SETTING;
|
||||
|
||||
if (!ctx->settingName()->literal()->StringLiteral()) {
|
||||
throw SemanticException("Setting name should be a string literal");
|
||||
}
|
||||
|
||||
setting_query->setting_name_ = ctx->settingName()->accept(this);
|
||||
MG_ASSERT(setting_query->setting_name_);
|
||||
|
||||
return setting_query;
|
||||
}
|
||||
|
||||
antlrcpp::Any CypherMainVisitor::visitShowSettings(MemgraphCypher::ShowSettingsContext * /*ctx*/) {
|
||||
auto *setting_query = storage_->Create<SettingQuery>();
|
||||
setting_query->action_ = SettingQuery::Action::SHOW_ALL_SETTINGS;
|
||||
return setting_query;
|
||||
}
|
||||
|
||||
antlrcpp::Any CypherMainVisitor::visitCypherUnion(MemgraphCypher::CypherUnionContext *ctx) {
|
||||
bool distinct = !ctx->ALL();
|
||||
auto *cypher_union = storage_->Create<CypherUnion>(distinct);
|
||||
|
@ -293,6 +293,26 @@ class CypherMainVisitor : public antlropencypher::MemgraphCypherBaseVisitor {
|
||||
*/
|
||||
antlrcpp::Any visitCheckStream(MemgraphCypher::CheckStreamContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return SettingQuery*
|
||||
*/
|
||||
antlrcpp::Any visitSettingQuery(MemgraphCypher::SettingQueryContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return SetSetting*
|
||||
*/
|
||||
antlrcpp::Any visitSetSetting(MemgraphCypher::SetSettingContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return ShowSetting*
|
||||
*/
|
||||
antlrcpp::Any visitShowSetting(MemgraphCypher::ShowSettingContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return ShowSettings*
|
||||
*/
|
||||
antlrcpp::Any visitShowSettings(MemgraphCypher::ShowSettingsContext *ctx) override;
|
||||
|
||||
/**
|
||||
* @return CypherUnion*
|
||||
*/
|
||||
|
@ -58,6 +58,8 @@ memgraphCypherKeyword : cypherKeyword
|
||||
| ROLES
|
||||
| QUOTE
|
||||
| SESSION
|
||||
| SETTING
|
||||
| SETTINGS
|
||||
| SNAPSHOT
|
||||
| START
|
||||
| STATS
|
||||
@ -98,6 +100,7 @@ query : cypherQuery
|
||||
| isolationLevelQuery
|
||||
| createSnapshotQuery
|
||||
| streamQuery
|
||||
| settingQuery
|
||||
;
|
||||
|
||||
authQuery : createRole
|
||||
@ -152,6 +155,11 @@ streamQuery : checkStream
|
||||
| showStreams
|
||||
;
|
||||
|
||||
settingQuery : setSetting
|
||||
| showSetting
|
||||
| showSettings
|
||||
;
|
||||
|
||||
loadCsv : LOAD CSV FROM csvFile ( WITH | NO ) HEADER
|
||||
( IGNORE BAD ) ?
|
||||
( DELIMITER delimiter ) ?
|
||||
@ -295,3 +303,13 @@ stopAllStreams : STOP ALL STREAMS ;
|
||||
showStreams : SHOW STREAMS ;
|
||||
|
||||
checkStream : CHECK STREAM streamName ( BATCH_LIMIT batchLimit=literal ) ? ( TIMEOUT timeout=literal ) ? ;
|
||||
|
||||
settingName : literal ;
|
||||
|
||||
settingValue : literal ;
|
||||
|
||||
setSetting : SET DATABASE SETTING settingName TO settingValue ;
|
||||
|
||||
showSetting : SHOW DATABASE SETTING settingName ;
|
||||
|
||||
showSettings : SHOW DATABASE SETTINGS ;
|
||||
|
@ -69,6 +69,8 @@ ROLE : R O L E ;
|
||||
ROLES : R O L E S ;
|
||||
QUOTE : Q U O T E ;
|
||||
SESSION : S E S S I O N ;
|
||||
SETTING : S E T T I N G ;
|
||||
SETTINGS : S E T T I N G S ;
|
||||
SNAPSHOT : S N A P S H O T ;
|
||||
START : S T A R T ;
|
||||
STATS : S T A T S ;
|
||||
|
@ -63,6 +63,8 @@ class PrivilegeExtractor : public QueryVisitor<void>, public HierarchicalTreeVis
|
||||
|
||||
void Visit(CreateSnapshotQuery &create_snapshot_query) override { AddPrivilege(AuthQuery::Privilege::DURABILITY); }
|
||||
|
||||
void Visit(SettingQuery & /*setting_query*/) override { AddPrivilege(AuthQuery::Privilege::CONFIG); }
|
||||
|
||||
bool PreVisit(Create & /*unused*/) override {
|
||||
AddPrivilege(AuthQuery::Privilege::CREATE);
|
||||
return false;
|
||||
|
@ -131,7 +131,8 @@ const trie::Trie kKeywords = {"union", "all",
|
||||
"batch_size", "consumer_group",
|
||||
"start", "stream",
|
||||
"streams", "transform",
|
||||
"topics", "check"};
|
||||
"topics", "check",
|
||||
"setting", "settings"};
|
||||
|
||||
// Unicode codepoints that are allowed at the start of the unescaped name.
|
||||
const std::bitset<kBitsetSize> kUnescapedNameAllowedStarts(
|
||||
|
@ -14,6 +14,7 @@
|
||||
#include "query/dump.hpp"
|
||||
#include "query/exceptions.hpp"
|
||||
#include "query/frontend/ast/ast.hpp"
|
||||
#include "query/frontend/ast/ast_visitor.hpp"
|
||||
#include "query/frontend/ast/cypher_main_visitor.hpp"
|
||||
#include "query/frontend/opencypher/parser.hpp"
|
||||
#include "query/frontend/semantic/required_privileges.hpp"
|
||||
@ -22,6 +23,7 @@
|
||||
#include "query/plan/planner.hpp"
|
||||
#include "query/plan/profile.hpp"
|
||||
#include "query/plan/vertex_count_cache.hpp"
|
||||
#include "query/streams.hpp"
|
||||
#include "query/trigger.hpp"
|
||||
#include "query/typed_value.hpp"
|
||||
#include "storage/v2/property_value.hpp"
|
||||
@ -30,11 +32,13 @@
|
||||
#include "utils/event_counter.hpp"
|
||||
#include "utils/exceptions.hpp"
|
||||
#include "utils/flag_validation.hpp"
|
||||
#include "utils/license.hpp"
|
||||
#include "utils/likely.hpp"
|
||||
#include "utils/logging.hpp"
|
||||
#include "utils/memory.hpp"
|
||||
#include "utils/memory_tracker.hpp"
|
||||
#include "utils/readable_size.hpp"
|
||||
#include "utils/settings.hpp"
|
||||
#include "utils/string.hpp"
|
||||
#include "utils/tsc.hpp"
|
||||
|
||||
@ -233,14 +237,34 @@ Callback HandleAuthQuery(AuthQuery *auth_query, AuthQueryHandler *auth, const Pa
|
||||
|
||||
Callback callback;
|
||||
|
||||
const auto license_check_result = utils::license::global_license_checker.IsValidLicense(utils::global_settings);
|
||||
|
||||
static const std::unordered_set enterprise_only_methods{
|
||||
AuthQuery::Action::CREATE_ROLE, AuthQuery::Action::DROP_ROLE, AuthQuery::Action::SET_ROLE,
|
||||
AuthQuery::Action::CLEAR_ROLE, AuthQuery::Action::GRANT_PRIVILEGE, AuthQuery::Action::DENY_PRIVILEGE,
|
||||
AuthQuery::Action::REVOKE_PRIVILEGE, AuthQuery::Action::SHOW_PRIVILEGES, AuthQuery::Action::SHOW_USERS_FOR_ROLE,
|
||||
AuthQuery::Action::SHOW_ROLE_FOR_USER};
|
||||
|
||||
if (license_check_result.HasError() && enterprise_only_methods.contains(auth_query->action_)) {
|
||||
throw utils::BasicException(
|
||||
utils::license::LicenseCheckErrorToString(license_check_result.GetError(), "advanced authentication features"));
|
||||
}
|
||||
|
||||
switch (auth_query->action_) {
|
||||
case AuthQuery::Action::CREATE_USER:
|
||||
callback.fn = [auth, username, password] {
|
||||
callback.fn = [auth, username, password, valid_enterprise_license = !license_check_result.HasError()] {
|
||||
MG_ASSERT(password.IsString() || password.IsNull());
|
||||
if (!auth->CreateUser(username, password.IsString() ? std::make_optional(std::string(password.ValueString()))
|
||||
: std::nullopt)) {
|
||||
throw QueryRuntimeException("User '{}' already exists.", username);
|
||||
}
|
||||
|
||||
// If the license is not valid we create users with admin access
|
||||
if (!valid_enterprise_license) {
|
||||
spdlog::warn("Granting all the privileges to {}.", username);
|
||||
auth->GrantPrivilege(username, kPrivilegesAll);
|
||||
}
|
||||
|
||||
return std::vector<std::vector<TypedValue>>();
|
||||
};
|
||||
return callback;
|
||||
@ -606,6 +630,87 @@ Callback HandleStreamQuery(StreamQuery *stream_query, const Parameters ¶mete
|
||||
}
|
||||
}
|
||||
|
||||
Callback HandleSettingQuery(SettingQuery *setting_query, const Parameters ¶meters, DbAccessor *db_accessor) {
|
||||
Frame frame(0);
|
||||
SymbolTable symbol_table;
|
||||
EvaluationContext evaluation_context;
|
||||
// TODO: MemoryResource for EvaluationContext, it should probably be passed as
|
||||
// the argument to Callback.
|
||||
evaluation_context.timestamp =
|
||||
std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch())
|
||||
.count();
|
||||
evaluation_context.parameters = parameters;
|
||||
ExpressionEvaluator evaluator(&frame, symbol_table, evaluation_context, db_accessor, storage::View::OLD);
|
||||
|
||||
Callback callback;
|
||||
switch (setting_query->action_) {
|
||||
case SettingQuery::Action::SET_SETTING: {
|
||||
const auto setting_name = EvaluateOptionalExpression(setting_query->setting_name_, &evaluator);
|
||||
if (!setting_name.IsString()) {
|
||||
throw utils::BasicException("Setting name should be a string literal");
|
||||
}
|
||||
|
||||
const auto setting_value = EvaluateOptionalExpression(setting_query->setting_value_, &evaluator);
|
||||
if (!setting_value.IsString()) {
|
||||
throw utils::BasicException("Setting value should be a string literal");
|
||||
}
|
||||
|
||||
callback.fn = [setting_name = std::string{setting_name.ValueString()},
|
||||
setting_value = std::string{setting_value.ValueString()}]() mutable {
|
||||
if (!utils::global_settings.SetValue(setting_name, setting_value)) {
|
||||
throw utils::BasicException("Unknown setting name '{}'", setting_name);
|
||||
}
|
||||
return std::vector<std::vector<TypedValue>>{};
|
||||
};
|
||||
return callback;
|
||||
}
|
||||
case SettingQuery::Action::SHOW_SETTING: {
|
||||
const auto setting_name = EvaluateOptionalExpression(setting_query->setting_name_, &evaluator);
|
||||
if (!setting_name.IsString()) {
|
||||
throw utils::BasicException("Setting name should be a string literal");
|
||||
}
|
||||
|
||||
callback.header = {"setting_value"};
|
||||
callback.fn = [setting_name = std::string{setting_name.ValueString()}] {
|
||||
auto maybe_value = utils::global_settings.GetValue(setting_name);
|
||||
if (!maybe_value) {
|
||||
throw utils::BasicException("Unknown setting name '{}'", setting_name);
|
||||
}
|
||||
std::vector<std::vector<TypedValue>> results;
|
||||
results.reserve(1);
|
||||
|
||||
std::vector<TypedValue> setting_value;
|
||||
setting_value.reserve(1);
|
||||
|
||||
setting_value.emplace_back(*maybe_value);
|
||||
results.push_back(std::move(setting_value));
|
||||
return results;
|
||||
};
|
||||
return callback;
|
||||
}
|
||||
case SettingQuery::Action::SHOW_ALL_SETTINGS: {
|
||||
callback.header = {"setting_name", "setting_value"};
|
||||
callback.fn = [] {
|
||||
auto all_settings = utils::global_settings.AllSettings();
|
||||
std::vector<std::vector<TypedValue>> results;
|
||||
results.reserve(all_settings.size());
|
||||
|
||||
for (const auto &[k, v] : all_settings) {
|
||||
std::vector<TypedValue> setting_info;
|
||||
setting_info.reserve(2);
|
||||
|
||||
setting_info.emplace_back(k);
|
||||
setting_info.emplace_back(v);
|
||||
results.push_back(std::move(setting_info));
|
||||
}
|
||||
|
||||
return results;
|
||||
};
|
||||
return callback;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Struct for lazy pulling from a vector
|
||||
struct PullPlanVector {
|
||||
explicit PullPlanVector(std::vector<std::vector<TypedValue>> values) : values_(std::move(values)) {}
|
||||
@ -1429,6 +1534,32 @@ PreparedQuery PrepareCreateSnapshotQuery(ParsedQuery parsed_query, bool in_expli
|
||||
RWType::NONE};
|
||||
}
|
||||
|
||||
PreparedQuery PrepareSettingQuery(ParsedQuery parsed_query, const bool in_explicit_transaction, DbAccessor *dba) {
|
||||
if (in_explicit_transaction) {
|
||||
throw SettingConfigInMulticommandTxException{};
|
||||
}
|
||||
|
||||
auto *setting_query = utils::Downcast<SettingQuery>(parsed_query.query);
|
||||
MG_ASSERT(setting_query);
|
||||
auto callback = HandleSettingQuery(setting_query, parsed_query.parameters, dba);
|
||||
|
||||
return PreparedQuery{std::move(callback.header), std::move(parsed_query.required_privileges),
|
||||
[callback_fn = std::move(callback.fn), pull_plan = std::shared_ptr<PullPlanVector>{nullptr}](
|
||||
AnyStream *stream, std::optional<int> n) mutable -> std::optional<QueryHandlerResult> {
|
||||
if (UNLIKELY(!pull_plan)) {
|
||||
pull_plan = std::make_shared<PullPlanVector>(callback_fn());
|
||||
}
|
||||
|
||||
if (pull_plan->Pull(stream, n)) {
|
||||
return QueryHandlerResult::COMMIT;
|
||||
}
|
||||
return std::nullopt;
|
||||
},
|
||||
RWType::NONE};
|
||||
// False positive report for the std::make_shared above
|
||||
// NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks)
|
||||
}
|
||||
|
||||
PreparedQuery PrepareInfoQuery(ParsedQuery parsed_query, bool in_explicit_transaction,
|
||||
std::map<std::string, TypedValue> *summary, InterpreterContext *interpreter_context,
|
||||
storage::Storage *db, utils::MemoryResource *execution_memory) {
|
||||
@ -1738,29 +1869,29 @@ Interpreter::PrepareResult Interpreter::Prepare(const std::string &query_string,
|
||||
trigger_context_collector_ ? &*trigger_context_collector_ : nullptr);
|
||||
} else if (utils::Downcast<ExplainQuery>(parsed_query.query)) {
|
||||
prepared_query = PrepareExplainQuery(std::move(parsed_query), &query_execution->summary, interpreter_context_,
|
||||
&*execution_db_accessor_, &query_execution->execution_memory);
|
||||
&*execution_db_accessor_, &query_execution->execution_memory_with_exception);
|
||||
} else if (utils::Downcast<ProfileQuery>(parsed_query.query)) {
|
||||
prepared_query =
|
||||
PrepareProfileQuery(std::move(parsed_query), in_explicit_transaction_, &query_execution->summary,
|
||||
interpreter_context_, &*execution_db_accessor_, &query_execution->execution_memory);
|
||||
prepared_query = PrepareProfileQuery(std::move(parsed_query), in_explicit_transaction_, &query_execution->summary,
|
||||
interpreter_context_, &*execution_db_accessor_,
|
||||
&query_execution->execution_memory_with_exception);
|
||||
} else if (utils::Downcast<DumpQuery>(parsed_query.query)) {
|
||||
prepared_query = PrepareDumpQuery(std::move(parsed_query), &query_execution->summary, &*execution_db_accessor_,
|
||||
&query_execution->execution_memory);
|
||||
} else if (utils::Downcast<IndexQuery>(parsed_query.query)) {
|
||||
prepared_query = PrepareIndexQuery(std::move(parsed_query), in_explicit_transaction_, &query_execution->summary,
|
||||
interpreter_context_, &query_execution->execution_memory);
|
||||
interpreter_context_, &query_execution->execution_memory_with_exception);
|
||||
} else if (utils::Downcast<AuthQuery>(parsed_query.query)) {
|
||||
prepared_query =
|
||||
PrepareAuthQuery(std::move(parsed_query), in_explicit_transaction_, &query_execution->summary,
|
||||
interpreter_context_, &*execution_db_accessor_, &query_execution->execution_memory);
|
||||
prepared_query = PrepareAuthQuery(std::move(parsed_query), in_explicit_transaction_, &query_execution->summary,
|
||||
interpreter_context_, &*execution_db_accessor_,
|
||||
&query_execution->execution_memory_with_exception);
|
||||
} else if (utils::Downcast<InfoQuery>(parsed_query.query)) {
|
||||
prepared_query =
|
||||
PrepareInfoQuery(std::move(parsed_query), in_explicit_transaction_, &query_execution->summary,
|
||||
interpreter_context_, interpreter_context_->db, &query_execution->execution_memory);
|
||||
prepared_query = PrepareInfoQuery(std::move(parsed_query), in_explicit_transaction_, &query_execution->summary,
|
||||
interpreter_context_, interpreter_context_->db,
|
||||
&query_execution->execution_memory_with_exception);
|
||||
} else if (utils::Downcast<ConstraintQuery>(parsed_query.query)) {
|
||||
prepared_query =
|
||||
PrepareConstraintQuery(std::move(parsed_query), in_explicit_transaction_, &query_execution->summary,
|
||||
interpreter_context_, &query_execution->execution_memory);
|
||||
interpreter_context_, &query_execution->execution_memory_with_exception);
|
||||
} else if (utils::Downcast<ReplicationQuery>(parsed_query.query)) {
|
||||
prepared_query = PrepareReplicationQuery(std::move(parsed_query), in_explicit_transaction_, interpreter_context_,
|
||||
&*execution_db_accessor_);
|
||||
@ -1781,6 +1912,8 @@ Interpreter::PrepareResult Interpreter::Prepare(const std::string &query_string,
|
||||
} else if (utils::Downcast<CreateSnapshotQuery>(parsed_query.query)) {
|
||||
prepared_query =
|
||||
PrepareCreateSnapshotQuery(std::move(parsed_query), in_explicit_transaction_, interpreter_context_);
|
||||
} else if (utils::Downcast<SettingQuery>(parsed_query.query)) {
|
||||
prepared_query = PrepareSettingQuery(std::move(parsed_query), in_explicit_transaction_, &*execution_db_accessor_);
|
||||
} else {
|
||||
LOG_FATAL("Should not get here -- unknown query type!");
|
||||
}
|
||||
|
@ -22,6 +22,7 @@
|
||||
#include "utils/event_counter.hpp"
|
||||
#include "utils/logging.hpp"
|
||||
#include "utils/memory.hpp"
|
||||
#include "utils/settings.hpp"
|
||||
#include "utils/skip_list.hpp"
|
||||
#include "utils/spin_lock.hpp"
|
||||
#include "utils/thread_pool.hpp"
|
||||
@ -269,8 +270,8 @@ class Interpreter final {
|
||||
private:
|
||||
struct QueryExecution {
|
||||
std::optional<PreparedQuery> prepared_query;
|
||||
utils::MonotonicBufferResource execution_monotonic_memory{kExecutionMemoryBlockSize};
|
||||
utils::ResourceWithOutOfMemoryException execution_memory{&execution_monotonic_memory};
|
||||
utils::MonotonicBufferResource execution_memory{kExecutionMemoryBlockSize};
|
||||
utils::ResourceWithOutOfMemoryException execution_memory_with_exception{&execution_memory};
|
||||
|
||||
std::map<std::string, TypedValue> summary;
|
||||
|
||||
@ -285,7 +286,7 @@ class Interpreter final {
|
||||
// destroy the prepared query which is using that instance
|
||||
// of execution memory.
|
||||
prepared_query.reset();
|
||||
execution_monotonic_memory.Release();
|
||||
execution_memory.Release();
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -1,12 +1,15 @@
|
||||
set(utils_src_files
|
||||
async_timer.cpp
|
||||
base64.cpp
|
||||
event_counter.cpp
|
||||
csv_parsing.cpp
|
||||
file.cpp
|
||||
file_locker.cpp
|
||||
license.cpp
|
||||
memory.cpp
|
||||
memory_tracker.cpp
|
||||
readable_size.cpp
|
||||
settings.cpp
|
||||
signals.cpp
|
||||
sysinfo/memory.cpp
|
||||
thread.cpp
|
||||
@ -14,4 +17,4 @@ set(utils_src_files
|
||||
uuid.cpp)
|
||||
|
||||
add_library(mg-utils STATIC ${utils_src_files})
|
||||
target_link_libraries(mg-utils stdc++fs Threads::Threads spdlog fmt gflags uuid rt)
|
||||
target_link_libraries(mg-utils mg-kvstore mg-slk stdc++fs Threads::Threads spdlog fmt gflags uuid rt)
|
||||
|
260
src/utils/base64.cpp
Normal file
260
src/utils/base64.cpp
Normal file
@ -0,0 +1,260 @@
|
||||
/*
|
||||
base64.cpp and base64.h
|
||||
base64 encoding and decoding with C++.
|
||||
More information at
|
||||
https://renenyffenegger.ch/notes/development/Base64/Encoding-and-decoding-base-64-with-cpp
|
||||
Version: 2.rc.08 (release candidate)
|
||||
Copyright (C) 2004-2017, 2020, 2021 René Nyffenegger
|
||||
This source code is provided 'as-is', without any express or implied
|
||||
warranty. In no event will the author be held liable for any damages
|
||||
arising from the use of this software.
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it
|
||||
freely, subject to the following restrictions:
|
||||
1. The origin of this source code must not be misrepresented; you must not
|
||||
claim that you wrote the original source code. If you use this source code
|
||||
in a product, an acknowledgment in the product documentation would be
|
||||
appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be
|
||||
misrepresented as being the original source code.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
René Nyffenegger rene.nyffenegger@adp-gmbh.ch
|
||||
*/
|
||||
|
||||
#include "base64.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <stdexcept>
|
||||
|
||||
namespace utils {
|
||||
namespace {
|
||||
//
|
||||
// Depending on the url parameter in base64_chars, one of
|
||||
// two sets of base64 characters needs to be chosen.
|
||||
// They differ in their last two characters.
|
||||
//
|
||||
constexpr std::array base64_chars = {
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
"abcdefghijklmnopqrstuvwxyz"
|
||||
"0123456789"
|
||||
"+/",
|
||||
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
"abcdefghijklmnopqrstuvwxyz"
|
||||
"0123456789"
|
||||
"-_"};
|
||||
|
||||
unsigned int pos_of_char(const unsigned char chr) {
|
||||
//
|
||||
// Return the position of chr within base64_encode()
|
||||
//
|
||||
|
||||
if (chr >= 'A' && chr <= 'Z') return chr - 'A';
|
||||
if (chr >= 'a' && chr <= 'z') return chr - 'a' + ('Z' - 'A') + 1;
|
||||
if (chr >= '0' && chr <= '9') return chr - '0' + ('Z' - 'A') + ('z' - 'a') + 2;
|
||||
if (chr == '+' || chr == '-')
|
||||
return 62; // Be liberal with input and accept both url ('-') and non-url ('+') base 64 characters (
|
||||
if (chr == '/' || chr == '_') return 63; // Ditto for '/' and '_'
|
||||
|
||||
//
|
||||
// 2020-10-23: Throw std::exception rather than const char*
|
||||
//(Pablo Martin-Gomez, https://github.com/Bouska)
|
||||
//
|
||||
throw std::runtime_error("Input is not valid base64-encoded data.");
|
||||
}
|
||||
|
||||
std::string insert_linebreaks(std::string str, size_t distance) {
|
||||
//
|
||||
// Provided by https://github.com/JomaCorpFX, adapted by me.
|
||||
//
|
||||
if (!str.length()) {
|
||||
return "";
|
||||
}
|
||||
|
||||
size_t pos = distance;
|
||||
|
||||
while (pos < str.size()) {
|
||||
str.insert(pos, "\n");
|
||||
pos += distance + 1;
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
template <typename String, unsigned int line_length>
|
||||
std::string encode_with_line_breaks(String s) {
|
||||
return insert_linebreaks(base64_encode(s, false), line_length);
|
||||
}
|
||||
|
||||
template <typename String>
|
||||
std::string encode_pem(String s) {
|
||||
return encode_with_line_breaks<String, 64>(s);
|
||||
}
|
||||
|
||||
template <typename String>
|
||||
std::string encode_mime(String s) {
|
||||
return encode_with_line_breaks<String, 76>(s);
|
||||
}
|
||||
|
||||
template <typename String>
|
||||
std::string encode(String s, bool url) {
|
||||
return base64_encode(reinterpret_cast<const unsigned char *>(s.data()), s.length(), url);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
std::string base64_encode(unsigned char const *bytes_to_encode, size_t in_len, bool url) {
|
||||
size_t len_encoded = (in_len + 2) / 3 * 4;
|
||||
|
||||
unsigned char trailing_char = url ? '.' : '=';
|
||||
|
||||
//
|
||||
// Choose set of base64 characters. They differ
|
||||
// for the last two positions, depending on the url
|
||||
// parameter.
|
||||
// A bool (as is the parameter url) is guaranteed
|
||||
// to evaluate to either 0 or 1 in C++ therefore,
|
||||
// the correct character set is chosen by subscripting
|
||||
// base64_chars with url.
|
||||
//
|
||||
const char *base64_chars_ = base64_chars[url];
|
||||
|
||||
std::string ret;
|
||||
ret.reserve(len_encoded);
|
||||
|
||||
unsigned int pos = 0;
|
||||
|
||||
while (pos < in_len) {
|
||||
// NOLINTNEXTLINE(hicpp-signed-bitwise)
|
||||
ret.push_back(base64_chars_[(bytes_to_encode[pos + 0] & 0xfc) >> 2]);
|
||||
|
||||
if (pos + 1 < in_len) {
|
||||
// NOLINTNEXTLINE(hicpp-signed-bitwise)
|
||||
ret.push_back(base64_chars_[((bytes_to_encode[pos + 0] & 0x03) << 4) + ((bytes_to_encode[pos + 1] & 0xf0) >> 4)]);
|
||||
|
||||
if (pos + 2 < in_len) {
|
||||
ret.push_back(
|
||||
// NOLINTNEXTLINE(hicpp-signed-bitwise)
|
||||
base64_chars_[((bytes_to_encode[pos + 1] & 0x0f) << 2) + ((bytes_to_encode[pos + 2] & 0xc0) >> 6)]);
|
||||
// NOLINTNEXTLINE(hicpp-signed-bitwise)
|
||||
ret.push_back(base64_chars_[bytes_to_encode[pos + 2] & 0x3f]);
|
||||
} else {
|
||||
// NOLINTNEXTLINE(hicpp-signed-bitwise)
|
||||
ret.push_back(base64_chars_[(bytes_to_encode[pos + 1] & 0x0f) << 2]);
|
||||
// NOLINTNEXTLINE(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions)
|
||||
ret.push_back(trailing_char);
|
||||
}
|
||||
} else {
|
||||
// NOLINTNEXTLINE(hicpp-signed-bitwise)
|
||||
ret.push_back(base64_chars_[(bytes_to_encode[pos + 0] & 0x03) << 4]);
|
||||
// NOLINTNEXTLINE(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions)
|
||||
ret.push_back(trailing_char);
|
||||
// NOLINTNEXTLINE(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions)
|
||||
ret.push_back(trailing_char);
|
||||
}
|
||||
|
||||
pos += 3;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
template <typename String>
|
||||
static std::string decode(String encoded_string, bool remove_linebreaks) {
|
||||
//
|
||||
// decode(…) is templated so that it can be used with String = const std::string&
|
||||
// or std::string_view (requires at least C++17)
|
||||
//
|
||||
|
||||
if (encoded_string.empty()) return std::string();
|
||||
|
||||
if (remove_linebreaks) {
|
||||
std::string copy(encoded_string);
|
||||
|
||||
copy.erase(std::remove(copy.begin(), copy.end(), '\n'), copy.end());
|
||||
|
||||
return base64_decode(copy, false);
|
||||
}
|
||||
|
||||
size_t length_of_string = encoded_string.length();
|
||||
size_t pos = 0;
|
||||
|
||||
//
|
||||
// The approximate length (bytes) of the decoded string might be one or
|
||||
// two bytes smaller, depending on the amount of trailing equal signs
|
||||
// in the encoded string. This approximation is needed to reserve
|
||||
// enough space in the string to be returned.
|
||||
//
|
||||
size_t approx_length_of_decoded_string = length_of_string / 4 * 3;
|
||||
std::string ret;
|
||||
ret.reserve(approx_length_of_decoded_string);
|
||||
|
||||
while (pos < length_of_string) {
|
||||
//
|
||||
// Iterate over encoded input string in chunks. The size of all
|
||||
// chunks except the last one is 4 bytes.
|
||||
//
|
||||
// The last chunk might be padded with equal signs or dots
|
||||
// in order to make it 4 bytes in size as well, but this
|
||||
// is not required as per RFC 2045.
|
||||
//
|
||||
// All chunks except the last one produce three output bytes.
|
||||
//
|
||||
// The last chunk produces at least one and up to three bytes.
|
||||
//
|
||||
|
||||
size_t pos_of_char_1 = pos_of_char(encoded_string[pos + 1]);
|
||||
|
||||
//
|
||||
// Emit the first output byte that is produced in each chunk:
|
||||
//
|
||||
// NOLINTNEXTLINE(hicpp-signed-bitwise)
|
||||
ret.push_back(static_cast<std::string::value_type>(((pos_of_char(encoded_string[pos + 0])) << 2) +
|
||||
((pos_of_char_1 & 0x30) >> 4))); // NOLINT(hicpp-signed-bitwise)
|
||||
|
||||
if ((pos + 2 <
|
||||
length_of_string) && // Check for data that is not padded with equal signs (which is allowed by RFC 2045)
|
||||
encoded_string[pos + 2] != '=' &&
|
||||
encoded_string[pos + 2] != '.' // accept URL-safe base 64 strings, too, so check for '.' also.
|
||||
) {
|
||||
//
|
||||
// Emit a chunk's second byte (which might not be produced in the last chunk).
|
||||
//
|
||||
unsigned int pos_of_char_2 = pos_of_char(encoded_string[pos + 2]);
|
||||
ret.push_back(
|
||||
// NOLINTNEXTLINE(hicpp-signed-bitwise)
|
||||
static_cast<std::string::value_type>(((pos_of_char_1 & 0x0f) << 4) + ((pos_of_char_2 & 0x3c) >> 2)));
|
||||
|
||||
if ((pos + 3 < length_of_string) && encoded_string[pos + 3] != '=' && encoded_string[pos + 3] != '.') {
|
||||
//
|
||||
// Emit a chunk's third byte (which might not be produced in the last chunk).
|
||||
//
|
||||
ret.push_back(
|
||||
// NOLINTNEXTLINE(hicpp-signed-bitwise)
|
||||
static_cast<std::string::value_type>(((pos_of_char_2 & 0x03) << 6) + pos_of_char(encoded_string[pos + 3])));
|
||||
}
|
||||
}
|
||||
|
||||
pos += 4;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::string base64_decode(std::string const &s, bool remove_linebreaks) { return decode(s, remove_linebreaks); }
|
||||
|
||||
std::string base64_encode(std::string const &s, bool url) { return encode(s, url); }
|
||||
|
||||
std::string base64_encode_pem(std::string const &s) { return encode_pem(s); }
|
||||
|
||||
std::string base64_encode_mime(std::string const &s) { return encode_mime(s); }
|
||||
|
||||
std::string base64_encode(std::string_view s, bool url) { return encode(s, url); }
|
||||
|
||||
std::string base64_encode_pem(std::string_view s) { return encode_pem(s); }
|
||||
|
||||
std::string base64_encode_mime(std::string_view s) { return encode_mime(s); }
|
||||
|
||||
std::string base64_decode(std::string_view s, bool remove_linebreaks) { return decode(s, remove_linebreaks); }
|
||||
|
||||
} // namespace utils
|
35
src/utils/base64.hpp
Normal file
35
src/utils/base64.hpp
Normal file
@ -0,0 +1,35 @@
|
||||
//
|
||||
// base64 encoding and decoding with C++.
|
||||
// Version: 2.rc.08 (release candidate)
|
||||
//
|
||||
|
||||
#ifndef BASE64_H_C0CE2A47_D10E_42C9_A27C_C883944E704A
|
||||
#define BASE64_H_C0CE2A47_D10E_42C9_A27C_C883944E704A
|
||||
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
namespace utils {
|
||||
std::string base64_encode(std::string const &s, bool url = false);
|
||||
std::string base64_encode_pem(std::string const &s);
|
||||
std::string base64_encode_mime(std::string const &s);
|
||||
|
||||
std::string base64_decode(std::string const &s, bool remove_linebreaks = false);
|
||||
std::string base64_encode(unsigned char const *, size_t len, bool url = false);
|
||||
|
||||
#if __cplusplus >= 201703L
|
||||
//
|
||||
// Interface with std::string_view rather than const std::string&
|
||||
// Requires C++17
|
||||
// Provided by Yannic Bonenberger (https://github.com/Yannic)
|
||||
//
|
||||
std::string base64_encode(std::string_view s, bool url = false);
|
||||
std::string base64_encode_pem(std::string_view s);
|
||||
std::string base64_encode_mime(std::string_view s);
|
||||
|
||||
std::string base64_decode(std::string_view s, bool remove_linebreaks = false);
|
||||
#endif // __cplusplus >= 201703L
|
||||
|
||||
} // namespace utils
|
||||
|
||||
#endif /* BASE64_H_C0CE2A47_D10E_42C9_A27C_C883944E704A */
|
284
src/utils/license.cpp
Normal file
284
src/utils/license.cpp
Normal file
@ -0,0 +1,284 @@
|
||||
#include "utils/license.hpp"
|
||||
|
||||
#include <atomic>
|
||||
#include <charconv>
|
||||
#include <chrono>
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "slk/serialization.hpp"
|
||||
#include "utils/base64.hpp"
|
||||
#include "utils/exceptions.hpp"
|
||||
#include "utils/logging.hpp"
|
||||
#include "utils/memory_tracker.hpp"
|
||||
#include "utils/settings.hpp"
|
||||
#include "utils/spin_lock.hpp"
|
||||
#include "utils/synchronized.hpp"
|
||||
|
||||
namespace utils::license {
|
||||
|
||||
namespace {
|
||||
constexpr std::string_view license_key_prefix = "mglk-";
|
||||
|
||||
std::optional<License> GetLicense(const std::string &license_key) {
|
||||
if (license_key.empty()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
static utils::Synchronized<std::pair<std::string, License>, utils::SpinLock> cached_license;
|
||||
{
|
||||
auto cache_locked = cached_license.Lock();
|
||||
const auto &[cached_key, license] = *cache_locked;
|
||||
if (cached_key == license_key) {
|
||||
return license;
|
||||
}
|
||||
}
|
||||
auto license = Decode(license_key);
|
||||
if (license) {
|
||||
auto cache_locked = cached_license.Lock();
|
||||
*cache_locked = std::make_pair(license_key, *license);
|
||||
}
|
||||
return license;
|
||||
}
|
||||
|
||||
LicenseCheckResult IsValidLicenseInternal(const License &license, const std::string &organization_name) {
|
||||
if (license.organization_name != organization_name) {
|
||||
return LicenseCheckError::INVALID_ORGANIZATION_NAME;
|
||||
}
|
||||
|
||||
const auto now =
|
||||
std::chrono::duration_cast<std::chrono::seconds>(std::chrono::system_clock::now().time_since_epoch()).count();
|
||||
|
||||
if (license.valid_until != 0 && now > license.valid_until) {
|
||||
return LicenseCheckError::EXPIRED_LICENSE;
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void RegisterLicenseSettings(LicenseChecker &license_checker, utils::Settings &settings) {
|
||||
settings.RegisterSetting(std::string{kEnterpriseLicenseSettingKey}, "",
|
||||
[&] { license_checker.RevalidateLicense(settings); });
|
||||
settings.RegisterSetting(std::string{kOrganizationNameSettingKey}, "",
|
||||
[&] { license_checker.RevalidateLicense(settings); });
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
LicenseChecker global_license_checker;
|
||||
|
||||
LicenseChecker::~LicenseChecker() { scheduler_.Stop(); }
|
||||
|
||||
std::pair<std::string, std::string> LicenseChecker::GetLicenseInfo(const utils::Settings &settings) const {
|
||||
if (license_info_override_) {
|
||||
spdlog::warn("Ignoring license info stored in the settings because a different source was specified.");
|
||||
return *license_info_override_;
|
||||
}
|
||||
|
||||
auto license_key = settings.GetValue(std::string{kEnterpriseLicenseSettingKey});
|
||||
MG_ASSERT(license_key, "License key is missing from the settings");
|
||||
|
||||
auto organization_name = settings.GetValue(std::string{kOrganizationNameSettingKey});
|
||||
MG_ASSERT(organization_name, "Organization name is missing from the settings");
|
||||
return std::make_pair(std::move(*license_key), std::move(*organization_name));
|
||||
}
|
||||
|
||||
void LicenseChecker::RevalidateLicense(const utils::Settings &settings) {
|
||||
const auto license_info = GetLicenseInfo(settings);
|
||||
RevalidateLicense(license_info.first, license_info.second);
|
||||
}
|
||||
|
||||
void LicenseChecker::RevalidateLicense(const std::string &license_key, const std::string &organization_name) {
|
||||
static utils::Synchronized<std::optional<int64_t>, utils::SpinLock> previous_memory_limit;
|
||||
const auto set_memory_limit = [](const auto memory_limit) {
|
||||
auto locked_previous_memory_limit_ptr = previous_memory_limit.Lock();
|
||||
auto &locked_previous_memory_limit = *locked_previous_memory_limit_ptr;
|
||||
if (!locked_previous_memory_limit || *locked_previous_memory_limit != memory_limit) {
|
||||
utils::total_memory_tracker.SetHardLimit(memory_limit);
|
||||
locked_previous_memory_limit = memory_limit;
|
||||
}
|
||||
};
|
||||
|
||||
if (enterprise_enabled_) [[unlikely]] {
|
||||
is_valid_.store(true, std::memory_order_relaxed);
|
||||
set_memory_limit(0);
|
||||
return;
|
||||
}
|
||||
|
||||
struct PreviousLicenseInfo {
|
||||
PreviousLicenseInfo(std::string license_key, std::string organization_name)
|
||||
: license_key(std::move(license_key)), organization_name(std::move(organization_name)) {}
|
||||
|
||||
std::string license_key;
|
||||
std::string organization_name;
|
||||
bool is_valid{false};
|
||||
};
|
||||
|
||||
static utils::Synchronized<std::optional<PreviousLicenseInfo>, utils::SpinLock> previous_license_info;
|
||||
|
||||
auto locked_previous_license_info_ptr = previous_license_info.Lock();
|
||||
auto &locked_previous_license_info = *locked_previous_license_info_ptr;
|
||||
const bool same_license_info = locked_previous_license_info &&
|
||||
locked_previous_license_info->license_key == license_key &&
|
||||
locked_previous_license_info->organization_name == organization_name;
|
||||
// If we already know it's invalid skip the check
|
||||
if (same_license_info && !locked_previous_license_info->is_valid) {
|
||||
return;
|
||||
}
|
||||
|
||||
locked_previous_license_info.emplace(license_key, organization_name);
|
||||
|
||||
const auto maybe_license = GetLicense(locked_previous_license_info->license_key);
|
||||
if (!maybe_license) {
|
||||
spdlog::warn(LicenseCheckErrorToString(LicenseCheckError::INVALID_LICENSE_KEY_STRING, "Enterprise features"));
|
||||
is_valid_.store(false, std::memory_order_relaxed);
|
||||
locked_previous_license_info->is_valid = false;
|
||||
set_memory_limit(0);
|
||||
return;
|
||||
}
|
||||
|
||||
const auto license_check_result =
|
||||
IsValidLicenseInternal(*maybe_license, locked_previous_license_info->organization_name);
|
||||
|
||||
if (license_check_result.HasError()) {
|
||||
spdlog::warn(LicenseCheckErrorToString(license_check_result.GetError(), "Enterprise features"));
|
||||
is_valid_.store(false, std::memory_order_relaxed);
|
||||
locked_previous_license_info->is_valid = false;
|
||||
set_memory_limit(0);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!same_license_info) {
|
||||
spdlog::info("All Enterprise features are active.");
|
||||
is_valid_.store(true, std::memory_order_relaxed);
|
||||
locked_previous_license_info->is_valid = true;
|
||||
set_memory_limit(maybe_license->memory_limit);
|
||||
}
|
||||
}
|
||||
|
||||
void LicenseChecker::EnableTesting() {
|
||||
enterprise_enabled_ = true;
|
||||
is_valid_.store(true, std::memory_order_relaxed);
|
||||
spdlog::info("All Enterprise features are activated for testing.");
|
||||
}
|
||||
|
||||
void LicenseChecker::CheckEnvLicense() {
|
||||
// NOLINTNEXTLINE(concurrency-mt-unsafe)
|
||||
const char *license_key = std::getenv("MEMGRAPH_ENTERPRISE_LICENSE");
|
||||
if (!license_key) {
|
||||
return;
|
||||
}
|
||||
|
||||
// NOLINTNEXTLINE(concurrency-mt-unsafe)
|
||||
const char *organization_name = std::getenv("MEMGRAPH_ORGANIZATION_NAME");
|
||||
if (!organization_name) {
|
||||
return;
|
||||
}
|
||||
|
||||
spdlog::warn("Using license info from environment variables");
|
||||
license_info_override_.emplace(license_key, organization_name);
|
||||
RevalidateLicense(license_key, organization_name);
|
||||
}
|
||||
|
||||
void LicenseChecker::SetLicenseInfoOverride(std::string license_key, std::string organization_name) {
|
||||
spdlog::warn("Using license info overrides");
|
||||
license_info_override_.emplace(std::move(license_key), std::move(organization_name));
|
||||
RevalidateLicense(license_info_override_->first, license_info_override_->second);
|
||||
}
|
||||
|
||||
std::string LicenseCheckErrorToString(LicenseCheckError error, const std::string_view feature) {
|
||||
switch (error) {
|
||||
case LicenseCheckError::INVALID_LICENSE_KEY_STRING:
|
||||
return fmt::format(
|
||||
"Invalid license key string. To use {} please set it to a valid string using "
|
||||
"the following query:\n"
|
||||
"SET DATABASE SETTING \"enterprise.license\" TO \"your-license-key\"",
|
||||
feature);
|
||||
case LicenseCheckError::INVALID_ORGANIZATION_NAME:
|
||||
return fmt::format(
|
||||
"The organization name contained in the license key is not the same as the one defined in the settings. To "
|
||||
"use {} please set the organization name to a valid string using the following query:\n"
|
||||
"SET DATABASE SETTING \"organization.name\" TO \"your-organization-name\"",
|
||||
feature);
|
||||
case LicenseCheckError::EXPIRED_LICENSE:
|
||||
return fmt::format(
|
||||
"Your license key has expired. To use {} please renew your license and set the updated license key using the "
|
||||
"following query:\n"
|
||||
"SET DATABASE SETTING \"enterprise.license\" TO \"your-license-key\"",
|
||||
feature);
|
||||
}
|
||||
}
|
||||
|
||||
LicenseCheckResult LicenseChecker::IsValidLicense(const utils::Settings &settings) const {
|
||||
if (enterprise_enabled_) [[unlikely]] {
|
||||
return {};
|
||||
}
|
||||
|
||||
const auto license_info = GetLicenseInfo(settings);
|
||||
|
||||
const auto maybe_license = GetLicense(license_info.first);
|
||||
if (!maybe_license) {
|
||||
return LicenseCheckError::INVALID_LICENSE_KEY_STRING;
|
||||
}
|
||||
|
||||
return IsValidLicenseInternal(*maybe_license, license_info.second);
|
||||
}
|
||||
|
||||
void LicenseChecker::StartBackgroundLicenseChecker(const utils::Settings &settings) {
|
||||
RevalidateLicense(settings);
|
||||
scheduler_.Run("licensechecker", std::chrono::minutes{5}, [&, this] { RevalidateLicense(settings); });
|
||||
}
|
||||
|
||||
bool LicenseChecker::IsValidLicenseFast() const { return is_valid_.load(std::memory_order_relaxed); }
|
||||
|
||||
std::string Encode(const License &license) {
|
||||
std::vector<uint8_t> buffer;
|
||||
slk::Builder builder([&buffer](const uint8_t *data, size_t size, bool /*have_more*/) {
|
||||
for (size_t i = 0; i < size; ++i) {
|
||||
buffer.push_back(data[i]);
|
||||
}
|
||||
});
|
||||
|
||||
slk::Save(license.organization_name, &builder);
|
||||
slk::Save(license.valid_until, &builder);
|
||||
slk::Save(license.memory_limit, &builder);
|
||||
builder.Finalize();
|
||||
|
||||
return std::string{license_key_prefix} + base64_encode(buffer.data(), buffer.size());
|
||||
}
|
||||
|
||||
std::optional<License> Decode(std::string_view license_key) {
|
||||
if (!license_key.starts_with(license_key_prefix)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
license_key.remove_prefix(license_key_prefix.size());
|
||||
|
||||
const auto decoded = std::invoke([license_key]() -> std::optional<std::string> {
|
||||
try {
|
||||
return base64_decode(license_key);
|
||||
} catch (const std::runtime_error & /*exception*/) {
|
||||
return std::nullopt;
|
||||
}
|
||||
});
|
||||
|
||||
if (!decoded) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
try {
|
||||
slk::Reader reader(std::bit_cast<uint8_t *>(decoded->c_str()), decoded->size());
|
||||
std::string organization_name;
|
||||
slk::Load(&organization_name, &reader);
|
||||
int64_t valid_until{0};
|
||||
slk::Load(&valid_until, &reader);
|
||||
int64_t memory_limit{0};
|
||||
slk::Load(&memory_limit, &reader);
|
||||
return License{.organization_name = organization_name, .valid_until = valid_until, .memory_limit = memory_limit};
|
||||
} catch (const slk::SlkReaderException &e) {
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace utils::license
|
66
src/utils/license.hpp
Normal file
66
src/utils/license.hpp
Normal file
@ -0,0 +1,66 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
#include "utils/result.hpp"
|
||||
#include "utils/scheduler.hpp"
|
||||
#include "utils/settings.hpp"
|
||||
|
||||
namespace utils::license {
|
||||
|
||||
struct License {
|
||||
std::string organization_name;
|
||||
int64_t valid_until;
|
||||
int64_t memory_limit;
|
||||
|
||||
bool operator==(const License &) const = default;
|
||||
};
|
||||
|
||||
constexpr std::string_view kEnterpriseLicenseSettingKey = "enterprise.license";
|
||||
constexpr std::string_view kOrganizationNameSettingKey = "organization.name";
|
||||
|
||||
enum class LicenseCheckError : uint8_t { INVALID_LICENSE_KEY_STRING, INVALID_ORGANIZATION_NAME, EXPIRED_LICENSE };
|
||||
|
||||
std::string LicenseCheckErrorToString(LicenseCheckError error, std::string_view feature);
|
||||
|
||||
using LicenseCheckResult = utils::BasicResult<LicenseCheckError, void>;
|
||||
|
||||
struct LicenseChecker {
|
||||
public:
|
||||
explicit LicenseChecker() = default;
|
||||
~LicenseChecker();
|
||||
|
||||
LicenseChecker(const LicenseChecker &) = delete;
|
||||
LicenseChecker operator=(const LicenseChecker &) = delete;
|
||||
LicenseChecker(LicenseChecker &&) = delete;
|
||||
LicenseChecker operator=(LicenseChecker &&) = delete;
|
||||
|
||||
void CheckEnvLicense();
|
||||
void SetLicenseInfoOverride(std::string license_key, std::string organization_name);
|
||||
void EnableTesting();
|
||||
LicenseCheckResult IsValidLicense(const utils::Settings &settings) const;
|
||||
bool IsValidLicenseFast() const;
|
||||
void StartBackgroundLicenseChecker(const utils::Settings &settings);
|
||||
|
||||
private:
|
||||
std::pair<std::string, std::string> GetLicenseInfo(const utils::Settings &settings) const;
|
||||
void RevalidateLicense(const utils::Settings &settings);
|
||||
void RevalidateLicense(const std::string &license_key, const std::string &organization_name);
|
||||
|
||||
std::optional<std::pair<std::string, std::string>> license_info_override_;
|
||||
bool enterprise_enabled_{false};
|
||||
std::atomic<bool> is_valid_{false};
|
||||
utils::Scheduler scheduler_;
|
||||
|
||||
friend void RegisterLicenseSettings(LicenseChecker &license_checker, utils::Settings &settings);
|
||||
};
|
||||
|
||||
void RegisterLicenseSettings(LicenseChecker &license_checker, utils::Settings &settings);
|
||||
|
||||
std::optional<License> Decode(std::string_view license_key);
|
||||
std::string Encode(const License &license);
|
||||
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
extern LicenseChecker global_license_checker;
|
||||
} // namespace utils::license
|
@ -52,7 +52,25 @@ void MemoryTracker::UpdatePeak(const int64_t will_be) {
|
||||
}
|
||||
}
|
||||
|
||||
void MemoryTracker::SetHardLimit(const int64_t limit) { hard_limit_.store(limit, std::memory_order_relaxed); }
|
||||
void MemoryTracker::SetHardLimit(const int64_t limit) {
|
||||
const int64_t next_limit = std::invoke([this, limit] {
|
||||
if (maximum_hard_limit_ == 0) {
|
||||
return limit;
|
||||
}
|
||||
return limit == 0 ? maximum_hard_limit_ : std::min(maximum_hard_limit_, limit);
|
||||
});
|
||||
|
||||
if (next_limit <= 0) {
|
||||
spdlog::warn("Invalid memory limit.");
|
||||
return;
|
||||
}
|
||||
|
||||
const auto previous_limit = hard_limit_.exchange(next_limit, std::memory_order_relaxed);
|
||||
if (previous_limit != next_limit) {
|
||||
// NOLINTNEXTLINE(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions)
|
||||
spdlog::info("Memory limit set to {}", utils::GetReadableSize(next_limit));
|
||||
}
|
||||
}
|
||||
|
||||
void MemoryTracker::TryRaiseHardLimit(const int64_t limit) {
|
||||
int64_t old_limit = hard_limit_.load(std::memory_order_relaxed);
|
||||
@ -60,6 +78,14 @@ void MemoryTracker::TryRaiseHardLimit(const int64_t limit) {
|
||||
;
|
||||
}
|
||||
|
||||
void MemoryTracker::SetMaximumHardLimit(const int64_t limit) {
|
||||
if (maximum_hard_limit_ < 0) {
|
||||
spdlog::warn("Invalid maximum hard limit.");
|
||||
return;
|
||||
}
|
||||
maximum_hard_limit_ = limit;
|
||||
}
|
||||
|
||||
void MemoryTracker::Alloc(const int64_t size) {
|
||||
MG_ASSERT(size >= 0, "Negative size passed to the MemoryTracker.");
|
||||
|
||||
|
@ -16,6 +16,8 @@ class MemoryTracker final {
|
||||
std::atomic<int64_t> amount_{0};
|
||||
std::atomic<int64_t> peak_{0};
|
||||
std::atomic<int64_t> hard_limit_{0};
|
||||
// Maximum possible value of a hard limit. If it's set to 0, no upper bound on the hard limit is set.
|
||||
int64_t maximum_hard_limit_{0};
|
||||
|
||||
void UpdatePeak(int64_t will_be);
|
||||
|
||||
@ -43,6 +45,7 @@ class MemoryTracker final {
|
||||
|
||||
void SetHardLimit(int64_t limit);
|
||||
void TryRaiseHardLimit(int64_t limit);
|
||||
void SetMaximumHardLimit(int64_t limit);
|
||||
|
||||
// By creating an object of this class, every allocation in its scope that goes over
|
||||
// the set hard limit produces an OutOfMemoryException.
|
||||
|
79
src/utils/settings.cpp
Normal file
79
src/utils/settings.cpp
Normal file
@ -0,0 +1,79 @@
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include "utils/logging.hpp"
|
||||
#include "utils/settings.hpp"
|
||||
|
||||
namespace utils {
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
Settings global_settings;
|
||||
|
||||
void Settings::Initialize(std::filesystem::path storage_path) {
|
||||
std::lock_guard settings_guard{settings_lock_};
|
||||
storage_.emplace(std::move(storage_path));
|
||||
}
|
||||
|
||||
void Settings::Finalize() {
|
||||
std::lock_guard settings_guard{settings_lock_};
|
||||
storage_.reset();
|
||||
on_change_callbacks_.clear();
|
||||
}
|
||||
|
||||
void Settings::RegisterSetting(std::string name, const std::string &default_value, OnChangeCallback callback) {
|
||||
std::lock_guard settings_guard{settings_lock_};
|
||||
MG_ASSERT(storage_);
|
||||
|
||||
if (const auto maybe_value = storage_->Get(name); maybe_value) {
|
||||
SPDLOG_INFO("The setting with name {} already exists!", name);
|
||||
} else {
|
||||
MG_ASSERT(storage_->Put(name, default_value), "Failed to register a setting");
|
||||
}
|
||||
|
||||
const auto [it, inserted] = on_change_callbacks_.emplace(std::move(name), callback);
|
||||
MG_ASSERT(inserted, "Settings storage is out of sync");
|
||||
}
|
||||
|
||||
std::optional<std::string> Settings::GetValue(const std::string &setting_name) const {
|
||||
std::shared_lock settings_guard{settings_lock_};
|
||||
MG_ASSERT(storage_);
|
||||
auto maybe_value = storage_->Get(setting_name);
|
||||
return maybe_value;
|
||||
}
|
||||
|
||||
bool Settings::SetValue(const std::string &setting_name, const std::string &new_value) {
|
||||
const auto settings_change_callback = std::invoke([&, this]() -> std::optional<OnChangeCallback> {
|
||||
std::lock_guard settings_guard{settings_lock_};
|
||||
MG_ASSERT(storage_);
|
||||
|
||||
if (const auto maybe_value = storage_->Get(setting_name); !maybe_value) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
MG_ASSERT(storage_->Put(setting_name, new_value), "Failed to modify the setting");
|
||||
|
||||
const auto it = on_change_callbacks_.find(setting_name);
|
||||
MG_ASSERT(it != on_change_callbacks_.end(), "Settings storage is out of sync");
|
||||
return it->second;
|
||||
});
|
||||
|
||||
if (!settings_change_callback) {
|
||||
return false;
|
||||
}
|
||||
|
||||
(*settings_change_callback)();
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<std::pair<std::string, std::string>> Settings::AllSettings() const {
|
||||
std::shared_lock settings_guard{settings_lock_};
|
||||
|
||||
MG_ASSERT(storage_);
|
||||
|
||||
std::vector<std::pair<std::string, std::string>> settings;
|
||||
settings.reserve(storage_->Size());
|
||||
for (const auto &[k, v] : *storage_) {
|
||||
settings.emplace_back(k, v);
|
||||
}
|
||||
|
||||
return settings;
|
||||
}
|
||||
} // namespace utils
|
32
src/utils/settings.hpp
Normal file
32
src/utils/settings.hpp
Normal file
@ -0,0 +1,32 @@
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "kvstore/kvstore.hpp"
|
||||
#include "utils/rw_lock.hpp"
|
||||
#include "utils/synchronized.hpp"
|
||||
|
||||
namespace utils {
|
||||
struct Settings {
|
||||
using OnChangeCallback = std::function<void()>;
|
||||
|
||||
void Initialize(std::filesystem::path storage_path);
|
||||
// RocksDB depends on statically allocated objects so we need to delete it before the static destruction kicks in
|
||||
void Finalize();
|
||||
|
||||
void RegisterSetting(std::string name, const std::string &default_value, OnChangeCallback callback);
|
||||
std::optional<std::string> GetValue(const std::string &setting_name) const;
|
||||
bool SetValue(const std::string &setting_name, const std::string &new_value);
|
||||
std::vector<std::pair<std::string, std::string>> AllSettings() const;
|
||||
|
||||
private:
|
||||
mutable utils::RWLock settings_lock_{RWLock::Priority::WRITE};
|
||||
std::unordered_map<std::string, OnChangeCallback> on_change_callbacks_;
|
||||
std::optional<kvstore::KVStore> storage_;
|
||||
};
|
||||
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
extern Settings global_settings;
|
||||
} // namespace utils
|
@ -4,7 +4,6 @@ import time
|
||||
import mgclient
|
||||
import common
|
||||
|
||||
|
||||
def get_cursor_with_user(username):
|
||||
connection = common.connect(username=username, password="")
|
||||
return connection.cursor()
|
||||
|
@ -55,10 +55,14 @@ def wait_for_server(port, delay=0.1):
|
||||
|
||||
def execute_test(memgraph_binary, tester_binary):
|
||||
storage_directory = tempfile.TemporaryDirectory()
|
||||
memgraph_args = [memgraph_binary,
|
||||
"--storage-properties-on-edges",
|
||||
"--data-directory", storage_directory.name,
|
||||
"--audit-enabled"]
|
||||
memgraph_args = [
|
||||
memgraph_binary,
|
||||
"--storage-properties-on-edges",
|
||||
"--data-directory",
|
||||
storage_directory.name,
|
||||
"--audit-enabled",
|
||||
"--log-file=memgraph.log",
|
||||
"--log-level=TRACE"]
|
||||
|
||||
# Start the memgraph binary
|
||||
memgraph = subprocess.Popen(list(map(str, memgraph_args)))
|
||||
@ -73,17 +77,21 @@ def execute_test(memgraph_binary, tester_binary):
|
||||
memgraph.terminate()
|
||||
assert memgraph.wait() == 0, "Memgraph process didn't exit cleanly!"
|
||||
|
||||
def execute_queries(queries):
|
||||
for query, params in queries:
|
||||
print(query, params)
|
||||
args = [tester_binary, "--query", query,
|
||||
"--params-json", json.dumps(params)]
|
||||
subprocess.run(args).check_returncode()
|
||||
|
||||
# Execute all queries
|
||||
print("\033[1;36m~~ Starting query execution ~~\033[0m")
|
||||
for query, params in QUERIES:
|
||||
print(query, params)
|
||||
args = [tester_binary, "--query", query,
|
||||
"--params-json", json.dumps(params)]
|
||||
subprocess.run(args).check_returncode()
|
||||
execute_queries(QUERIES)
|
||||
print("\033[1;36m~~ Finished query execution ~~\033[0m\n")
|
||||
|
||||
# Shutdown the memgraph binary
|
||||
memgraph.terminate()
|
||||
|
||||
assert memgraph.wait() == 0, "Memgraph process didn't exit cleanly!"
|
||||
|
||||
# Verify the written log
|
||||
@ -99,6 +107,7 @@ def execute_test(memgraph_binary, tester_binary):
|
||||
params = json.loads(params)
|
||||
queries.append((query, params))
|
||||
print(query, params)
|
||||
|
||||
assert queries == QUERIES, "Logged queries don't match " \
|
||||
"executed queries!"
|
||||
print("\033[1;36m~~ Finished log verification ~~\033[0m\n")
|
||||
|
@ -218,7 +218,7 @@ def execute_test(memgraph_binary, tester_binary, checker_binary):
|
||||
execute_admin_queries([
|
||||
"CREATE USER ADmin IDENTIFIED BY 'admin'",
|
||||
"GRANT ALL PRIVILEGES TO admIN",
|
||||
"CREATE USER usEr IDENTIFIED BY 'user'"
|
||||
"CREATE USER usEr IDENTIFIED BY 'user'",
|
||||
])
|
||||
|
||||
# Find all existing permissions
|
||||
|
@ -100,7 +100,7 @@ class Memgraph:
|
||||
kwargs.pop("module_executable", self._auth_module)]
|
||||
for key, value in kwargs.items():
|
||||
ldap_key = "--auth-module-" + key.replace("_", "-")
|
||||
if type(value) == bool:
|
||||
if isinstance(value, bool):
|
||||
args.append(ldap_key + "=" + str(value).lower())
|
||||
else:
|
||||
args.append(ldap_key)
|
||||
@ -124,6 +124,7 @@ class Memgraph:
|
||||
|
||||
def initialize_test(memgraph, tester_binary, **kwargs):
|
||||
memgraph.start(module_executable="")
|
||||
|
||||
execute_tester(tester_binary,
|
||||
["CREATE USER root", "GRANT ALL PRIVILEGES TO root"])
|
||||
check_login = kwargs.pop("check_login", True)
|
||||
|
@ -3,6 +3,7 @@
|
||||
#include "query/interpreter.hpp"
|
||||
#include "storage/v2/isolation_level.hpp"
|
||||
#include "storage/v2/storage.hpp"
|
||||
#include "utils/license.hpp"
|
||||
#include "utils/on_scope_exit.hpp"
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
@ -17,6 +18,8 @@ int main(int argc, char *argv[]) {
|
||||
storage::Storage db;
|
||||
auto data_directory = std::filesystem::temp_directory_path() / "single_query_test";
|
||||
utils::OnScopeExit([&data_directory] { std::filesystem::remove_all(data_directory); });
|
||||
|
||||
utils::license::global_license_checker.EnableTesting();
|
||||
query::InterpreterContext interpreter_context{&db, query::InterpreterConfig{}, data_directory,
|
||||
"non existing bootstrap servers"};
|
||||
query::Interpreter interpreter{&interpreter_context};
|
||||
|
@ -260,6 +260,12 @@ target_link_libraries(${test_prefix}utils_csv_parsing mg-utils fmt)
|
||||
add_unit_test(utils_async_timer.cpp)
|
||||
target_link_libraries(${test_prefix}utils_async_timer mg-utils)
|
||||
|
||||
add_unit_test(utils_license.cpp)
|
||||
target_link_libraries(${test_prefix}utils_license mg-utils)
|
||||
|
||||
add_unit_test(utils_settings.cpp)
|
||||
target_link_libraries(${test_prefix}utils_settings mg-utils)
|
||||
|
||||
# Test mg-storage-v2
|
||||
|
||||
add_unit_test(commit_log_v2.cpp)
|
||||
|
@ -8,6 +8,7 @@
|
||||
#include "auth/crypto.hpp"
|
||||
#include "utils/cast.hpp"
|
||||
#include "utils/file.hpp"
|
||||
#include "utils/license.hpp"
|
||||
|
||||
using namespace auth;
|
||||
namespace fs = std::filesystem;
|
||||
@ -21,13 +22,15 @@ class AuthWithStorage : public ::testing::Test {
|
||||
utils::EnsureDir(test_folder_);
|
||||
FLAGS_auth_password_permit_null = true;
|
||||
FLAGS_auth_password_strength_regex = ".+";
|
||||
|
||||
utils::license::global_license_checker.EnableTesting();
|
||||
}
|
||||
|
||||
virtual void TearDown() { fs::remove_all(test_folder_); }
|
||||
|
||||
fs::path test_folder_{fs::temp_directory_path() / ("unit_auth_test_" + std::to_string(static_cast<int>(getpid())))};
|
||||
fs::path test_folder_{fs::temp_directory_path() / "MG_tests_unit_auth"};
|
||||
|
||||
Auth auth{test_folder_};
|
||||
Auth auth{test_folder_ / ("unit_auth_test_" + std::to_string(static_cast<int>(getpid())))};
|
||||
};
|
||||
|
||||
TEST_F(AuthWithStorage, AddRole) {
|
||||
|
@ -3432,4 +3432,34 @@ TEST_P(CypherMainVisitorTest, CheckStream) {
|
||||
StreamQuery::Action::CHECK_STREAM, "checkedStream", TypedValue(30), TypedValue(444));
|
||||
}
|
||||
|
||||
TEST_P(CypherMainVisitorTest, SettingQuery) {
|
||||
auto &ast_generator = *GetParam();
|
||||
|
||||
TestInvalidQuery("SHOW DB SETTINGS", ast_generator);
|
||||
TestInvalidQuery("SHOW SETTINGS", ast_generator);
|
||||
TestInvalidQuery("SHOW DATABASE SETTING", ast_generator);
|
||||
TestInvalidQuery("SHOW DB SETTING 'setting'", ast_generator);
|
||||
TestInvalidQuery("SHOW SETTING 'setting'", ast_generator);
|
||||
TestInvalidQuery<SemanticException>("SHOW DATABASE SETTING 1", ast_generator);
|
||||
TestInvalidQuery("SET SETTING 'setting' TO 'value'", ast_generator);
|
||||
TestInvalidQuery("SET DB SETTING 'setting' TO 'value'", ast_generator);
|
||||
TestInvalidQuery<SemanticException>("SET DATABASE SETTING 1 TO 'value'", ast_generator);
|
||||
TestInvalidQuery<SemanticException>("SET DATABASE SETTING 'setting' TO 2", ast_generator);
|
||||
|
||||
const auto validate_setting_query = [&](const auto &query, const auto action,
|
||||
const std::optional<TypedValue> &expected_setting_name,
|
||||
const std::optional<TypedValue> &expected_setting_value) {
|
||||
auto *parsed_query = dynamic_cast<SettingQuery *>(ast_generator.ParseQuery(query));
|
||||
EXPECT_EQ(parsed_query->action_, action) << query;
|
||||
EXPECT_NO_FATAL_FAILURE(CheckOptionalExpression(ast_generator, parsed_query->setting_name_, expected_setting_name));
|
||||
EXPECT_NO_FATAL_FAILURE(
|
||||
CheckOptionalExpression(ast_generator, parsed_query->setting_value_, expected_setting_value));
|
||||
};
|
||||
|
||||
validate_setting_query("SHOW DATABASE SETTINGS", SettingQuery::Action::SHOW_ALL_SETTINGS, std::nullopt, std::nullopt);
|
||||
validate_setting_query("SHOW DATABASE SETTING 'setting'", SettingQuery::Action::SHOW_SETTING, TypedValue{"setting"},
|
||||
std::nullopt);
|
||||
validate_setting_query("SET DATABASE SETTING 'setting' TO 'value'", SettingQuery::Action::SET_SETTING,
|
||||
TypedValue{"setting"}, TypedValue{"value"});
|
||||
}
|
||||
} // namespace
|
||||
|
@ -4,6 +4,6 @@
|
||||
int main(int argc, char **argv) {
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
logging::RedirectToStderr();
|
||||
spdlog::set_level(spdlog::level::warn);
|
||||
spdlog::set_level(spdlog::level::trace);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
|
@ -170,3 +170,8 @@ TEST_F(TestPrivilegeExtractor, StreamQuery) {
|
||||
auto *query = storage.Create<StreamQuery>();
|
||||
EXPECT_THAT(GetRequiredPrivileges(query), UnorderedElementsAre(AuthQuery::Privilege::STREAM));
|
||||
}
|
||||
|
||||
TEST_F(TestPrivilegeExtractor, SettingQuery) {
|
||||
auto *query = storage.Create<SettingQuery>();
|
||||
EXPECT_THAT(GetRequiredPrivileges(query), UnorderedElementsAre(AuthQuery::Privilege::CONFIG));
|
||||
}
|
||||
|
127
tests/unit/utils_license.cpp
Normal file
127
tests/unit/utils_license.cpp
Normal file
@ -0,0 +1,127 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "utils/license.hpp"
|
||||
#include "utils/settings.hpp"
|
||||
|
||||
class LicenseTest : public ::testing::Test {
|
||||
public:
|
||||
void SetUp() override {
|
||||
settings.emplace();
|
||||
settings->Initialize(settings_directory);
|
||||
|
||||
license_checker.emplace();
|
||||
utils::license::RegisterLicenseSettings(*license_checker, *settings);
|
||||
|
||||
license_checker->StartBackgroundLicenseChecker(*settings);
|
||||
}
|
||||
|
||||
void TearDown() override { std::filesystem::remove_all(test_directory); }
|
||||
|
||||
protected:
|
||||
const std::filesystem::path test_directory{"MG_tests_unit_utils_license"};
|
||||
const std::filesystem::path settings_directory{test_directory / "settings"};
|
||||
|
||||
void CheckLicenseValidity(const bool expected_valid) {
|
||||
ASSERT_EQ(!license_checker->IsValidLicense(*settings).HasError(), expected_valid);
|
||||
ASSERT_EQ(license_checker->IsValidLicenseFast(), expected_valid);
|
||||
}
|
||||
|
||||
std::optional<utils::Settings> settings;
|
||||
std::optional<utils::license::LicenseChecker> license_checker;
|
||||
};
|
||||
|
||||
TEST_F(LicenseTest, EncodeDecode) {
|
||||
const std::array licenses = {
|
||||
utils::license::License{"Organization", 1, 2},
|
||||
utils::license::License{"", -1, 0},
|
||||
utils::license::License{"Some very long name for the organization Ltd", -999, -9999},
|
||||
};
|
||||
|
||||
for (const auto &license : licenses) {
|
||||
const auto result = utils::license::Encode(license);
|
||||
auto maybe_license = utils::license::Decode(result);
|
||||
ASSERT_TRUE(maybe_license);
|
||||
ASSERT_EQ(*maybe_license, license);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(LicenseTest, TestingFlag) {
|
||||
CheckLicenseValidity(false);
|
||||
|
||||
license_checker->EnableTesting();
|
||||
CheckLicenseValidity(true);
|
||||
|
||||
SCOPED_TRACE("EnableTesting shouldn't be affected by settings change");
|
||||
settings->SetValue("enterprise.license", "");
|
||||
CheckLicenseValidity(true);
|
||||
}
|
||||
|
||||
TEST_F(LicenseTest, LicenseOrganizationName) {
|
||||
const std::string organization_name{"Memgraph"};
|
||||
utils::license::License license{.organization_name = organization_name, .valid_until = 0, .memory_limit = 0};
|
||||
|
||||
settings->SetValue("enterprise.license", utils::license::Encode(license));
|
||||
settings->SetValue("organization.name", organization_name);
|
||||
CheckLicenseValidity(true);
|
||||
|
||||
settings->SetValue("organization.name", fmt::format("{}modified", organization_name));
|
||||
CheckLicenseValidity(false);
|
||||
|
||||
settings->SetValue("organization.name", organization_name);
|
||||
CheckLicenseValidity(true);
|
||||
}
|
||||
|
||||
TEST_F(LicenseTest, Expiration) {
|
||||
const std::string organization_name{"Memgraph"};
|
||||
|
||||
{
|
||||
const auto now =
|
||||
std::chrono::duration_cast<std::chrono::seconds>(std::chrono::system_clock::now().time_since_epoch());
|
||||
const auto delta = std::chrono::seconds(1);
|
||||
const auto valid_until = now + delta;
|
||||
utils::license::License license{
|
||||
.organization_name = organization_name, .valid_until = valid_until.count(), .memory_limit = 0};
|
||||
|
||||
settings->SetValue("enterprise.license", utils::license::Encode(license));
|
||||
settings->SetValue("organization.name", organization_name);
|
||||
CheckLicenseValidity(true);
|
||||
|
||||
std::this_thread::sleep_for(delta + std::chrono::seconds(1));
|
||||
ASSERT_TRUE(license_checker->IsValidLicense(*settings).HasError());
|
||||
// We can't check fast checker because it has unknown refresh rate
|
||||
}
|
||||
{
|
||||
SCOPED_TRACE("License with valid_until = 0 is always valid");
|
||||
utils::license::License license{.organization_name = organization_name, .valid_until = 0, .memory_limit = 0};
|
||||
settings->SetValue("enterprise.license", utils::license::Encode(license));
|
||||
settings->SetValue("organization.name", organization_name);
|
||||
CheckLicenseValidity(true);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(LicenseTest, LicenseInfoOverride) {
|
||||
CheckLicenseValidity(false);
|
||||
|
||||
const std::string organization_name{"Memgraph"};
|
||||
utils::license::License license{.organization_name = organization_name, .valid_until = 0, .memory_limit = 0};
|
||||
const std::string license_key = utils::license::Encode(license);
|
||||
|
||||
{
|
||||
SCOPED_TRACE("Checker should use overrides instead of info from the settings");
|
||||
license_checker->SetLicenseInfoOverride(license_key, organization_name);
|
||||
CheckLicenseValidity(true);
|
||||
}
|
||||
{
|
||||
SCOPED_TRACE("License info override shouldn't be affected by settings change");
|
||||
settings->SetValue("enterprise.license", "INVALID");
|
||||
CheckLicenseValidity(true);
|
||||
}
|
||||
{
|
||||
SCOPED_TRACE("Override with invalid key");
|
||||
license_checker->SetLicenseInfoOverride("INVALID", organization_name);
|
||||
CheckLicenseValidity(false);
|
||||
settings->SetValue("enterprise.license", license_key);
|
||||
settings->SetValue("organization.name", organization_name);
|
||||
CheckLicenseValidity(false);
|
||||
}
|
||||
}
|
158
tests/unit/utils_settings.cpp
Normal file
158
tests/unit/utils_settings.cpp
Normal file
@ -0,0 +1,158 @@
|
||||
#include <filesystem>
|
||||
|
||||
#include <gmock/gmock-generated-matchers.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "utils/settings.hpp"
|
||||
|
||||
class SettingsTest : public ::testing::Test {
|
||||
public:
|
||||
void TearDown() override { std::filesystem::remove_all(test_directory); }
|
||||
|
||||
protected:
|
||||
const std::filesystem::path test_directory{"MG_tests_unit_utils_settings"};
|
||||
const std::filesystem::path settings_directory{test_directory / "settings"};
|
||||
static void DummyCallback() {}
|
||||
};
|
||||
|
||||
namespace {
|
||||
void CheckSettingValue(const utils::Settings &settings, const std::string &setting_name,
|
||||
const std::string &expected_value) {
|
||||
auto maybe_value = settings.GetValue(setting_name);
|
||||
ASSERT_TRUE(maybe_value) << "Failed to access registered setting";
|
||||
ASSERT_EQ(maybe_value, expected_value);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST_F(SettingsTest, RegisterSetting) {
|
||||
const std::string setting_name{"name"};
|
||||
const std::string default_value{"value"};
|
||||
|
||||
{
|
||||
utils::Settings settings;
|
||||
|
||||
settings.Initialize(settings_directory);
|
||||
settings.RegisterSetting(setting_name, default_value, DummyCallback);
|
||||
CheckSettingValue(settings, setting_name, default_value);
|
||||
}
|
||||
{
|
||||
utils::Settings settings;
|
||||
settings.Initialize(settings_directory);
|
||||
// registering the same object shouldn't change its value
|
||||
settings.RegisterSetting(setting_name, fmt::format("{}-modified", default_value), DummyCallback);
|
||||
CheckSettingValue(settings, setting_name, default_value);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(SettingsTest, RegisterSettingCallback) {
|
||||
const std::string setting_name{"name"};
|
||||
const std::string default_value{"value"};
|
||||
|
||||
utils::Settings settings;
|
||||
settings.Initialize(settings_directory);
|
||||
|
||||
size_t callback_counter{0};
|
||||
const auto callback = [&]() { ++callback_counter; };
|
||||
|
||||
size_t setting_change_counter{0};
|
||||
const auto assert_equal_counters = [&] { ASSERT_EQ(callback_counter, setting_change_counter); };
|
||||
|
||||
settings.RegisterSetting(setting_name, default_value, callback);
|
||||
assert_equal_counters();
|
||||
|
||||
ASSERT_TRUE(settings.SetValue(setting_name, default_value));
|
||||
++setting_change_counter;
|
||||
assert_equal_counters();
|
||||
|
||||
ASSERT_TRUE(settings.SetValue(setting_name, fmt::format("{}-modified", default_value)));
|
||||
++setting_change_counter;
|
||||
assert_equal_counters();
|
||||
}
|
||||
|
||||
TEST_F(SettingsTest, GetSetRegisteredSetting) {
|
||||
const std::string setting_name{"name"};
|
||||
const std::string setting_value{"value"};
|
||||
const std::string default_value{"default"};
|
||||
|
||||
utils::Settings settings;
|
||||
settings.Initialize(settings_directory);
|
||||
settings.RegisterSetting(setting_name, default_value, DummyCallback);
|
||||
|
||||
CheckSettingValue(settings, setting_name, default_value);
|
||||
ASSERT_TRUE(settings.SetValue(setting_name, setting_value)) << "Failed to modify registered setting";
|
||||
CheckSettingValue(settings, setting_name, setting_value);
|
||||
}
|
||||
|
||||
TEST_F(SettingsTest, GetSetUnregisteredSetting) {
|
||||
utils::Settings settings;
|
||||
settings.Initialize(settings_directory);
|
||||
ASSERT_FALSE(settings.GetValue("Somesetting")) << "Accessed unregistered setting";
|
||||
ASSERT_FALSE(settings.SetValue("Somesetting", "Somevalue")) << "Modified unregistered setting";
|
||||
}
|
||||
|
||||
TEST_F(SettingsTest, Initialization) {
|
||||
utils::Settings settings;
|
||||
settings.Initialize(settings_directory);
|
||||
ASSERT_NO_FATAL_FAILURE(settings.GetValue("setting"));
|
||||
ASSERT_NO_FATAL_FAILURE(settings.SetValue("setting", "value"));
|
||||
ASSERT_NO_FATAL_FAILURE(settings.AllSettings());
|
||||
}
|
||||
|
||||
namespace {
|
||||
std::vector<std::pair<std::string, std::string>> GenerateSettings(const size_t amount) {
|
||||
std::vector<std::pair<std::string, std::string>> result;
|
||||
result.reserve(amount);
|
||||
|
||||
for (size_t i = 0; i < amount; ++i) {
|
||||
result.emplace_back(fmt::format("setting{}", i), fmt::format("value{}", i));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST_F(SettingsTest, AllSettings) {
|
||||
const auto generated_settings = GenerateSettings(100);
|
||||
|
||||
utils::Settings settings;
|
||||
settings.Initialize(settings_directory);
|
||||
for (const auto &[setting_name, setting_value] : generated_settings) {
|
||||
settings.RegisterSetting(setting_name, setting_value, DummyCallback);
|
||||
}
|
||||
ASSERT_THAT(settings.AllSettings(), testing::UnorderedElementsAreArray(generated_settings));
|
||||
}
|
||||
|
||||
TEST_F(SettingsTest, Persistance) {
|
||||
auto generated_settings = GenerateSettings(100);
|
||||
|
||||
utils::Settings settings;
|
||||
settings.Initialize(settings_directory);
|
||||
|
||||
for (const auto &[setting_name, setting_value] : generated_settings) {
|
||||
settings.RegisterSetting(setting_name, setting_value, DummyCallback);
|
||||
}
|
||||
|
||||
ASSERT_THAT(settings.AllSettings(), testing::UnorderedElementsAreArray(generated_settings));
|
||||
|
||||
// reinitialize to other directory and then back to the first
|
||||
settings.Initialize(test_directory / "other_settings");
|
||||
ASSERT_TRUE(settings.AllSettings().empty());
|
||||
|
||||
settings.Initialize(settings_directory);
|
||||
ASSERT_THAT(settings.AllSettings(), testing::UnorderedElementsAreArray(generated_settings));
|
||||
|
||||
for (size_t i = 0; i < generated_settings.size(); ++i) {
|
||||
auto &[setting_name, setting_value] = generated_settings[i];
|
||||
setting_value = fmt::format("new_value{}", i);
|
||||
settings.SetValue(setting_name, setting_value);
|
||||
}
|
||||
|
||||
ASSERT_THAT(settings.AllSettings(), testing::UnorderedElementsAreArray(generated_settings));
|
||||
|
||||
// reinitialize to other directory and then back to the first
|
||||
settings.Initialize(test_directory / "other_settings");
|
||||
ASSERT_TRUE(settings.AllSettings().empty());
|
||||
|
||||
settings.Initialize(settings_directory);
|
||||
ASSERT_THAT(settings.AllSettings(), testing::UnorderedElementsAreArray(generated_settings));
|
||||
}
|
Loading…
Reference in New Issue
Block a user