mirror of
				https://github.com/Telecominfraproject/wlan-cloud-ucentralsec.git
				synced 2025-10-31 10:47:48 +00:00 
			
		
		
		
	Compare commits
	
		
			1 Commits
		
	
	
		
			v2.6.0-RC1
			...
			release/v2
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 0a2190b43f | 
							
								
								
									
										102
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										102
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							| @@ -13,7 +13,6 @@ on: | |||||||
|   pull_request: |   pull_request: | ||||||
|     branches: |     branches: | ||||||
|       - main |       - main | ||||||
|       - 'release/*' |  | ||||||
|  |  | ||||||
| defaults: | defaults: | ||||||
|   run: |   run: | ||||||
| @@ -26,78 +25,45 @@ jobs: | |||||||
|       DOCKER_REGISTRY_URL: tip-tip-wlan-cloud-ucentral.jfrog.io |       DOCKER_REGISTRY_URL: tip-tip-wlan-cloud-ucentral.jfrog.io | ||||||
|       DOCKER_REGISTRY_USERNAME: ucentral |       DOCKER_REGISTRY_USERNAME: ucentral | ||||||
|     steps: |     steps: | ||||||
|     - name: Checkout actions repo |     - uses: actions/checkout@v2 | ||||||
|       uses: actions/checkout@v2 |  | ||||||
|       with: |  | ||||||
|         repository: Telecominfraproject/.github |  | ||||||
|         path: github |  | ||||||
|  |  | ||||||
|     - name: Build and push Docker image |     - name: Build Docker image | ||||||
|       uses: ./github/composite-actions/docker-image-build |       run: docker build -t wlan-cloud-owsec:${{ github.sha }} . | ||||||
|       with: |  | ||||||
|         image_name: owsec |  | ||||||
|         registry: tip-tip-wlan-cloud-ucentral.jfrog.io |  | ||||||
|         registry_user: ucentral |  | ||||||
|         registry_password: ${{ secrets.DOCKER_REGISTRY_PASSWORD }} |  | ||||||
|  |  | ||||||
|     - name: Notify on failure via Slack |     - name: Tag Docker image | ||||||
|       if: failure() && github.ref == 'refs/heads/main' |  | ||||||
|       uses: rtCamp/action-slack-notify@v2 |  | ||||||
|       env: |  | ||||||
|         SLACK_USERNAME: GitHub Actions failure notifier |  | ||||||
|         SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }} |  | ||||||
|         SLACK_COLOR: "${{ job.status }}" |  | ||||||
|         SLACK_ICON: https://raw.githubusercontent.com/quintessence/slack-icons/master/images/github-logo-slack-icon.png |  | ||||||
|         SLACK_TITLE: Docker build failed for OWSec service |  | ||||||
|  |  | ||||||
|   trigger-testing: |  | ||||||
|     if: startsWith(github.ref, 'refs/pull/') |  | ||||||
|     runs-on: ubuntu-latest |  | ||||||
|     needs: docker |  | ||||||
|     steps: |  | ||||||
|     - name: Get base branch name and set as output |  | ||||||
|       id: get_base_branch |  | ||||||
|       run: | |       run: | | ||||||
|         echo ::set-output name=branch::$(echo ${GITHUB_BASE_REF##*/}) |         TAGS="${{ github.sha }}" | ||||||
|         echo ::set-output name=owgw_branch::$(echo ${GITHUB_BASE_REF##*/} | sed 's/main/master/g') |  | ||||||
|  |  | ||||||
|     - name: Checkout actions repo |         if [[ ${GITHUB_REF} == "refs/heads/"* ]] | ||||||
|       uses: actions/checkout@v2 |         then | ||||||
|       with: |           CURRENT_TAG=$(echo ${GITHUB_REF#refs/heads/} | tr '/' '-') | ||||||
|         repository: Telecominfraproject/.github |           TAGS="$TAGS $CURRENT_TAG" | ||||||
|         path: github |         else | ||||||
|  |           if [[ ${GITHUB_REF} == "refs/tags/"* ]] | ||||||
|  |           then | ||||||
|  |             CURRENT_TAG=$(echo ${GITHUB_REF#refs/tags/} | tr '/' '-') | ||||||
|  |             TAGS="$TAGS $CURRENT_TAG" | ||||||
|  |           else # PR build | ||||||
|  |             CURRENT_TAG=$(echo ${GITHUB_HEAD_REF#refs/heads/} | tr '/' '-') | ||||||
|  |             TAGS="$TAGS $CURRENT_TAG" | ||||||
|  |           fi | ||||||
|  |         fi | ||||||
|  |  | ||||||
|     - name: Trigger testing of OpenWifi Docker Compose deployment and wait for result |         echo "Result tags: $TAGS" | ||||||
|       uses: ./github/composite-actions/trigger-workflow-and-wait |  | ||||||
|       env: |  | ||||||
|         BASE_BRANCH: ${{ steps.get_base_branch.outputs.branch }} |  | ||||||
|         OWGW_BASE_BRANCH: ${{ steps.get_base_branch.outputs.owgw_branch }} |  | ||||||
|       with: |  | ||||||
|         owner: Telecominfraproject |  | ||||||
|         repo: wlan-testing |  | ||||||
|         workflow: ow_docker-compose.yml |  | ||||||
|         token: ${{ secrets.WLAN_TESTING_PAT }} |  | ||||||
|         ref: master |  | ||||||
|         inputs: '{"deployment_version": "${{ env.BASE_BRANCH }}", "owgw_version": "${{ env.OWGW_BASE_BRANCH }}", "owsec_version": "${{ github.sha }}", "owfms_version": "${{ env.BASE_BRANCH }}", "owprov_version": "${{ env.BASE_BRANCH }}", "owanalytics_version": "${{ env.BASE_BRANCH }}", "owsub_version": "${{ env.BASE_BRANCH }}", "microservice": "owsec"}' |  | ||||||
|  |  | ||||||
|   trigger-deploy-to-dev: |         for tag in $TAGS; do | ||||||
|     runs-on: ubuntu-latest |           docker tag wlan-cloud-owsec:${{ github.sha }} ${{ env.DOCKER_REGISTRY_URL }}/owsec:$tag | ||||||
|     if: github.ref == 'refs/heads/main' |         done | ||||||
|     needs: |  | ||||||
|       - docker |  | ||||||
|     steps: |  | ||||||
|     - name: Checkout actions repo |  | ||||||
|       uses: actions/checkout@v2 |  | ||||||
|       with: |  | ||||||
|         repository: Telecominfraproject/.github |  | ||||||
|         path: github |  | ||||||
|  |  | ||||||
|     - name: Trigger deployment of the latest version to dev instance and wait for result |     - name: Log into Docker registry | ||||||
|       uses: ./github/composite-actions/trigger-workflow-and-wait |       if: startsWith(github.ref, 'refs/tags/') || startsWith(github.ref, 'refs/pull/') || github.ref == 'refs/heads/main' | ||||||
|  |       uses: docker/login-action@v1 | ||||||
|       with: |       with: | ||||||
|         owner: Telecominfraproject |         registry: ${{ env.DOCKER_REGISTRY_URL }} | ||||||
|         repo: wlan-testing |         username: ${{ env.DOCKER_REGISTRY_USERNAME }} | ||||||
|         workflow: ucentralgw-dev-deployment.yaml |         password: ${{ secrets.DOCKER_REGISTRY_PASSWORD }} | ||||||
|         token: ${{ secrets.WLAN_TESTING_PAT }} |  | ||||||
|         ref: master |     - name: Push Docker images | ||||||
|         inputs: '{"force_latest": "true"}' |       if: startsWith(github.ref, 'refs/tags/') || startsWith(github.ref, 'refs/pull/') || github.ref == 'refs/heads/main' | ||||||
|  |       run: | | ||||||
|  |         docker images | grep ${{ env.DOCKER_REGISTRY_URL }}/owsec | awk -F ' ' '{print $1":"$2}' | xargs -I {} docker push {} | ||||||
|   | |||||||
							
								
								
									
										1
									
								
								.github/workflows/cleanup.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.github/workflows/cleanup.yml
									
									
									
									
										vendored
									
									
								
							| @@ -4,7 +4,6 @@ on: | |||||||
|   pull_request: |   pull_request: | ||||||
|     branches: |     branches: | ||||||
|       - main |       - main | ||||||
|       - 'release/*' |  | ||||||
|     types: [ closed ] |     types: [ closed ] | ||||||
|  |  | ||||||
| defaults: | defaults: | ||||||
|   | |||||||
							
								
								
									
										24
									
								
								.github/workflows/enforce-jira-issue-key.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										24
									
								
								.github/workflows/enforce-jira-issue-key.yml
									
									
									
									
										vendored
									
									
								
							| @@ -1,24 +0,0 @@ | |||||||
| name: Ensure Jira issue is linked |  | ||||||
|  |  | ||||||
| on: |  | ||||||
|   pull_request: |  | ||||||
|     types: [opened, edited, reopened, synchronize] |  | ||||||
|     branches: |  | ||||||
|       - 'release/*' |  | ||||||
|  |  | ||||||
| jobs: |  | ||||||
|   check_for_issue_key: |  | ||||||
|     runs-on: ubuntu-latest |  | ||||||
|     steps: |  | ||||||
|       - name: Checkout actions repo |  | ||||||
|         uses: actions/checkout@v2 |  | ||||||
|         with: |  | ||||||
|           repository: Telecominfraproject/.github |  | ||||||
|           path: github |  | ||||||
|  |  | ||||||
|       - name: Run JIRA check |  | ||||||
|         uses: ./github/composite-actions/enforce-jira-issue-key |  | ||||||
|         with: |  | ||||||
|           jira_base_url: ${{ secrets.TIP_JIRA_URL }} |  | ||||||
|           jira_user_email: ${{ secrets.TIP_JIRA_USER_EMAIL }} |  | ||||||
|           jira_api_token: ${{ secrets.TIP_JIRA_API_TOKEN }} |  | ||||||
							
								
								
									
										46
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										46
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							| @@ -1,46 +0,0 @@ | |||||||
| name: Release chart package |  | ||||||
|  |  | ||||||
| on: |  | ||||||
|   push: |  | ||||||
|     tags: |  | ||||||
|       - 'v*' |  | ||||||
|  |  | ||||||
| defaults: |  | ||||||
|   run: |  | ||||||
|     shell: bash |  | ||||||
|  |  | ||||||
| jobs: |  | ||||||
|   helm-package: |  | ||||||
|     runs-on: ubuntu-20.04 |  | ||||||
|     env: |  | ||||||
|       HELM_REPO_URL: https://tip.jfrog.io/artifactory/tip-wlan-cloud-ucentral-helm/ |  | ||||||
|       HELM_REPO_USERNAME: ucentral |  | ||||||
|     steps: |  | ||||||
|       - name: Checkout uCentral assembly chart repo |  | ||||||
|         uses: actions/checkout@v2 |  | ||||||
|         with: |  | ||||||
|           path: wlan-cloud-ucentralsec |  | ||||||
|  |  | ||||||
|       - name: Build package |  | ||||||
|         working-directory: wlan-cloud-ucentralsec/helm |  | ||||||
|         run: | |  | ||||||
|           helm plugin install https://github.com/aslafy-z/helm-git --version 0.10.0 |  | ||||||
|           helm repo add bitnami https://charts.bitnami.com/bitnami |  | ||||||
|           helm repo update |  | ||||||
|           helm dependency update |  | ||||||
|           mkdir dist |  | ||||||
|           helm package . -d dist |  | ||||||
|  |  | ||||||
|       - name: Generate GitHub release body |  | ||||||
|         working-directory: wlan-cloud-ucentralsec/helm |  | ||||||
|         run: | |  | ||||||
|           pip3 install yq -q |  | ||||||
|           echo "Docker image - tip-tip-wlan-cloud-ucentral.jfrog.io/owsec:$GITHUB_REF_NAME" > release.txt |  | ||||||
|           echo "Helm charted may be attached to this release" >> release.txt |  | ||||||
|           echo "Deployment artifacts may be found in https://github.com/Telecominfraproject/wlan-cloud-ucentral-deploy/tree/$GITHUB_REF_NAME" >> release.txt |  | ||||||
|  |  | ||||||
|       - name: Create GitHub release |  | ||||||
|         uses: softprops/action-gh-release@v1 |  | ||||||
|         with: |  | ||||||
|           body_path: wlan-cloud-ucentralsec/helm/release.txt |  | ||||||
|           files: wlan-cloud-ucentralsec/helm/dist/* |  | ||||||
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -18,4 +18,3 @@ _deps | |||||||
| *.csr | *.csr | ||||||
| /cmake-build/ | /cmake-build/ | ||||||
| /smake-build-debug/ | /smake-build-debug/ | ||||||
| test_scripts/curl/result.json |  | ||||||
|   | |||||||
							
								
								
									
										125
									
								
								CMakeLists.txt
									
									
									
									
									
								
							
							
						
						
									
										125
									
								
								CMakeLists.txt
									
									
									
									
									
								
							| @@ -1,5 +1,5 @@ | |||||||
| cmake_minimum_required(VERSION 3.13) | cmake_minimum_required(VERSION 3.13) | ||||||
| project(owsec VERSION 2.6.0) | project(owsec VERSION 2.2.0) | ||||||
|  |  | ||||||
| set(CMAKE_CXX_STANDARD 17) | set(CMAKE_CXX_STANDARD 17) | ||||||
|  |  | ||||||
| @@ -20,120 +20,69 @@ endif() | |||||||
|  |  | ||||||
| # Auto build increment. You must define BUILD_INCREMENT with cmake -DBUILD_INCREMENT=1 | # Auto build increment. You must define BUILD_INCREMENT with cmake -DBUILD_INCREMENT=1 | ||||||
| if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/build) | if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/build) | ||||||
|     file(READ ${CMAKE_CURRENT_SOURCE_DIR}/build BUILD_NUM) |     file(READ build BUILD_NUM) | ||||||
|     if(BUILD_INCREMENT) |     if(BUILD_INCREMENT) | ||||||
|         MATH(EXPR BUILD_NUM "${BUILD_NUM}+1") |         MATH(EXPR BUILD_NUM "${BUILD_NUM}+1") | ||||||
|         file(WRITE ${CMAKE_CURRENT_SOURCE_DIR}/build ${BUILD_NUM}) |         file(WRITE build ${BUILD_NUM}) | ||||||
|     endif() |     endif() | ||||||
| else() | else() | ||||||
|     set(BUILD_NUM 1) |     set(BUILD_NUM 1) | ||||||
|     file(WRITE ${CMAKE_CURRENT_SOURCE_DIR}/build ${BUILD_NUM}) |     file(WRITE build ${BUILD_NUM}) | ||||||
| endif() | endif() | ||||||
|  |  | ||||||
| find_package(Git QUIET) |  | ||||||
| if(GIT_FOUND AND EXISTS "${PROJECT_SOURCE_DIR}/.git") |  | ||||||
|     execute_process(COMMAND ${GIT_EXECUTABLE} describe --always --tags |  | ||||||
|             WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} |  | ||||||
|             RESULT_VARIABLE GIT_RESULT |  | ||||||
|             OUTPUT_VARIABLE GIT_HASH) |  | ||||||
|     if(NOT GIT_RESULT EQUAL "0") |  | ||||||
|         message(FATAL_ERROR "git describe --always --tags failed with ${GIT_RESULT}") |  | ||||||
|     endif() |  | ||||||
|     string(REGEX REPLACE "\n$" "" GIT_HASH "${GIT_HASH}") |  | ||||||
| endif() |  | ||||||
|  |  | ||||||
| add_definitions(-DAWS_CUSTOM_MEMORY_MANAGEMENT) |  | ||||||
|  |  | ||||||
| set(BUILD_SHARED_LIBS 1) | set(BUILD_SHARED_LIBS 1) | ||||||
|  |  | ||||||
|  | add_definitions(-DAPP_VERSION="${CMAKE_PROJECT_VERSION}" -DBUILD_NUMBER="${BUILD_NUM}") | ||||||
| add_definitions(-DTIP_SECURITY_SERVICE="1") | add_definitions(-DTIP_SECURITY_SERVICE="1") | ||||||
|  |  | ||||||
| add_compile_options(-Wall -Wextra) | set(Boost_USE_STATIC_LIBS OFF) | ||||||
| if(ASAN) | set(Boost_USE_MULTITHREADED ON) | ||||||
|     add_compile_options(-fsanitize=address) | set(Boost_USE_STATIC_RUNTIME OFF) | ||||||
|     add_link_options(-fsanitize=address) | find_package(Boost REQUIRED system) | ||||||
| endif() |  | ||||||
|  |  | ||||||
| find_package(OpenSSL REQUIRED) | find_package(OpenSSL REQUIRED) | ||||||
| find_package(ZLIB REQUIRED) | find_package(ZLIB REQUIRED) | ||||||
| find_package(fmt  REQUIRED) |  | ||||||
| find_package(AWSSDK     REQUIRED COMPONENTS sns) |  | ||||||
| find_package(nlohmann_json  REQUIRED) |  | ||||||
| find_package(CppKafka REQUIRED) | find_package(CppKafka REQUIRED) | ||||||
| find_package(PostgreSQL REQUIRED) | find_package(PostgreSQL REQUIRED) | ||||||
| find_package(MySQL REQUIRED) | find_package(MySQL REQUIRED) | ||||||
| find_package(Poco REQUIRED COMPONENTS JSON Crypto JWT Net Util NetSSL Data DataSQLite DataPostgreSQL DataMySQL) | find_package(Poco REQUIRED COMPONENTS JSON Crypto JWT Net Util NetSSL Data DataSQLite DataPostgreSQL DataMySQL) | ||||||
|  |  | ||||||
| include_directories(/usr/local/include  /usr/local/opt/openssl/include src include/kafka /usr/local/opt/mysql-client/include) |  | ||||||
|  |  | ||||||
| configure_file(src/ow_version.h.in ${PROJECT_SOURCE_DIR}/src/ow_version.h @ONLY) |  | ||||||
|  |  | ||||||
| add_executable( owsec | add_executable( owsec | ||||||
|                 build |                 build | ||||||
|         src/ow_version.h.in |  | ||||||
|         src/framework/CountryCodes.h |  | ||||||
|         src/framework/KafkaTopics.h |  | ||||||
|         src/framework/MicroService.h |  | ||||||
|         src/framework/orm.h |  | ||||||
|         src/framework/StorageClass.h |  | ||||||
|         src/framework/ow_constants.h |  | ||||||
|         src/framework/WebSocketClientNotifications.h |  | ||||||
|         src/seclibs/qrcode/qrcodegen.hpp src/seclibs/qrcode/qrcodegen.cpp |  | ||||||
|         src/seclibs/cpptotp/bytes.cpp src/seclibs/cpptotp/bytes.h |  | ||||||
|         src/seclibs/cpptotp/otp.cpp src/seclibs/cpptotp/otp.h |  | ||||||
|         src/seclibs/cpptotp/sha1.cpp src/seclibs/cpptotp/sha1.h |  | ||||||
|         src/RESTObjects/RESTAPI_SecurityObjects.h src/RESTObjects/RESTAPI_SecurityObjects.cpp |  | ||||||
|         src/RESTObjects/RESTAPI_ProvObjects.cpp src/RESTObjects/RESTAPI_ProvObjects.h |  | ||||||
|         src/RESTObjects/RESTAPI_GWobjects.h src/RESTObjects/RESTAPI_GWobjects.cpp |  | ||||||
|         src/RESTObjects/RESTAPI_FMSObjects.h src/RESTObjects/RESTAPI_FMSObjects.cpp |  | ||||||
|         src/RESTAPI/RESTAPI_oauth2_handler.h src/RESTAPI/RESTAPI_oauth2_handler.cpp |  | ||||||
|         src/RESTAPI/RESTAPI_users_handler.cpp src/RESTAPI/RESTAPI_users_handler.h |  | ||||||
|         src/RESTAPI/RESTAPI_user_handler.cpp src/RESTAPI/RESTAPI_user_handler.h |  | ||||||
|         src/RESTAPI/RESTAPI_action_links.cpp src/RESTAPI/RESTAPI_action_links.h |  | ||||||
|         src/RESTAPI/RESTAPI_validate_token_handler.cpp src/RESTAPI/RESTAPI_validate_token_handler.h |  | ||||||
|         src/RESTAPI/RESTAPI_system_endpoints_handler.cpp src/RESTAPI/RESTAPI_system_endpoints_handler.h |  | ||||||
|         src/RESTAPI/RESTAPI_asset_server.cpp src/RESTAPI/RESTAPI_asset_server.h |  | ||||||
|         src/RESTAPI/RESTAPI_avatar_handler.cpp src/RESTAPI/RESTAPI_avatar_handler.h |  | ||||||
|         src/RESTAPI/RESTAPI_subavatar_handler.cpp src/RESTAPI/RESTAPI_subavatar_handler.h |  | ||||||
|         src/RESTAPI/RESTAPI_email_handler.cpp src/RESTAPI/RESTAPI_email_handler.h |  | ||||||
|         src/RESTAPI/RESTAPI_sms_handler.cpp src/RESTAPI/RESTAPI_sms_handler.h |  | ||||||
|         src/RESTAPI/RESTAPI_suboauth2_handler.h src/RESTAPI/RESTAPI_suboauth2_handler.cpp |  | ||||||
|         src/RESTAPI/RESTAPI_subuser_handler.h src/RESTAPI/RESTAPI_subuser_handler.cpp |  | ||||||
|         src/RESTAPI/RESTAPI_subusers_handler.h src/RESTAPI/RESTAPI_subusers_handler.cpp |  | ||||||
|         src/RESTAPI/RESTAPI_validate_sub_token_handler.cpp src/RESTAPI/RESTAPI_validate_sub_token_handler.h |  | ||||||
|         src/RESTAPI/RESTAPI_submfa_handler.cpp src/RESTAPI/RESTAPI_submfa_handler.h |  | ||||||
|         src/RESTAPI/RESTAPI_preferences.cpp src/RESTAPI/RESTAPI_preferences.h |  | ||||||
|         src/RESTAPI/RESTAPI_subpreferences.cpp src/RESTAPI/RESTAPI_subpreferences.h |  | ||||||
|         src/RESTAPI/RESTAPI_routers.cpp |  | ||||||
|                 src/Daemon.h src/Daemon.cpp |                 src/Daemon.h src/Daemon.cpp | ||||||
|         src/SpecialUserHelpers.h |                 src/MicroService.cpp src/MicroService.h | ||||||
|  |                 src/SubSystemServer.cpp src/SubSystemServer.h | ||||||
|  |                 src/RESTAPI_oauth2Handler.h src/RESTAPI_oauth2Handler.cpp | ||||||
|  |                 src/RESTAPI_handler.h src/RESTAPI_handler.cpp | ||||||
|  |                 src/RESTAPI_server.cpp src/RESTAPI_server.h | ||||||
|  |                 src/RESTAPI_SecurityObjects.cpp src/RESTAPI_SecurityObjects.h | ||||||
|  |                 src/RESTAPI_system_command.h src/RESTAPI_system_command.cpp | ||||||
|  |                 src/RESTAPI_protocol.h | ||||||
|                 src/AuthService.h src/AuthService.cpp |                 src/AuthService.h src/AuthService.cpp | ||||||
|  |                 src/KafkaManager.h src/KafkaManager.cpp | ||||||
|                 src/StorageService.cpp src/StorageService.h |                 src/StorageService.cpp src/StorageService.h | ||||||
|         src/SMTPMailerService.cpp src/SMTPMailerService.h |                 src/Utils.cpp src/Utils.h | ||||||
|         src/SMSSender.cpp src/SMSSender.h |                 src/storage_setup.cpp | ||||||
|         src/MFAServer.cpp src/MFAServer.h |                 src/storage_tables.cpp src/SMTPMailerService.cpp src/SMTPMailerService.h | ||||||
|         src/SMS_provider_aws.cpp src/SMS_provider_aws.h |                 src/RESTAPI_users_handler.cpp src/RESTAPI_users_handler.h | ||||||
|         src/SMS_provider.cpp src/SMS_provider.h |                 src/RESTAPI_user_handler.cpp src/RESTAPI_user_handler.h | ||||||
|         src/SMS_provider_twilio.cpp src/SMS_provider_twilio.h |                 src/RESTAPI_action_links.cpp src/RESTAPI_action_links.h src/storage_users.cpp | ||||||
|         src/ActionLinkManager.cpp src/ActionLinkManager.h |                 src/RESTAPI_InternalServer.cpp src/RESTAPI_InternalServer.h | ||||||
|         src/ACLProcessor.h |                 src/RESTAPI_validateToken_handler.cpp src/RESTAPI_validateToken_handler.h | ||||||
|         src/framework/OpenWifiTypes.h |                 src/RESTAPI_systemEndpoints_handler.cpp src/RESTAPI_systemEndpoints_handler.h | ||||||
|         src/storage/orm_users.cpp src/storage/orm_users.h |                 src/RESTAPI_AssetServer.cpp src/RESTAPI_AssetServer.h | ||||||
|         src/storage/orm_tokens.cpp src/storage/orm_tokens.h |                 src/RESTAPI_avatarHandler.cpp src/RESTAPI_avatarHandler.h | ||||||
|         src/storage/orm_preferences.cpp src/storage/orm_preferences.h |                 src/storage_avatar.cpp src/storage_avatar.h src/storage_users.h | ||||||
|         src/storage/orm_actionLinks.cpp src/storage/orm_actionLinks.h |                 src/OpenWifiTypes.h src/RESTAPI_email_handler.cpp src/RESTAPI_email_handler.h | ||||||
|         src/storage/orm_avatar.cpp src/storage/orm_avatar.h |                 src/storage_tokens.cpp | ||||||
|         src/SpecialUserHelpers.h |                 src/RESTAPI_GenericServer.h src/RESTAPI_GenericServer.cpp | ||||||
|         src/RESTAPI/RESTAPI_db_helpers.h src/storage/orm_logins.cpp src/storage/orm_logins.h src/RESTAPI/RESTAPI_totp_handler.cpp src/RESTAPI/RESTAPI_totp_handler.h src/TotpCache.h src/RESTAPI/RESTAPI_subtotp_handler.cpp src/RESTAPI/RESTAPI_subtotp_handler.h src/RESTAPI/RESTAPI_signup_handler.cpp src/RESTAPI/RESTAPI_signup_handler.h) |                 src/RESTAPI_errors.h | ||||||
|  |                 ) | ||||||
|  |  | ||||||
| if(NOT SMALL_BUILD) | if(NOT SMALL_BUILD) | ||||||
|     target_link_libraries(owsec PUBLIC |     target_link_libraries(owsec PUBLIC | ||||||
|             ${Poco_LIBRARIES} |             ${Poco_LIBRARIES} ${Boost_LIBRARIES} ${MySQL_LIBRARIES}  ${ZLIB_LIBRARIES} | ||||||
|             ${MySQL_LIBRARIES} |  | ||||||
|             ${ZLIB_LIBRARIES} |  | ||||||
|             CppKafka::cppkafka |             CppKafka::cppkafka | ||||||
|             ${AWSSDK_LINK_LIBRARIES} |  | ||||||
|             fmt::fmt |  | ||||||
|             ) |             ) | ||||||
|     if(UNIX AND NOT APPLE) |     if(UNIX AND NOT APPLE) | ||||||
|         target_link_libraries(owsec PUBLIC PocoJSON) |         target_link_libraries(owsec PUBLIC PocoJSON) | ||||||
|   | |||||||
							
								
								
									
										105
									
								
								Dockerfile
									
									
									
									
									
								
							
							
						
						
									
										105
									
								
								Dockerfile
									
									
									
									
									
								
							| @@ -1,39 +1,15 @@ | |||||||
| FROM alpine:3.15 AS build-base | FROM alpine AS builder | ||||||
|  |  | ||||||
| RUN apk add --update --no-cache \ | RUN apk add --update --no-cache \ | ||||||
|     make cmake g++ git \ |     openssl openssh \ | ||||||
|     unixodbc-dev postgresql-dev mariadb-dev \ |     ncurses-libs \ | ||||||
|     librdkafka-dev boost-dev openssl-dev \ |     bash util-linux coreutils curl \ | ||||||
|     zlib-dev nlohmann-json \ |     make cmake gcc g++ libstdc++ libgcc git zlib-dev \ | ||||||
|     curl-dev |     openssl-dev boost-dev unixodbc-dev postgresql-dev mariadb-dev \ | ||||||
|  |     apache2-utils yaml-dev apr-util-dev \ | ||||||
|  |     librdkafka-dev | ||||||
|  |  | ||||||
| FROM build-base AS poco-build |  | ||||||
|  |  | ||||||
| ADD https://api.github.com/repos/stephb9959/poco/git/refs/heads/master version.json |  | ||||||
| RUN git clone https://github.com/stephb9959/poco /poco | RUN git clone https://github.com/stephb9959/poco /poco | ||||||
|  |  | ||||||
| WORKDIR /poco |  | ||||||
| RUN mkdir cmake-build |  | ||||||
| WORKDIR cmake-build |  | ||||||
| RUN cmake .. |  | ||||||
| RUN cmake --build . --config Release -j8 |  | ||||||
| RUN cmake --build . --target install |  | ||||||
|  |  | ||||||
| FROM build-base AS fmtlib-build |  | ||||||
|  |  | ||||||
| ADD https://api.github.com/repos/fmtlib/fmt/git/refs/heads/master version.json |  | ||||||
| RUN git clone https://github.com/fmtlib/fmt /fmtlib |  | ||||||
|  |  | ||||||
| WORKDIR /fmtlib |  | ||||||
| RUN mkdir cmake-build |  | ||||||
| WORKDIR cmake-build |  | ||||||
| RUN cmake .. |  | ||||||
| RUN make |  | ||||||
| RUN make install |  | ||||||
|  |  | ||||||
| FROM build-base AS cppkafka-build |  | ||||||
|  |  | ||||||
| ADD https://api.github.com/repos/stephb9959/cppkafka/git/refs/heads/master version.json |  | ||||||
| RUN git clone https://github.com/stephb9959/cppkafka /cppkafka | RUN git clone https://github.com/stephb9959/cppkafka /cppkafka | ||||||
|  |  | ||||||
| WORKDIR /cppkafka | WORKDIR /cppkafka | ||||||
| @@ -43,61 +19,24 @@ RUN cmake .. | |||||||
| RUN cmake --build . --config Release -j8 | RUN cmake --build . --config Release -j8 | ||||||
| RUN cmake --build . --target install | RUN cmake --build . --target install | ||||||
|  |  | ||||||
| FROM build-base AS json-schema-validator-build | WORKDIR /poco | ||||||
|  |  | ||||||
| ADD https://api.github.com/repos/pboettch/json-schema-validator/git/refs/heads/master version.json |  | ||||||
| RUN git clone https://github.com/pboettch/json-schema-validator /json-schema-validator |  | ||||||
|  |  | ||||||
| WORKDIR /json-schema-validator |  | ||||||
| RUN mkdir cmake-build | RUN mkdir cmake-build | ||||||
| WORKDIR cmake-build | WORKDIR cmake-build | ||||||
| RUN cmake .. | RUN cmake .. | ||||||
| RUN make |  | ||||||
| RUN make install |  | ||||||
|  |  | ||||||
| FROM build-base AS aws-sdk-cpp-build |  | ||||||
|  |  | ||||||
| ADD https://api.github.com/repos/aws/aws-sdk-cpp/git/refs/heads/main version.json |  | ||||||
| RUN git clone --recurse-submodules https://github.com/aws/aws-sdk-cpp /aws-sdk-cpp |  | ||||||
|  |  | ||||||
| WORKDIR /aws-sdk-cpp |  | ||||||
| RUN mkdir cmake-build |  | ||||||
| WORKDIR cmake-build |  | ||||||
| RUN cmake .. -DBUILD_ONLY="sns;s3" \ |  | ||||||
|              -DCMAKE_BUILD_TYPE=Release \ |  | ||||||
|              -DCMAKE_CXX_FLAGS="-Wno-error=stringop-overflow -Wno-error=uninitialized" \ |  | ||||||
|              -DAUTORUN_UNIT_TESTS=OFF |  | ||||||
| RUN cmake --build . --config Release -j8 | RUN cmake --build . --config Release -j8 | ||||||
| RUN cmake --build . --target install | RUN cmake --build . --target install | ||||||
|  |  | ||||||
| FROM build-base AS owsec-build |  | ||||||
|  |  | ||||||
| ADD CMakeLists.txt build /owsec/ | ADD CMakeLists.txt build /owsec/ | ||||||
| ADD cmake /owsec/cmake | ADD cmake /owsec/cmake | ||||||
| ADD src /owsec/src | ADD src /owsec/src | ||||||
| ADD .git /owsec/.git |  | ||||||
|  |  | ||||||
| COPY --from=poco-build /usr/local/include /usr/local/include |  | ||||||
| COPY --from=poco-build /usr/local/lib /usr/local/lib |  | ||||||
| COPY --from=cppkafka-build /usr/local/include /usr/local/include |  | ||||||
| COPY --from=cppkafka-build /usr/local/lib /usr/local/lib |  | ||||||
| COPY --from=json-schema-validator-build /usr/local/include /usr/local/include |  | ||||||
| COPY --from=json-schema-validator-build /usr/local/lib /usr/local/lib |  | ||||||
| COPY --from=aws-sdk-cpp-build /usr/local/include /usr/local/include |  | ||||||
| COPY --from=aws-sdk-cpp-build /usr/local/lib /usr/local/lib |  | ||||||
|  |  | ||||||
| COPY --from=fmtlib-build /usr/local/include /usr/local/include |  | ||||||
| COPY --from=fmtlib-build /usr/local/lib /usr/local/lib |  | ||||||
|  |  | ||||||
| WORKDIR /owsec | WORKDIR /owsec | ||||||
| RUN mkdir cmake-build | RUN mkdir cmake-build | ||||||
| WORKDIR /owsec/cmake-build | WORKDIR /owsec/cmake-build | ||||||
| RUN cmake .. \ | RUN cmake .. | ||||||
|           -Dcrypto_LIBRARY=/usr/lib/libcrypto.so \ |  | ||||||
|           -DBUILD_SHARED_LIBS=ON |  | ||||||
| RUN cmake --build . --config Release -j8 | RUN cmake --build . --config Release -j8 | ||||||
|  |  | ||||||
| FROM alpine:3.15 | FROM alpine | ||||||
|  |  | ||||||
| ENV OWSEC_USER=owsec \ | ENV OWSEC_USER=owsec \ | ||||||
|     OWSEC_ROOT=/owsec-data \ |     OWSEC_ROOT=/owsec-data \ | ||||||
| @@ -109,28 +48,16 @@ RUN addgroup -S "$OWSEC_USER" && \ | |||||||
| RUN mkdir /openwifi | RUN mkdir /openwifi | ||||||
| RUN mkdir -p "$OWSEC_ROOT" "$OWSEC_CONFIG" && \ | RUN mkdir -p "$OWSEC_ROOT" "$OWSEC_CONFIG" && \ | ||||||
|     chown "$OWSEC_USER": "$OWSEC_ROOT" "$OWSEC_CONFIG" |     chown "$OWSEC_USER": "$OWSEC_ROOT" "$OWSEC_CONFIG" | ||||||
|  | RUN apk add --update --no-cache librdkafka mariadb-connector-c libpq unixodbc su-exec gettext ca-certificates | ||||||
|  | COPY --from=builder /owsec/cmake-build/owsec /openwifi/owsec | ||||||
|  | COPY --from=builder /cppkafka/cmake-build/src/lib/* /lib/ | ||||||
|  | COPY --from=builder /poco/cmake-build/lib/* /lib/ | ||||||
|  |  | ||||||
| RUN apk add --update --no-cache librdkafka su-exec gettext ca-certificates bash jq curl \ | COPY owsec.properties.tmpl ${OWSEC_CONFIG}/ | ||||||
|     mariadb-connector-c libpq unixodbc postgresql-client |  | ||||||
|  |  | ||||||
| COPY readiness_check /readiness_check |  | ||||||
| COPY test_scripts/curl/cli /cli |  | ||||||
|  |  | ||||||
| COPY owsec.properties.tmpl / |  | ||||||
| COPY wwwassets /dist/wwwassets |  | ||||||
| COPY templates /dist/templates |  | ||||||
| COPY docker-entrypoint.sh / | COPY docker-entrypoint.sh / | ||||||
| COPY wait-for-postgres.sh / |  | ||||||
| RUN wget https://raw.githubusercontent.com/Telecominfraproject/wlan-cloud-ucentral-deploy/main/docker-compose/certs/restapi-ca.pem \ | RUN wget https://raw.githubusercontent.com/Telecominfraproject/wlan-cloud-ucentral-deploy/main/docker-compose/certs/restapi-ca.pem \ | ||||||
|     -O /usr/local/share/ca-certificates/restapi-ca-selfsigned.pem |     -O /usr/local/share/ca-certificates/restapi-ca-selfsigned.pem | ||||||
|  |  | ||||||
| COPY --from=owsec-build /owsec/cmake-build/owsec /openwifi/owsec |  | ||||||
| COPY --from=cppkafka-build /cppkafka/cmake-build/src/lib/* /usr/local/lib |  | ||||||
| COPY --from=poco-build /poco/cmake-build/lib/* /usr/local/lib |  | ||||||
| COPY --from=aws-sdk-cpp-build /aws-sdk-cpp/cmake-build/aws-cpp-sdk-core/libaws-cpp-sdk-core.so /usr/local/lib |  | ||||||
| COPY --from=aws-sdk-cpp-build /aws-sdk-cpp/cmake-build/aws-cpp-sdk-s3/libaws-cpp-sdk-s3.so /usr/local/lib |  | ||||||
| COPY --from=aws-sdk-cpp-build /aws-sdk-cpp/cmake-build/aws-cpp-sdk-sns/libaws-cpp-sdk-sns.so /usr/local/lib |  | ||||||
|  |  | ||||||
| EXPOSE 16001 17001 16101 | EXPOSE 16001 17001 16101 | ||||||
|  |  | ||||||
| ENTRYPOINT ["/docker-entrypoint.sh"] | ENTRYPOINT ["/docker-entrypoint.sh"] | ||||||
|   | |||||||
							
								
								
									
										114
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										114
									
								
								README.md
									
									
									
									
									
								
							| @@ -13,12 +13,12 @@ into your own systems. If all you need it to access the uCentralGW for example ( | |||||||
| - choose one to manage (pick an endpoint that matches what you are trying to do by looking at its `type`. For the gateway, type = ucentrtalgw) | - choose one to manage (pick an endpoint that matches what you are trying to do by looking at its `type`. For the gateway, type = ucentrtalgw) | ||||||
| - make your calls (use the PublicEndPoint of the corresponding entry to make your calls, do not forget to add `/api/v1` as the root os the call) | - make your calls (use the PublicEndPoint of the corresponding entry to make your calls, do not forget to add `/api/v1` as the root os the call) | ||||||
|  |  | ||||||
| The CLI for the [uCentralGW](https://github.com/telecominfraproject/wlan-cloud-ucentralgw/blob/main/test_scripts/curl/cli) has a very good example of this.  | The CLI for the [uCentralGW](https://github.com/telecominfraproject/wlan-cloud-ucentralgw/blob/main/test_scripts/curl/cli) has a very good example of this. Loog for the `setgateway`  | ||||||
| Look for the `setgateway` function. | function. | ||||||
|  |  | ||||||
| ## Firewall Considerations | ## Firewall Considerations | ||||||
| The entire uCentral systems uses several MicroServices. In order for the whole system to work, you should provide the following port | The entire uCentral systems uses several MicroServices. In order for the whole system to work, you should provide the following port | ||||||
| access: | access | ||||||
|  |  | ||||||
| - Security | - Security | ||||||
|   - Properties file: owsec.properties |   - Properties file: owsec.properties | ||||||
| @@ -28,21 +28,14 @@ access: | |||||||
|     - ALB: 16101 |     - ALB: 16101 | ||||||
|  |  | ||||||
| - Gateway: | - Gateway: | ||||||
|   - Properties file: owgw.properties |   - Properties file: ucentralgw.properties | ||||||
|   - Ports |   - Ports | ||||||
|     - Public: 16002 |     - Public: 16002 | ||||||
|     - Private: 17002 |     - Private: 17002 | ||||||
|     - ALB: 16102 |     - ALB: 16102 | ||||||
|  |  | ||||||
| - Firmware: | - Firmware: | ||||||
|   - Properties file: owfms.properties |   - Properties file: ucentralfms.properties | ||||||
|   - Ports |  | ||||||
|     - Public: 16004 |  | ||||||
|     - Private: 17004 |  | ||||||
|     - ALB: 16104 |  | ||||||
|  |  | ||||||
| - Provisioning: |  | ||||||
|   - Properties file: owprov.properties |  | ||||||
|   - Ports |   - Ports | ||||||
|     - Public: 16004 |     - Public: 16004 | ||||||
|     - Private: 17004 |     - Private: 17004 | ||||||
| @@ -86,6 +79,7 @@ Is this safe to show the hash in a text file? Let me put it this way, if you can | |||||||
| would have control over the entire internet. It's incredibly safe. If you love math, you can find a lot of videos explaining | would have control over the entire internet. It's incredibly safe. If you love math, you can find a lot of videos explaining | ||||||
| how hashes work and why they are safe. | how hashes work and why they are safe. | ||||||
|  |  | ||||||
|  |  | ||||||
| ### `authentication.validation.expression` | ### `authentication.validation.expression` | ||||||
| This is a regular expression (regex) to verify the incoming password. You can find many examples on the internet on how to create these expressions. I suggest  | This is a regular expression (regex) to verify the incoming password. You can find many examples on the internet on how to create these expressions. I suggest  | ||||||
| that using Google is your friend. Someone has figured out what you want to do already. Click [here](https://stackoverflow.com/questions/19605150/regex-for-password-must-contain-at-least-eight-characters-at-least-one-number-a) | that using Google is your friend. Someone has figured out what you want to do already. Click [here](https://stackoverflow.com/questions/19605150/regex-for-password-must-contain-at-least-eight-characters-at-least-one-number-a) | ||||||
| @@ -98,45 +92,11 @@ to get a sample. The default is | |||||||
| ### `authentication.oldpasswords` | ### `authentication.oldpasswords` | ||||||
| The number of older passwords to keep. Default is 5. | The number of older passwords to keep. Default is 5. | ||||||
|  |  | ||||||
| ### Changing default password |  | ||||||
|  |  | ||||||
| On the first startup of the service new user will be created with the default credentials from properties `authentication.default.username` and `authentication.default.password`, but **you will have to change the password** before making any real requests. |  | ||||||
|  |  | ||||||
| You can this using [owgw-ui](https://github.com/Telecominfraproject/wlan-cloud-ucentralgw-ui/) on first login or using the following script: |  | ||||||
|  |  | ||||||
| ``` |  | ||||||
| export OWSEC=openwifi.wlan.local:16001 # endpoint to your owsec RESTAPI endpoint |  | ||||||
| #export FLAGS="-k" # uncomment and add curl flags that you would like to pass for the request (for example '-k' may be used to pass errors with self-signed certificates) |  | ||||||
| export OWSEC_DEFAULT_USERNAME=root@system.com # default username that you've set in property 'authentication.default.username' |  | ||||||
| export OWSEC_DEFAULT_PASSWORD=weLoveWifi # default password __in cleartext__ from property 'authentication.default.password' |  | ||||||
| export OWSEC_NEW_PASSWORD=NewPass123% # new password that must be set for the user (must comply with 'authentication.validation.expression') |  | ||||||
| test_scripts/curl/cli testlogin $OWSEC_DEFAULT_USERNAME $OWSEC_DEFAULT_PASSWORD $OWSEC_NEW_PASSWORD |  | ||||||
| ``` |  | ||||||
|  |  | ||||||
| CLI is also included in Docker image if you want to run it this way: |  | ||||||
|  |  | ||||||
| ``` |  | ||||||
| export OWSEC=openwifi.wlan.local:16001 |  | ||||||
| #export FLAGS="-k" |  | ||||||
| export OWSEC_DEFAULT_USERNAME=root@system.com |  | ||||||
| export OWSEC_DEFAULT_PASSWORD=weLoveWifi |  | ||||||
| export OWSEC_NEW_PASSWORD=NewPass123% |  | ||||||
| docker run --rm -ti \ |  | ||||||
|   --network=host \ |  | ||||||
|   --env OWSEC \ |  | ||||||
|   --env FLAGS \ |  | ||||||
|   --env OWSEC_DEFAULT_USERNAME \ |  | ||||||
|   --env OWSEC_DEFAULT_PASSWORD \ |  | ||||||
|   --env OWSEC_NEW_PASSWORD \ |  | ||||||
|   tip-tip-wlan-cloud-ucentral.jfrog.io/owsec:main \ |  | ||||||
|   /cli testlogin $OWSEC_DEFAULT_USERNAME $OWSEC_DEFAULT_PASSWORD $OWSEC_NEW_PASSWORD |  | ||||||
| ``` |  | ||||||
|  |  | ||||||
| ### Kafka integration | ### Kafka integration | ||||||
| This security service uses Kafka to coordinate security with other services that are part of the system. You must have a Kafka service running | This security service uses Kafka to coordinate security with other services that are part of the system. You must have a Kafka service running | ||||||
| in order to use this. You can find several examples of Kafka services available with Docker. Here are the values you need to configure. | in order to use this. You can find several examples of Kafka services available with Docker. Here are the values you need to configure. | ||||||
|  |  | ||||||
| ``` | ```asm | ||||||
| openwifi.kafka.group.id = security | openwifi.kafka.group.id = security | ||||||
| openwifi.kafka.client.id = security1 | openwifi.kafka.client.id = security1 | ||||||
| openwifi.kafka.enable = true | openwifi.kafka.enable = true | ||||||
| @@ -166,7 +126,7 @@ Here are the parameters for the public interface. The important files are: | |||||||
| - `restapi-key.pem` : the key associated with this certificate | - `restapi-key.pem` : the key associated with this certificate | ||||||
| - `openwifi.restapi.host.0.key.password` : if you key is password protected, you may supply that password here. | - `openwifi.restapi.host.0.key.password` : if you key is password protected, you may supply that password here. | ||||||
|  |  | ||||||
| ``` | ```asm | ||||||
| openwifi.restapi.host.0.backlog = 100 | openwifi.restapi.host.0.backlog = 100 | ||||||
| openwifi.restapi.host.0.security = relaxed | openwifi.restapi.host.0.security = relaxed | ||||||
| openwifi.restapi.host.0.rootca = $OWSEC_ROOT/certs/restapi-ca.pem | openwifi.restapi.host.0.rootca = $OWSEC_ROOT/certs/restapi-ca.pem | ||||||
| @@ -181,7 +141,7 @@ openwifi.restapi.host.0.key.password = mypassword | |||||||
| The private interface is used for service-to-service communication. You can use self-signed certificates here or letsencrypt. The file names are similar  | The private interface is used for service-to-service communication. You can use self-signed certificates here or letsencrypt. The file names are similar  | ||||||
| to the filenames used in the previous section. | to the filenames used in the previous section. | ||||||
|  |  | ||||||
| ``` | ```asm | ||||||
| openwifi.internal.restapi.host.0.backlog = 100 | openwifi.internal.restapi.host.0.backlog = 100 | ||||||
| openwifi.internal.restapi.host.0.security = relaxed | openwifi.internal.restapi.host.0.security = relaxed | ||||||
| openwifi.internal.restapi.host.0.rootca = $OWSEC_ROOT/certs/restapi-ca.pem | openwifi.internal.restapi.host.0.rootca = $OWSEC_ROOT/certs/restapi-ca.pem | ||||||
| @@ -196,15 +156,14 @@ openwifi.internal.restapi.host.0.key.password = mypassword | |||||||
| Here are other important values you must set. | Here are other important values you must set. | ||||||
|  |  | ||||||
|  |  | ||||||
| ``` | ```asm | ||||||
| openwifi.system.data = $OWSEC_ROOT/data | openwifi.system.data = $OWSEC_ROOT/data | ||||||
| openwifi.system.uri.private = https://localhost:17001 | openwifi.system.uri.private = https://localhost:17001 | ||||||
| openwifi.system.uri.public = https://openwifi.dpaas.arilia.com:16001 | openwifi.system.uri.public = https://openwifi.dpaas.arilia.com:16001 | ||||||
| openwifi.system.uri.ui = https://ucentral-ui.arilia.com |  | ||||||
| openwifi.system.commandchannel = /tmp/app.ucentralsec | openwifi.system.commandchannel = /tmp/app.ucentralsec | ||||||
| openwifi.service.key = $OWSEC_ROOT/certs/restapi-key.pem | openwifi.service.key = $OWSEC_ROOT/certs/restapi-key.pem | ||||||
| openwifi.service.key.password = mypassword |  | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
| #### `openwifi.system.data` | #### `openwifi.system.data` | ||||||
| The location of some important data files including the user name database. | The location of some important data files including the user name database. | ||||||
|  |  | ||||||
| @@ -214,54 +173,3 @@ This is the FQDN used internally between services. | |||||||
| #### `openwifi.system.uri.public` | #### `openwifi.system.uri.public` | ||||||
| This is the FQDN used externally serving the OpenAPI interface. | This is the FQDN used externally serving the OpenAPI interface. | ||||||
|  |  | ||||||
| ### Sending SMS for Multifactor Aithentication |  | ||||||
| `owsec` hs the ability to send SMS messages to users during login or to send notifications. In order to do so, |  | ||||||
| an SMS provider must be configured. At present time, 2 providers are supported: Tilio and AWS SNS |  | ||||||
|  |  | ||||||
| #### AWS SMS |  | ||||||
| For SNS you must create an IAM ID that has sns:sendmessage rights.   |  | ||||||
|  |  | ||||||
| ``` |  | ||||||
| smssender.enabled = true |  | ||||||
| smssender.provider = aws |  | ||||||
| smssender.aws.secretkey = *************************************** |  | ||||||
| smssender.aws.accesskey = *************************************** |  | ||||||
| smssender.aws.region = ************** |  | ||||||
| ``` |  | ||||||
|  |  | ||||||
| #### Twilio |  | ||||||
| For Twilio, you must provide the following |  | ||||||
|  |  | ||||||
| ``` |  | ||||||
| smssender.enabled = true |  | ||||||
| smssender.provider = twilio |  | ||||||
| smssender.twilio.sid = *********************** |  | ||||||
| smssender.twilio.token = ********************** |  | ||||||
| smssender.twilio.phonenumber = +18888888888 |  | ||||||
| ``` |  | ||||||
|  |  | ||||||
| ### `owsec` Messaging Configuration |  | ||||||
| `owsec` nay require to send e-mails. In order to do so, you must configure an email sender. We have run tests  |  | ||||||
| with GMail and AWS SES. For each, you must obtain the proper credentials and insert them in this configuration as well |  | ||||||
| as the proper mail host. |  | ||||||
|  |  | ||||||
| ``` |  | ||||||
| mailer.enabled = true |  | ||||||
| mailer.hostname = smtp.gmail.com |  | ||||||
| mailer.username = ************************ |  | ||||||
| mailer.password = ************************ |  | ||||||
| mailer.sender = OpenWIFI |  | ||||||
| mailer.loginmethod = login |  | ||||||
| mailer.port = 587 |  | ||||||
| mailer.templates = $OWSEC_ROOT/templates |  | ||||||
| ``` |  | ||||||
|  |  | ||||||
| #### Google Authenticator |  | ||||||
| In order to use the Google Time-based One-Time Password (TOTP), the user must download the Google Authenticator  |  | ||||||
| on any other app that support the TOTP protocol. You should include the following in your configuration |  | ||||||
|  |  | ||||||
| ``` |  | ||||||
| totp.issuer = OrgName |  | ||||||
| ``` |  | ||||||
|  |  | ||||||
| It is very important that you not use spaces in your OrgName. |  | ||||||
| @@ -11,7 +11,7 @@ if [[ "$TEMPLATE_CONFIG" = 'true' && ! -f "$OWSEC_CONFIG"/owsec.properties ]]; t | |||||||
|   RESTAPI_HOST_CERT=${RESTAPI_HOST_CERT:-"\$OWSEC_ROOT/certs/restapi-cert.pem"} \ |   RESTAPI_HOST_CERT=${RESTAPI_HOST_CERT:-"\$OWSEC_ROOT/certs/restapi-cert.pem"} \ | ||||||
|   RESTAPI_HOST_KEY=${RESTAPI_HOST_KEY:-"\$OWSEC_ROOT/certs/restapi-key.pem"} \ |   RESTAPI_HOST_KEY=${RESTAPI_HOST_KEY:-"\$OWSEC_ROOT/certs/restapi-key.pem"} \ | ||||||
|   RESTAPI_HOST_KEY_PASSWORD=${RESTAPI_HOST_KEY_PASSWORD:-"mypassword"} \ |   RESTAPI_HOST_KEY_PASSWORD=${RESTAPI_HOST_KEY_PASSWORD:-"mypassword"} \ | ||||||
|   RESTAPI_WWWASSETS=${RESTAPI_WWWASSETS:-"\$OWSEC_ROOT/persist/wwwassets"} \ |   RESTAPI_WWWASSETS=${RESTAPI_WWWASSETS:-"\$OWSEC_ROOT/wwwassets"} \ | ||||||
|   INTERNAL_RESTAPI_HOST_ROOTCA=${INTERNAL_RESTAPI_HOST_ROOTCA:-"\$OWSEC_ROOT/certs/restapi-ca.pem"} \ |   INTERNAL_RESTAPI_HOST_ROOTCA=${INTERNAL_RESTAPI_HOST_ROOTCA:-"\$OWSEC_ROOT/certs/restapi-ca.pem"} \ | ||||||
|   INTERNAL_RESTAPI_HOST_PORT=${INTERNAL_RESTAPI_HOST_PORT:-"17001"} \ |   INTERNAL_RESTAPI_HOST_PORT=${INTERNAL_RESTAPI_HOST_PORT:-"17001"} \ | ||||||
|   INTERNAL_RESTAPI_HOST_CERT=${INTERNAL_RESTAPI_HOST_CERT:-"\$OWSEC_ROOT/certs/restapi-cert.pem"} \ |   INTERNAL_RESTAPI_HOST_CERT=${INTERNAL_RESTAPI_HOST_CERT:-"\$OWSEC_ROOT/certs/restapi-cert.pem"} \ | ||||||
| @@ -25,29 +25,13 @@ if [[ "$TEMPLATE_CONFIG" = 'true' && ! -f "$OWSEC_CONFIG"/owsec.properties ]]; t | |||||||
|   SYSTEM_URI_UI=${SYSTEM_URI_UI:-"http://localhost"} \ |   SYSTEM_URI_UI=${SYSTEM_URI_UI:-"http://localhost"} \ | ||||||
|   SERVICE_KEY=${SERVICE_KEY:-"\$OWSEC_ROOT/certs/restapi-key.pem"} \ |   SERVICE_KEY=${SERVICE_KEY:-"\$OWSEC_ROOT/certs/restapi-key.pem"} \ | ||||||
|   SERVICE_KEY_PASSWORD=${SERVICE_KEY_PASSWORD:-"mypassword"} \ |   SERVICE_KEY_PASSWORD=${SERVICE_KEY_PASSWORD:-"mypassword"} \ | ||||||
|   SMSSENDER_ENABLED=${SMSSENDER_ENABLED:-"false"} \ |   MAILER_HOSTNAME=${MAILER_HOSTNAME:-"smtp.gmail.com"} \ | ||||||
|   SMSSENDER_PROVIDER=${SMSSENDER_PROVIDER:-""} \ |   MAILER_USERNAME=${MAILER_USERNAME:-"************************"} \ | ||||||
|   SMSSENDER_AWS_SECRETKEY=${SMSSENDER_AWS_SECRETKEY:-""} \ |   MAILER_PASSWORD=${MAILER_PASSWORD:-"************************"} \ | ||||||
|   SMSSENDER_AWS_ACCESSKEY=${SMSSENDER_AWS_ACCESSKEY:-""} \ |  | ||||||
|   SMSSENDER_AWS_REGION=${SMSSENDER_AWS_REGION:-""} \ |  | ||||||
|   SMSSENDER_TWILIO_SID=${SMSSENDER_TWILIO_SID:-""} \ |  | ||||||
|   SMSSENDER_TWILIO_TOKEN=${SMSSENDER_TWILIO_TOKEN:-""} \ |  | ||||||
|   SMSSENDER_TWILIO_PHONENUMBER=${SMSSENDER_TWILIO_PHONENUMBER:-""} \ |  | ||||||
|   MAILER_ENABLED=${MAILER_ENABLED:-"false"} \ |  | ||||||
|   MAILER_HOSTNAME=${MAILER_HOSTNAME:-"localhost"} \ |  | ||||||
|   MAILER_USERNAME=${MAILER_USERNAME:-""} \ |  | ||||||
|   MAILER_PASSWORD=${MAILER_PASSWORD:-""} \ |  | ||||||
|   MAILER_SENDER=${MAILER_SENDER:-"OpenWIFI"} \ |   MAILER_SENDER=${MAILER_SENDER:-"OpenWIFI"} \ | ||||||
|   MAILER_PORT=${MAILER_PORT:-"587"} \ |   MAILER_PORT=${MAILER_PORT:-"587"} \ | ||||||
|   MAILER_TEMPLATES=${MAILER_TEMPLATES:-"\$OWSEC_ROOT/persist/templates"} \ |  | ||||||
|   KAFKA_ENABLE=${KAFKA_ENABLE:-"true"} \ |   KAFKA_ENABLE=${KAFKA_ENABLE:-"true"} \ | ||||||
|   KAFKA_BROKERLIST=${KAFKA_BROKERLIST:-"localhost:9092"} \ |   KAFKA_BROKERLIST=${KAFKA_BROKERLIST:-"localhost:9092"} \ | ||||||
|   KAFKA_SSL_CA_LOCATION=${KAFKA_SSL_CA_LOCATION:-""} \ |  | ||||||
|   KAFKA_SSL_CERTIFICATE_LOCATION=${KAFKA_SSL_CERTIFICATE_LOCATION:-""} \ |  | ||||||
|   KAFKA_SSL_KEY_LOCATION=${KAFKA_SSL_KEY_LOCATION:-""} \ |  | ||||||
|   KAFKA_SSL_KEY_PASSWORD=${KAFKA_SSL_KEY_PASSWORD:-""} \ |  | ||||||
|   DOCUMENT_POLICY_ACCESS=${DOCUMENT_POLICY_ACCESS:-"\$OWSEC_ROOT/persist/wwwassets/access_policy.html"} \ |  | ||||||
|   DOCUMENT_POLICY_PASSWORD=${DOCUMENT_POLICY_PASSWORD:-"\$OWSEC_ROOT/persist/wwwassets/password_policy.html"} \ |  | ||||||
|   STORAGE_TYPE=${STORAGE_TYPE:-"sqlite"} \ |   STORAGE_TYPE=${STORAGE_TYPE:-"sqlite"} \ | ||||||
|   STORAGE_TYPE_POSTGRESQL_HOST=${STORAGE_TYPE_POSTGRESQL_HOST:-"localhost"} \ |   STORAGE_TYPE_POSTGRESQL_HOST=${STORAGE_TYPE_POSTGRESQL_HOST:-"localhost"} \ | ||||||
|   STORAGE_TYPE_POSTGRESQL_USERNAME=${STORAGE_TYPE_POSTGRESQL_USERNAME:-"owsec"} \ |   STORAGE_TYPE_POSTGRESQL_USERNAME=${STORAGE_TYPE_POSTGRESQL_USERNAME:-"owsec"} \ | ||||||
| @@ -59,25 +43,7 @@ if [[ "$TEMPLATE_CONFIG" = 'true' && ! -f "$OWSEC_CONFIG"/owsec.properties ]]; t | |||||||
|   STORAGE_TYPE_MYSQL_PASSWORD=${STORAGE_TYPE_MYSQL_PASSWORD:-"owsec"} \ |   STORAGE_TYPE_MYSQL_PASSWORD=${STORAGE_TYPE_MYSQL_PASSWORD:-"owsec"} \ | ||||||
|   STORAGE_TYPE_MYSQL_DATABASE=${STORAGE_TYPE_MYSQL_DATABASE:-"owsec"} \ |   STORAGE_TYPE_MYSQL_DATABASE=${STORAGE_TYPE_MYSQL_DATABASE:-"owsec"} \ | ||||||
|   STORAGE_TYPE_MYSQL_PORT=${STORAGE_TYPE_MYSQL_PORT:-"3306"} \ |   STORAGE_TYPE_MYSQL_PORT=${STORAGE_TYPE_MYSQL_PORT:-"3306"} \ | ||||||
|   envsubst < /owsec.properties.tmpl > $OWSEC_CONFIG/owsec.properties |   envsubst < $OWSEC_CONFIG/owsec.properties.tmpl > $OWSEC_CONFIG/owsec.properties | ||||||
| fi |  | ||||||
|  |  | ||||||
| # Check if wwwassets directory exists |  | ||||||
| export RESTAPI_WWWASSETS=$(grep 'openwifi.restapi.wwwassets' $OWSEC_CONFIG/owsec.properties | awk -F '=' '{print $2}' | xargs | envsubst) |  | ||||||
| if [[ ! -d "$(dirname $RESTAPI_WWWASSETS)" ]]; then |  | ||||||
|   mkdir -p $(dirname $RESTAPI_WWWASSETS) |  | ||||||
| fi |  | ||||||
| if [[ ! -d "$RESTAPI_WWWASSETS" ]]; then |  | ||||||
|   cp -r /dist/wwwassets $RESTAPI_WWWASSETS |  | ||||||
| fi |  | ||||||
|  |  | ||||||
| # Check if templates directory exists |  | ||||||
| export MAILER_TEMPLATES=$(grep 'mailer.templates' $OWSEC_CONFIG/owsec.properties | awk -F '=' '{print $2}' | xargs | envsubst) |  | ||||||
| if [[ ! -d "$(dirname $MAILER_TEMPLATES)" ]]; then |  | ||||||
|   mkdir -p $(dirname $MAILER_TEMPLATES) |  | ||||||
| fi |  | ||||||
| if [[ ! -d "$MAILER_TEMPLATES" ]]; then |  | ||||||
|   cp -r /dist/templates $MAILER_TEMPLATES |  | ||||||
| fi | fi | ||||||
|  |  | ||||||
| if [ "$1" = '/openwifi/owsec' -a "$(id -u)" = '0' ]; then | if [ "$1" = '/openwifi/owsec' -a "$(id -u)" = '0' ]; then | ||||||
|   | |||||||
| @@ -5,14 +5,14 @@ name: owsec | |||||||
| version: 0.1.0 | version: 0.1.0 | ||||||
| dependencies: | dependencies: | ||||||
| - name: postgresql | - name: postgresql | ||||||
|   repository: https://tip.jfrog.io/artifactory/tip-wlan-cloud-ucentral-helm/ |   repository: https://charts.bitnami.com/bitnami | ||||||
|   version: 10.9.2 |   version: 10.9.2 | ||||||
|   condition: postgresql.enabled |   condition: postgresql.enabled | ||||||
| - name: mysql | - name: mysql | ||||||
|   repository: https://tip.jfrog.io/artifactory/tip-wlan-cloud-ucentral-helm/ |   repository: https://charts.bitnami.com/bitnami | ||||||
|   version: 8.8.3 |   version: 8.8.3 | ||||||
|   condition: mysql.enabled |   condition: mysql.enabled | ||||||
| - name: mariadb | - name: mariadb | ||||||
|   repository: https://tip.jfrog.io/artifactory/tip-wlan-cloud-ucentral-helm/ |   repository: https://charts.bitnami.com/bitnami | ||||||
|   version: 9.4.2 |   version: 9.4.2 | ||||||
|   condition: mariadb.enabled |   condition: mariadb.enabled | ||||||
|   | |||||||
| @@ -20,7 +20,7 @@ Currently this chart is not assembled in charts archives, so [helm-git](https:// | |||||||
| To install the chart with the release name `my-release`: | To install the chart with the release name `my-release`: | ||||||
|  |  | ||||||
| ```bash | ```bash | ||||||
| $ helm install --name my-release git+https://github.com/Telecominfraproject/wlan-cloud-ucentralsec@helm/owsec-0.1.0.tgz?ref=main | $ helm install --name my-release git+https://github.com/Telecominfraproject/wlan-cloud-ucentralsec@helm?ref=main | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
| The command deploys the Security on the Kubernetes cluster in the default configuration. The [configuration](#configuration) section lists the parameters that can be configured during installation. | The command deploys the Security on the Kubernetes cluster in the default configuration. The [configuration](#configuration) section lists the parameters that can be configured during installation. | ||||||
|   | |||||||
| @@ -30,13 +30,3 @@ Create chart name and version as used by the chart label. | |||||||
| {{- define "owsec.chart" -}} | {{- define "owsec.chart" -}} | ||||||
| {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} | {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} | ||||||
| {{- end -}} | {{- end -}} | ||||||
|  |  | ||||||
| {{- define "owsec.ingress.apiVersion" -}} |  | ||||||
|   {{- if .Capabilities.APIVersions.Has "networking.k8s.io/v1" -}} |  | ||||||
|       {{- print "networking.k8s.io/v1" -}} |  | ||||||
|   {{- else if .Capabilities.APIVersions.Has "networking.k8s.io/v1beta1" -}} |  | ||||||
|     {{- print "networking.k8s.io/v1beta1" -}} |  | ||||||
|   {{- else -}} |  | ||||||
|     {{- print "extensions/v1beta1" -}} |  | ||||||
|   {{- end -}} |  | ||||||
| {{- end -}} |  | ||||||
|   | |||||||
| @@ -13,7 +13,6 @@ spec: | |||||||
|   replicas: {{ .Values.replicaCount }} |   replicas: {{ .Values.replicaCount }} | ||||||
|   strategy: |   strategy: | ||||||
|     type: {{ .Values.strategyType }} |     type: {{ .Values.strategyType }} | ||||||
|   revisionHistoryLimit: {{ .Values.revisionHistoryLimit }} |  | ||||||
|   selector: |   selector: | ||||||
|     matchLabels: |     matchLabels: | ||||||
|       app.kubernetes.io/name: {{ include "owsec.name" . }} |       app.kubernetes.io/name: {{ include "owsec.name" . }} | ||||||
| @@ -25,9 +24,6 @@ spec: | |||||||
|     metadata: |     metadata: | ||||||
|       annotations: |       annotations: | ||||||
|         checksum/config: {{ include "owsec.config" . | sha256sum }} |         checksum/config: {{ include "owsec.config" . | sha256sum }} | ||||||
|         {{- with .Values.podAnnotations }} |  | ||||||
|         {{- toYaml . | nindent 8 }} |  | ||||||
|         {{- end }} |  | ||||||
|       labels: |       labels: | ||||||
|         app.kubernetes.io/name: {{ include "owsec.name" . }} |         app.kubernetes.io/name: {{ include "owsec.name" . }} | ||||||
|         app.kubernetes.io/instance: {{ .Release.Name }} |         app.kubernetes.io/instance: {{ .Release.Name }} | ||||||
| @@ -36,16 +32,6 @@ spec: | |||||||
|         {{- end }} |         {{- end }} | ||||||
|     spec: |     spec: | ||||||
|  |  | ||||||
|       initContainers: |  | ||||||
|         - name: wait-kafka |  | ||||||
|           image: "{{ .Values.images.dockerize.repository }}:{{ .Values.images.dockerize.tag }}" |  | ||||||
|           imagePullPolicy: {{ .Values.images.dockerize.pullPolicy }} |  | ||||||
|           args: |  | ||||||
|             - -wait |  | ||||||
|             - tcp://{{ index .Values.configProperties "openwifi.kafka.brokerlist" }} |  | ||||||
|             - -timeout |  | ||||||
|             - 600s |  | ||||||
|  |  | ||||||
|       containers: |       containers: | ||||||
|  |  | ||||||
|         - name: owsec |         - name: owsec | ||||||
|   | |||||||
| @@ -2,7 +2,7 @@ | |||||||
| {{- range $ingress, $ingressValue := .Values.ingresses }} | {{- range $ingress, $ingressValue := .Values.ingresses }} | ||||||
| {{- if $ingressValue.enabled }} | {{- if $ingressValue.enabled }} | ||||||
| --- | --- | ||||||
| apiVersion: {{ include "owsec.ingress.apiVersion" $root }} | apiVersion: extensions/v1beta1 | ||||||
| kind: Ingress | kind: Ingress | ||||||
| metadata: | metadata: | ||||||
|   name: {{ include "owsec.fullname" $root }}-{{ $ingress }} |   name: {{ include "owsec.fullname" $root }}-{{ $ingress }} | ||||||
| @@ -36,25 +36,11 @@ spec: | |||||||
|       paths: |       paths: | ||||||
|       {{- range $ingressValue.paths }} |       {{- range $ingressValue.paths }} | ||||||
|         - path: {{ .path }} |         - path: {{ .path }} | ||||||
|           {{- if $root.Capabilities.APIVersions.Has "networking.k8s.io/v1" }} |  | ||||||
|           pathType: {{ .pathType | default "ImplementationSpecific" }} |  | ||||||
|           {{- end }} |  | ||||||
|           backend: |           backend: | ||||||
|             {{- if $root.Capabilities.APIVersions.Has "networking.k8s.io/v1" }} |  | ||||||
|             service: |  | ||||||
|               name: {{ include "owsec.fullname" $root }}-{{ .serviceName }} |  | ||||||
|               port: |  | ||||||
|               {{- if kindIs "string" .servicePort }} |  | ||||||
|                 name: {{ .servicePort }} |  | ||||||
|               {{- else }} |  | ||||||
|                 number: {{ .servicePort }} |  | ||||||
|               {{- end }} |  | ||||||
|             {{- else }} |  | ||||||
|             serviceName: {{ include "owsec.fullname" $root }}-{{ .serviceName }} |             serviceName: {{ include "owsec.fullname" $root }}-{{ .serviceName }} | ||||||
|             servicePort: {{ .servicePort }} |             servicePort: {{ .servicePort }} | ||||||
|       {{- end }} |       {{- end }} | ||||||
|   {{- end }} |   {{- end }} | ||||||
|   {{- end }} |  | ||||||
|  |  | ||||||
| {{- end }} | {{- end }} | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,7 +1,6 @@ | |||||||
| # System | # System | ||||||
| replicaCount: 1 | replicaCount: 1 | ||||||
| strategyType: Recreate | strategyType: Recreate | ||||||
| revisionHistoryLimit: 2 |  | ||||||
|  |  | ||||||
| nameOverride: "" | nameOverride: "" | ||||||
| fullnameOverride: "" | fullnameOverride: "" | ||||||
| @@ -9,20 +8,16 @@ fullnameOverride: "" | |||||||
| images: | images: | ||||||
|   owsec: |   owsec: | ||||||
|     repository: tip-tip-wlan-cloud-ucentral.jfrog.io/owsec |     repository: tip-tip-wlan-cloud-ucentral.jfrog.io/owsec | ||||||
|     tag: v2.6.0-RC1 |     tag: v2.2.0-RC1 | ||||||
|     pullPolicy: Always |     pullPolicy: Always | ||||||
| #    regcred: | #    regcred: | ||||||
| #      registry: tip-tip-wlan-cloud-ucentral.jfrog.io | #      registry: tip-tip-wlan-cloud-ucentral.jfrog.io | ||||||
| #      username: username | #      username: username | ||||||
| #      password: password | #      password: password | ||||||
|   dockerize: |  | ||||||
|     repository: tip-tip-wlan-cloud-ucentral.jfrog.io/dockerize |  | ||||||
|     tag: 0.16.0 |  | ||||||
|     pullPolicy: IfNotPresent |  | ||||||
|  |  | ||||||
| services: | services: | ||||||
|   owsec: |   owsec: | ||||||
|     type: ClusterIP |     type: LoadBalancer | ||||||
|     ports: |     ports: | ||||||
|       restapi: |       restapi: | ||||||
|         servicePort: 16001 |         servicePort: 16001 | ||||||
| @@ -40,9 +35,9 @@ checks: | |||||||
|         path: / |         path: / | ||||||
|         port: 16101 |         port: 16101 | ||||||
|     readiness: |     readiness: | ||||||
|       exec: |       httpGet: | ||||||
|         command: |         path: / | ||||||
|           - /readiness_check |         port: 16101 | ||||||
|  |  | ||||||
| ingresses: | ingresses: | ||||||
|   restapi: |   restapi: | ||||||
| @@ -54,7 +49,6 @@ ingresses: | |||||||
|     - restapi.chart-example.local |     - restapi.chart-example.local | ||||||
|     paths: |     paths: | ||||||
|     - path: / |     - path: / | ||||||
|       pathType: ImplementationSpecific |  | ||||||
|       serviceName: owsec |       serviceName: owsec | ||||||
|       servicePort: restapi |       servicePort: restapi | ||||||
|  |  | ||||||
| @@ -100,8 +94,6 @@ tolerations: [] | |||||||
|  |  | ||||||
| affinity: {} | affinity: {} | ||||||
|  |  | ||||||
| podAnnotations: {} |  | ||||||
|  |  | ||||||
| persistence: | persistence: | ||||||
|   enabled: true |   enabled: true | ||||||
|   # storageClassName: "-" |   # storageClassName: "-" | ||||||
| @@ -114,14 +106,8 @@ persistence: | |||||||
| public_env_variables: | public_env_variables: | ||||||
|   OWSEC_ROOT: /owsec-data |   OWSEC_ROOT: /owsec-data | ||||||
|   OWSEC_CONFIG: /owsec-data |   OWSEC_CONFIG: /owsec-data | ||||||
|   # Environment variables required for the readiness checks using script |  | ||||||
|   FLAGS: "-s --connect-timeout 3" |  | ||||||
|   # NOTE in order for readiness check to use system info you need to set READINESS_METHOD to "systeminfo" and set OWSEC to the OWSEC's REST API endpoint |  | ||||||
|   #READINESS_METHOD: systeminfo |  | ||||||
|  |  | ||||||
| secret_env_variables: | secret_env_variables: {} | ||||||
|   OWSEC_USERNAME: tip@ucentral.com |  | ||||||
|   OWSEC_PASSWORD: openwifi |  | ||||||
|  |  | ||||||
| configProperties: | configProperties: | ||||||
|   # -> Public part |   # -> Public part | ||||||
| @@ -133,7 +119,7 @@ configProperties: | |||||||
|   openwifi.restapi.host.0.port: 16001 |   openwifi.restapi.host.0.port: 16001 | ||||||
|   openwifi.restapi.host.0.cert: $OWSEC_ROOT/certs/restapi-cert.pem |   openwifi.restapi.host.0.cert: $OWSEC_ROOT/certs/restapi-cert.pem | ||||||
|   openwifi.restapi.host.0.key: $OWSEC_ROOT/certs/restapi-key.pem |   openwifi.restapi.host.0.key: $OWSEC_ROOT/certs/restapi-key.pem | ||||||
|   openwifi.restapi.wwwassets: $OWSEC_ROOT/persist/wwwassets |   openwifi.restapi.wwwassets: $OWSEC_ROOT/wwwassets | ||||||
|   openwifi.internal.restapi.host.0.backlog: 100 |   openwifi.internal.restapi.host.0.backlog: 100 | ||||||
|   openwifi.internal.restapi.host.0.security: relaxed |   openwifi.internal.restapi.host.0.security: relaxed | ||||||
|   openwifi.internal.restapi.host.0.rootca: $OWSEC_ROOT/certs/restapi-ca.pem |   openwifi.internal.restapi.host.0.rootca: $OWSEC_ROOT/certs/restapi-ca.pem | ||||||
| @@ -146,17 +132,11 @@ configProperties: | |||||||
|   authentication.default.access: master |   authentication.default.access: master | ||||||
|   authentication.service.type: internal |   authentication.service.type: internal | ||||||
|   # Mailer |   # Mailer | ||||||
|   mailer.enabled: "false" |  | ||||||
|   mailer.hostname: smtp.gmail.com |   mailer.hostname: smtp.gmail.com | ||||||
|   mailer.sender: OpenWIFI |   mailer.sender: OpenWIFI | ||||||
|   mailer.loginmethod: login |   mailer.loginmethod: login | ||||||
|   mailer.port: 587 |   mailer.port: 587 | ||||||
|   mailer.templates: $OWSEC_ROOT/persist/templates |   mailer.templates: $OWSEC_ROOT/templates | ||||||
|   # SMS |  | ||||||
|   smssender.enabled: "false" |  | ||||||
|   smssender.provider: "aws" |  | ||||||
|   #smssender.aws.region: "" |  | ||||||
|   #smssender.twilio.phonenumber: "" |  | ||||||
|   # ALB |   # ALB | ||||||
|   alb.enable: "true" |   alb.enable: "true" | ||||||
|   alb.port: 16101 |   alb.port: 16101 | ||||||
| @@ -167,10 +147,6 @@ configProperties: | |||||||
|   openwifi.kafka.brokerlist: localhost:9092 |   openwifi.kafka.brokerlist: localhost:9092 | ||||||
|   openwifi.kafka.auto.commit: false |   openwifi.kafka.auto.commit: false | ||||||
|   openwifi.kafka.queue.buffering.max.ms: 50 |   openwifi.kafka.queue.buffering.max.ms: 50 | ||||||
|   openwifi.kafka.ssl.ca.location: "" |  | ||||||
|   openwifi.kafka.ssl.certificate.location: "" |  | ||||||
|   openwifi.kafka.ssl.key.location: "" |  | ||||||
|   openwifi.kafka.ssl.key.password: "" |  | ||||||
|   # Storage |   # Storage | ||||||
|   storage.type: sqlite # (sqlite|postgresql|mysql|odbc) |   storage.type: sqlite # (sqlite|postgresql|mysql|odbc) | ||||||
|   ## SQLite |   ## SQLite | ||||||
| @@ -200,9 +176,22 @@ configProperties: | |||||||
|   openwifi.system.uri.ui: https://localhost |   openwifi.system.uri.ui: https://localhost | ||||||
|   openwifi.system.commandchannel: /tmp/app_owsec |   openwifi.system.commandchannel: /tmp/app_owsec | ||||||
|   # Logging |   # Logging | ||||||
|   logging.type: console |   logging.formatters.f1.class: PatternFormatter | ||||||
|   logging.path: $OWSEC_ROOT/logs |   logging.formatters.f1.pattern: "%Y-%m-%d %H:%M:%S %s: [%p] %t" | ||||||
|   logging.level: debug |   logging.formatters.f1.times: UTC | ||||||
|  |   logging.channels.c1.class: ConsoleChannel | ||||||
|  |   logging.channels.c1.formatter: f1 | ||||||
|  |   logging.channels.c2.class: FileChannel | ||||||
|  |   logging.channels.c2.path: /tmp/log_owsec | ||||||
|  |   logging.channels.c2.formatter.class: PatternFormatter | ||||||
|  |   logging.channels.c2.formatter.pattern: "%Y-%m-%d %H:%M:%S %s: [%p] %t" | ||||||
|  |   logging.channels.c2.rotation: "20 M" | ||||||
|  |   logging.channels.c2.archive: timestamp | ||||||
|  |   logging.channels.c2.purgeCount: 20 | ||||||
|  |   logging.channels.c3.class: ConsoleChannel | ||||||
|  |   logging.channels.c3.pattern: "%s: [%p] %t" | ||||||
|  |   logging.loggers.root.channel: c1 | ||||||
|  |   logging.loggers.root.level: debug | ||||||
|  |  | ||||||
|   # -> Secret part |   # -> Secret part | ||||||
|   # REST API |   # REST API | ||||||
| @@ -214,12 +203,6 @@ configProperties: | |||||||
|   # Mailer |   # Mailer | ||||||
|   mailer.username: no-reply@arilia.com |   mailer.username: no-reply@arilia.com | ||||||
|   mailer.password: "**************************" |   mailer.password: "**************************" | ||||||
|   # SMS |  | ||||||
|   #smssender.aws.secretkey: "" |  | ||||||
|   #smssender.aws.accesskey: "" |  | ||||||
|   #smssender.twilio.sid: "" |  | ||||||
|   #smssender.twilio.token: "" |  | ||||||
|   # |  | ||||||
|   # Storage |   # Storage | ||||||
|   ## PostgreSQL |   ## PostgreSQL | ||||||
|   storage.type.postgresql.username: stephb |   storage.type.postgresql.username: stephb | ||||||
|   | |||||||
| @@ -1,8 +1,8 @@ | |||||||
| openapi: 3.0.1 | openapi: 3.0.1 | ||||||
| info: | info: | ||||||
|   title: uCentral Security API |   title: uCentral Security API | ||||||
|   description: A process to manage security logins. |   description: A process to manage security logins | ||||||
|   version: 2.5.0 |   version: 2.0.0 | ||||||
|   license: |   license: | ||||||
|     name: BSD3 |     name: BSD3 | ||||||
|     url: https://github.com/Telecominfraproject/wlan-cloud-ucentralgw/blob/master/LICENSE |     url: https://github.com/Telecominfraproject/wlan-cloud-ucentralgw/blob/master/LICENSE | ||||||
| @@ -51,22 +51,6 @@ components: | |||||||
|             properties: |             properties: | ||||||
|               ErrorCode: |               ErrorCode: | ||||||
|                 type: integer |                 type: integer | ||||||
|                 enum: |  | ||||||
|                   - 0     # Success |  | ||||||
|                   - 1     # PASSWORD_CHANGE_REQUIRED, |  | ||||||
|                   - 2     # INVALID_CREDENTIALS, |  | ||||||
|                   - 3     # PASSWORD_ALREADY_USED, |  | ||||||
|                   - 4     # USERNAME_PENDING_VERIFICATION, |  | ||||||
|                   - 5     # PASSWORD_INVALID, |  | ||||||
|                   - 6     # INTERNAL_ERROR, |  | ||||||
|                   - 7     # ACCESS_DENIED, |  | ||||||
|                   - 8     # INVALID_TOKEN |  | ||||||
|                   - 9     # EXPIRED_TOKEN |  | ||||||
|                   - 10    # RATE_LIMIT_EXCEEDED |  | ||||||
|                   - 11    # BAD_MFA_TRANSACTION |  | ||||||
|                   - 12    # MFA_FAILURE |  | ||||||
|                   - 13    # SECURITY_SERVICE_UNREACHABLE |  | ||||||
|                   - 14    # CANNOT REFRESH TOKEN |  | ||||||
|               ErrorDetails: |               ErrorDetails: | ||||||
|                 type: string |                 type: string | ||||||
|               ErrorDescription: |               ErrorDescription: | ||||||
| @@ -85,20 +69,8 @@ components: | |||||||
|               Code: |               Code: | ||||||
|                 type: integer |                 type: integer | ||||||
| 
 | 
 | ||||||
|     BadRequest: |  | ||||||
|       description: The requested operation failed. |  | ||||||
|       content: |  | ||||||
|         application/json: |  | ||||||
|           schema: |  | ||||||
|             properties: |  | ||||||
|               ErrorCode: |  | ||||||
|                 type: integer |  | ||||||
|               ErrorDetails: |  | ||||||
|                 type: string |  | ||||||
|               ErrorDescription: |  | ||||||
|                 type: integer |  | ||||||
| 
 |  | ||||||
|   schemas: |   schemas: | ||||||
|  | 
 | ||||||
|     WebTokenRequest: |     WebTokenRequest: | ||||||
|       description: User Id and password. |       description: User Id and password. | ||||||
|       type: object |       type: object | ||||||
| @@ -121,15 +93,6 @@ components: | |||||||
|         userId: support@example.com |         userId: support@example.com | ||||||
|         password: support |         password: support | ||||||
| 
 | 
 | ||||||
|     WebTokenRefreshRequest: |  | ||||||
|       type: object |  | ||||||
|       properties: |  | ||||||
|         userId: |  | ||||||
|           type: string |  | ||||||
|           default: support@example.com |  | ||||||
|         refresh_token: |  | ||||||
|           type: string |  | ||||||
| 
 |  | ||||||
|     WebTokenResult: |     WebTokenResult: | ||||||
|       description: Login and Refresh Tokens to be used in subsequent API calls. |       description: Login and Refresh Tokens to be used in subsequent API calls. | ||||||
|       type: object |       type: object | ||||||
| @@ -233,40 +196,6 @@ components: | |||||||
|           items: |           items: | ||||||
|             $ref: '#/components/schemas/SystemEndpoint' |             $ref: '#/components/schemas/SystemEndpoint' | ||||||
| 
 | 
 | ||||||
|     MobilePhoneNumber: |  | ||||||
|       type: object |  | ||||||
|       properties: |  | ||||||
|         number: |  | ||||||
|           type: string |  | ||||||
|         verified: |  | ||||||
|           type: boolean |  | ||||||
|         primary: |  | ||||||
|           type: boolean |  | ||||||
| 
 |  | ||||||
|     MfaAuthInfo: |  | ||||||
|       type: object |  | ||||||
|       properties: |  | ||||||
|         enabled: |  | ||||||
|           type: boolean |  | ||||||
|         method: |  | ||||||
|           type: string |  | ||||||
|           enum: |  | ||||||
|             - sms |  | ||||||
|             - email |  | ||||||
|             - authenticator |  | ||||||
| 
 |  | ||||||
|     UserLoginLoginExtensions: |  | ||||||
|       type: object |  | ||||||
|       properties: |  | ||||||
|         mobiles: |  | ||||||
|           type: array |  | ||||||
|           items: |  | ||||||
|             $ref: '#/components/schemas/MobilePhoneNumber' |  | ||||||
|         authenticatorSecret: |  | ||||||
|           type: string |  | ||||||
|         mfa: |  | ||||||
|           $ref: '#/components/schemas/MfaAuthInfo' |  | ||||||
| 
 |  | ||||||
|     UserInfo: |     UserInfo: | ||||||
|       type: object |       type: object | ||||||
|       properties: |       properties: | ||||||
| @@ -338,12 +267,10 @@ components: | |||||||
|           enum: |           enum: | ||||||
|             - root |             - root | ||||||
|             - admin |             - admin | ||||||
|             - subscriber |             - sub | ||||||
|             - csr |             - csr | ||||||
|             - system |             - system | ||||||
|             - installer |             - special | ||||||
|             - noc |  | ||||||
|             - accounting |  | ||||||
|         oauthType: |         oauthType: | ||||||
|           type: string |           type: string | ||||||
|           enum: |           enum: | ||||||
| @@ -360,14 +287,8 @@ components: | |||||||
|         securityPolicyChange: |         securityPolicyChange: | ||||||
|           type: integer |           type: integer | ||||||
|           format: int64 |           format: int64 | ||||||
|         modified: |  | ||||||
|           type: integer |  | ||||||
|           format: int64 |  | ||||||
|         userTypeProprietaryInfo: |         userTypeProprietaryInfo: | ||||||
|           $ref: '#/components/schemas/UserLoginLoginExtensions' |  | ||||||
|         signupUUID: |  | ||||||
|           type: string |           type: string | ||||||
|           format: uuid |  | ||||||
| 
 | 
 | ||||||
|     UserList: |     UserList: | ||||||
|       type: object |       type: object | ||||||
| @@ -393,57 +314,6 @@ components: | |||||||
|         text: |         text: | ||||||
|           type: string |           type: string | ||||||
| 
 | 
 | ||||||
|     SMSInfo: |  | ||||||
|       type: object |  | ||||||
|       properties: |  | ||||||
|         from: |  | ||||||
|           type: string |  | ||||||
|         to: |  | ||||||
|           type: string |  | ||||||
|         text: |  | ||||||
|           type: string |  | ||||||
| 
 |  | ||||||
|     MFAChallengeRequest: |  | ||||||
|       type: object |  | ||||||
|       properties: |  | ||||||
|         uuid: |  | ||||||
|           type: string |  | ||||||
|           format: uuid |  | ||||||
|         question: |  | ||||||
|           type: string |  | ||||||
|         created: |  | ||||||
|           type: integer |  | ||||||
|           format: integer64 |  | ||||||
|         method: |  | ||||||
|           type: string |  | ||||||
| 
 |  | ||||||
|     MFAChallengeResponse: |  | ||||||
|       type: object |  | ||||||
|       properties: |  | ||||||
|         uuid: |  | ||||||
|           type: string |  | ||||||
|           format: uuid |  | ||||||
|         answer: |  | ||||||
|           type: string |  | ||||||
| 
 |  | ||||||
|     SubMfaConfig: |  | ||||||
|       type: object |  | ||||||
|       properties: |  | ||||||
|         id: |  | ||||||
|           type: string |  | ||||||
|           format: uuid |  | ||||||
|         type: |  | ||||||
|           type: string |  | ||||||
|           enum: |  | ||||||
|             - disabled |  | ||||||
|             - sms |  | ||||||
|             - email |  | ||||||
|         email: |  | ||||||
|           type: string |  | ||||||
|           format: email |  | ||||||
|         sms: |  | ||||||
|           type: string |  | ||||||
| 
 |  | ||||||
|     ######################################################################################### |     ######################################################################################### | ||||||
|     ## |     ## | ||||||
|     ## These are endpoints that all services in the uCentral stack must provide |     ## These are endpoints that all services in the uCentral stack must provide | ||||||
| @@ -688,22 +558,6 @@ components: | |||||||
|           items: |           items: | ||||||
|             $ref: '#/components/schemas/TagValuePair' |             $ref: '#/components/schemas/TagValuePair' | ||||||
| 
 | 
 | ||||||
|     Preferences: |  | ||||||
|       type: object |  | ||||||
|       properties: |  | ||||||
|         modified: |  | ||||||
|           type: integer |  | ||||||
|           format: int64 |  | ||||||
|         data: |  | ||||||
|           type: array |  | ||||||
|           items: |  | ||||||
|             type: object |  | ||||||
|             properties: |  | ||||||
|               tag: |  | ||||||
|                 type: string |  | ||||||
|               value: |  | ||||||
|                 type: string |  | ||||||
| 
 |  | ||||||
|     ######################################################################################### |     ######################################################################################### | ||||||
|     ## |     ## | ||||||
|     ## End of uCentral system wide values |     ## End of uCentral system wide values | ||||||
| @@ -736,106 +590,20 @@ paths: | |||||||
|           schema: |           schema: | ||||||
|             type: boolean |             type: boolean | ||||||
|           required: false |           required: false | ||||||
|         - in: query |  | ||||||
|           name: resendMFACode |  | ||||||
|           schema: |  | ||||||
|             type: boolean |  | ||||||
|           required: false |  | ||||||
|         - in: query |  | ||||||
|           name: completeMFAChallenge |  | ||||||
|           schema: |  | ||||||
|             type: boolean |  | ||||||
|           required: false |  | ||||||
|         - in: query |  | ||||||
|           name: grant_type |  | ||||||
|           schema: |  | ||||||
|             type: string |  | ||||||
|             example: refresh_token |  | ||||||
|           required: false |  | ||||||
|       requestBody: |       requestBody: | ||||||
|         description: User id and password |         description: User id and password | ||||||
|         required: true |         required: true | ||||||
|         content: |         content: | ||||||
|           application/json: |           application/json: | ||||||
|             schema: |             schema: | ||||||
|               oneOf: |               $ref: '#/components/schemas/WebTokenRequest' | ||||||
|                 - $ref: '#/components/schemas/WebTokenRequest' |  | ||||||
|                 - $ref: '#/components/schemas/MFAChallengeResponse' |  | ||||||
|                 - $ref: '#/components/schemas/WebTokenRefreshRequest' |  | ||||||
|       responses: |       responses: | ||||||
|         200: |         200: | ||||||
|           description: successful operation |           description: successful operation | ||||||
|           content: |           content: | ||||||
|             application/json: |             application/json: | ||||||
|               schema: |               schema: | ||||||
|                 oneOf: |                 $ref: '#/components/schemas/WebTokenResult' | ||||||
|                   - $ref: '#/components/schemas/WebTokenResult' |  | ||||||
|                   - $ref: '#/components/schemas/MFAChallengeRequest' |  | ||||||
|         403: |  | ||||||
|           $ref: '#/components/responses/Unauthorized' |  | ||||||
|         404: |  | ||||||
|           $ref: '#/components/responses/NotFound' |  | ||||||
| 
 |  | ||||||
|   /suboauth2: |  | ||||||
|     post: |  | ||||||
|       tags: |  | ||||||
|         - Authentication |  | ||||||
|       summary: Get access token - to be used as Bearer token header for all other API requests. |  | ||||||
|       operationId: getSubAccessToken |  | ||||||
|       parameters: |  | ||||||
|         - in: query |  | ||||||
|           name: newPassword |  | ||||||
|           description: used when a user is trying to change her password. This will be the new password. |  | ||||||
|           schema: |  | ||||||
|             type: string |  | ||||||
|           required: false |  | ||||||
|         - in: query |  | ||||||
|           name: forgotPassword |  | ||||||
|           description: A user forgot her password. She needs to present her e-mail address in the userId and set this to true |  | ||||||
|           schema: |  | ||||||
|             type: boolean |  | ||||||
|           required: false |  | ||||||
|         - in: query |  | ||||||
|           name: requirements |  | ||||||
|           description: A user forgot her password. She needs to present her e-mail address in the userId and set this to true |  | ||||||
|           schema: |  | ||||||
|             type: boolean |  | ||||||
|           required: false |  | ||||||
|         - in: query |  | ||||||
|           name: resendMFACode |  | ||||||
|           schema: |  | ||||||
|             type: boolean |  | ||||||
|           required: false |  | ||||||
|         - in: query |  | ||||||
|           name: completeMFAChallenge |  | ||||||
|           schema: |  | ||||||
|             type: boolean |  | ||||||
|           required: false |  | ||||||
|         - in: query |  | ||||||
|           name: grant_type |  | ||||||
|           schema: |  | ||||||
|             type: string |  | ||||||
|             example: refresh_token |  | ||||||
|           required: false |  | ||||||
|       requestBody: |  | ||||||
|         description: User id and password |  | ||||||
|         required: true |  | ||||||
|         content: |  | ||||||
|           application/json: |  | ||||||
|             schema: |  | ||||||
|               oneOf: |  | ||||||
|                 - $ref: '#/components/schemas/WebTokenRequest' |  | ||||||
|                 - $ref: '#/components/schemas/MFAChallengeResponse' |  | ||||||
|                 - $ref: '#/components/schemas/WebTokenRefreshRequest' |  | ||||||
|       responses: |  | ||||||
|         200: |  | ||||||
|           description: successful operation |  | ||||||
|           content: |  | ||||||
|             application/json: |  | ||||||
|               schema: |  | ||||||
|                 oneOf: |  | ||||||
|                   - $ref: '#/components/schemas/WebTokenResult' |  | ||||||
|                   - $ref: '#/components/schemas/MFAChallengeRequest' |  | ||||||
|         403: |         403: | ||||||
|           $ref: '#/components/responses/Unauthorized' |           $ref: '#/components/responses/Unauthorized' | ||||||
|         404: |         404: | ||||||
| @@ -866,36 +634,11 @@ paths: | |||||||
|         404: |         404: | ||||||
|           $ref: '#/components/responses/NotFound' |           $ref: '#/components/responses/NotFound' | ||||||
| 
 | 
 | ||||||
|   /suboauth2/{token}: |  | ||||||
|     delete: |  | ||||||
|       tags: |  | ||||||
|         - Authentication |  | ||||||
|       summary: Revoke a token. |  | ||||||
|       operationId: removeSubAccessToken |  | ||||||
|       parameters: |  | ||||||
|         - in: path |  | ||||||
|           name: token |  | ||||||
|           schema: |  | ||||||
|             type: |  | ||||||
|               string |  | ||||||
|           required: true |  | ||||||
|       responses: |  | ||||||
|         204: |  | ||||||
|           description: successful operation |  | ||||||
|           content: |  | ||||||
|             application/json: |  | ||||||
|               schema: |  | ||||||
|                 $ref: '#/components/responses/Success' |  | ||||||
|         403: |  | ||||||
|           $ref: '#/components/responses/Unauthorized' |  | ||||||
|         404: |  | ||||||
|           $ref: '#/components/responses/NotFound' |  | ||||||
| 
 |  | ||||||
|   /systemEndpoints: |   /systemEndpoints: | ||||||
|     get: |     get: | ||||||
|       tags: |       tags: | ||||||
|         - Authentication |         - Authentication | ||||||
|       summary: Retrieve the system layout. |       summary: retrieve the system layout | ||||||
|       operationId: getSystemInfo |       operationId: getSystemInfo | ||||||
|       responses: |       responses: | ||||||
|         200: |         200: | ||||||
| @@ -947,72 +690,6 @@ paths: | |||||||
|             type: string |             type: string | ||||||
|             example: id1,id2,id3,id4,id5 |             example: id1,id2,id3,id4,id5 | ||||||
|           required: false |           required: false | ||||||
|         - in: query |  | ||||||
|           description: Name matching |  | ||||||
|           name: nameSearch |  | ||||||
|           schema: |  | ||||||
|             type: string |  | ||||||
|         - in: query |  | ||||||
|           description: Name matching |  | ||||||
|           name: emailSearch |  | ||||||
|           schema: |  | ||||||
|             type: string |  | ||||||
|       responses: |  | ||||||
|         200: |  | ||||||
|           $ref: '#/components/schemas/UserList' |  | ||||||
|         403: |  | ||||||
|           $ref: '#/components/responses/Unauthorized' |  | ||||||
|         404: |  | ||||||
|           $ref: '#/components/responses/NotFound' |  | ||||||
| 
 |  | ||||||
|   /subusers: |  | ||||||
|     get: |  | ||||||
|       tags: |  | ||||||
|         - Subscribers |  | ||||||
|       summary: Retrieve a list of existing users as well as some information about them. |  | ||||||
|       operationId: getSubUsers |  | ||||||
|       parameters: |  | ||||||
|         - in: query |  | ||||||
|           name: offset |  | ||||||
|           schema: |  | ||||||
|             type: integer |  | ||||||
|             format: int64 |  | ||||||
|           required: false |  | ||||||
|         - in: query |  | ||||||
|           name: limit |  | ||||||
|           schema: |  | ||||||
|             type: integer |  | ||||||
|             format: int64 |  | ||||||
|           required: false |  | ||||||
|         - in: query |  | ||||||
|           description: Selecting this option means the newest record will be returned. Use limit to select how many. |  | ||||||
|           name: filter |  | ||||||
|           schema: |  | ||||||
|             type: string |  | ||||||
|           required: false |  | ||||||
|         - in: query |  | ||||||
|           description: Return only the ids. |  | ||||||
|           name: idOnly |  | ||||||
|           schema: |  | ||||||
|             type: boolean |  | ||||||
|           required: false |  | ||||||
|         - in: query |  | ||||||
|           description: Return only the ids. |  | ||||||
|           name: select |  | ||||||
|           schema: |  | ||||||
|             type: string |  | ||||||
|             example: id1,id2,id3,id4,id5 |  | ||||||
|           required: false |  | ||||||
|         - in: query |  | ||||||
|           description: Name matching |  | ||||||
|           name: nameSearch |  | ||||||
|           schema: |  | ||||||
|             type: string |  | ||||||
|         - in: query |  | ||||||
|           description: Name matching |  | ||||||
|           name: emailSearch |  | ||||||
|           schema: |  | ||||||
|             type: string |  | ||||||
|       responses: |       responses: | ||||||
|         200: |         200: | ||||||
|           $ref: '#/components/schemas/UserList' |           $ref: '#/components/schemas/UserList' | ||||||
| @@ -1026,7 +703,7 @@ paths: | |||||||
|       tags: |       tags: | ||||||
|         - User Management |         - User Management | ||||||
|       operationId: getUser |       operationId: getUser | ||||||
|       summary: Retrieve the information for a single user. |       summary: Retrieve the information for a single user | ||||||
|       parameters: |       parameters: | ||||||
|         - in: path |         - in: path | ||||||
|           name: id |           name: id | ||||||
| @@ -1046,7 +723,7 @@ paths: | |||||||
|       tags: |       tags: | ||||||
|         - User Management |         - User Management | ||||||
|       operationId: deleteUser |       operationId: deleteUser | ||||||
|       summary: Delete a single user. |       summary: Delete s single user | ||||||
|       parameters: |       parameters: | ||||||
|         - in: path |         - in: path | ||||||
|           name: id |           name: id | ||||||
| @@ -1066,7 +743,7 @@ paths: | |||||||
|       tags: |       tags: | ||||||
|         - User Management |         - User Management | ||||||
|       operationId: createUser |       operationId: createUser | ||||||
|       summary: Create a single user. |       summary: Create a single user | ||||||
|       parameters: |       parameters: | ||||||
|         - in: path |         - in: path | ||||||
|           name: id |           name: id | ||||||
| @@ -1098,7 +775,7 @@ paths: | |||||||
|       tags: |       tags: | ||||||
|         - User Management |         - User Management | ||||||
|       operationId: updateUser |       operationId: updateUser | ||||||
|       summary: Modify a single user. |       summary: Modifying a single user | ||||||
|       parameters: |       parameters: | ||||||
|         - in: path |         - in: path | ||||||
|           name: id |           name: id | ||||||
| @@ -1111,134 +788,6 @@ paths: | |||||||
|           schema: |           schema: | ||||||
|             type: boolean |             type: boolean | ||||||
|           required: false |           required: false | ||||||
|         - in: query |  | ||||||
|           name: forgotPassword |  | ||||||
|           schema: |  | ||||||
|             type: boolean |  | ||||||
|             default: false |  | ||||||
|           required: false |  | ||||||
|         - in: query |  | ||||||
|           name: resetMFA |  | ||||||
|           schema: |  | ||||||
|             type: boolean |  | ||||||
|             default: false |  | ||||||
|           required: false |  | ||||||
|       requestBody: |  | ||||||
|         description: User details (some fields are ignored during update) |  | ||||||
|         content: |  | ||||||
|           application/json: |  | ||||||
|             schema: |  | ||||||
|               $ref: '#/components/schemas/UserInfo' |  | ||||||
|       responses: |  | ||||||
|         200: |  | ||||||
|           $ref: '#/components/schemas/UserInfo' |  | ||||||
|         403: |  | ||||||
|           $ref: '#/components/responses/Unauthorized' |  | ||||||
|         404: |  | ||||||
|           $ref: '#/components/responses/NotFound' |  | ||||||
| 
 |  | ||||||
|   /subuser/{id}: |  | ||||||
|     get: |  | ||||||
|       tags: |  | ||||||
|         - Subscribers |  | ||||||
|       operationId: getSubUser |  | ||||||
|       summary: Retrieve the information for a single user. |  | ||||||
|       parameters: |  | ||||||
|         - in: path |  | ||||||
|           name: id |  | ||||||
|           schema: |  | ||||||
|             type: string |  | ||||||
|             format: uuid |  | ||||||
|           required: true |  | ||||||
|       responses: |  | ||||||
|         200: |  | ||||||
|           $ref: '#/components/schemas/UserInfo' |  | ||||||
|         403: |  | ||||||
|           $ref: '#/components/responses/Unauthorized' |  | ||||||
|         404: |  | ||||||
|           $ref: '#/components/responses/NotFound' |  | ||||||
| 
 |  | ||||||
|     delete: |  | ||||||
|       tags: |  | ||||||
|         - Subscribers |  | ||||||
|       operationId: deleteSubUser |  | ||||||
|       summary: Delete a single user. |  | ||||||
|       parameters: |  | ||||||
|         - in: path |  | ||||||
|           name: id |  | ||||||
|           schema: |  | ||||||
|             type: integer |  | ||||||
|             format: int64 |  | ||||||
|           required: true |  | ||||||
|       responses: |  | ||||||
|         204: |  | ||||||
|           $ref: '#/components/responses/Success' |  | ||||||
|         403: |  | ||||||
|           $ref: '#/components/responses/Unauthorized' |  | ||||||
|         404: |  | ||||||
|           $ref: '#/components/responses/NotFound' |  | ||||||
| 
 |  | ||||||
|     post: |  | ||||||
|       tags: |  | ||||||
|         - Subscribers |  | ||||||
|       operationId: createSubUser |  | ||||||
|       summary: Create a single user. |  | ||||||
|       parameters: |  | ||||||
|         - in: path |  | ||||||
|           name: id |  | ||||||
|           #must be set to 0 for user creation |  | ||||||
|           schema: |  | ||||||
|             type: integer |  | ||||||
|             format: int64 |  | ||||||
|           required: true |  | ||||||
|         - in: query |  | ||||||
|           name: email_verification |  | ||||||
|           schema: |  | ||||||
|             type: boolean |  | ||||||
|           required: false |  | ||||||
|       requestBody: |  | ||||||
|         description: User details (some fields are ignored during creation) |  | ||||||
|         content: |  | ||||||
|           application/json: |  | ||||||
|             schema: |  | ||||||
|               $ref: '#/components/schemas/UserInfo' |  | ||||||
|       responses: |  | ||||||
|         200: |  | ||||||
|           $ref: '#/components/schemas/UserInfo' |  | ||||||
|         403: |  | ||||||
|           $ref: '#/components/responses/Unauthorized' |  | ||||||
|         404: |  | ||||||
|           $ref: '#/components/responses/NotFound' |  | ||||||
| 
 |  | ||||||
|     put: |  | ||||||
|       tags: |  | ||||||
|         - Subscribers |  | ||||||
|       operationId: updateSubUser |  | ||||||
|       summary: Modify a single user. |  | ||||||
|       parameters: |  | ||||||
|         - in: path |  | ||||||
|           name: id |  | ||||||
|           schema: |  | ||||||
|             type: integer |  | ||||||
|             format: int64 |  | ||||||
|           required: true |  | ||||||
|         - in: query |  | ||||||
|           name: email_verification |  | ||||||
|           schema: |  | ||||||
|             type: boolean |  | ||||||
|           required: false |  | ||||||
|         - in: query |  | ||||||
|           name: forgotPassword |  | ||||||
|           schema: |  | ||||||
|             type: boolean |  | ||||||
|             default: false |  | ||||||
|           required: false |  | ||||||
|         - in: query |  | ||||||
|           name: resetMFA |  | ||||||
|           schema: |  | ||||||
|             type: boolean |  | ||||||
|             default: false |  | ||||||
|           required: false |  | ||||||
|       requestBody: |       requestBody: | ||||||
|         description: User details (some fields are ignored during update) |         description: User details (some fields are ignored during update) | ||||||
|         content: |         content: | ||||||
| @@ -1258,7 +807,7 @@ paths: | |||||||
|       tags: |       tags: | ||||||
|         - Avatar |         - Avatar | ||||||
|       operationId: getAvatar |       operationId: getAvatar | ||||||
|       summary: Retrieve the avatar associated with a user ID. |       summary: Retrieve teh avatar associated with a user ID | ||||||
|       parameters: |       parameters: | ||||||
|         - in: path |         - in: path | ||||||
|           name: id |           name: id | ||||||
| @@ -1292,7 +841,7 @@ paths: | |||||||
|       tags: |       tags: | ||||||
|         - Avatar |         - Avatar | ||||||
|       operationId: deleteAvatar |       operationId: deleteAvatar | ||||||
|       summary: Remove an avatar associated with a user ID. |       summary: Remove an Avatar associated with a user ID | ||||||
|       parameters: |       parameters: | ||||||
|         - in: path |         - in: path | ||||||
|           name: id |           name: id | ||||||
| @@ -1312,7 +861,7 @@ paths: | |||||||
|       tags: |       tags: | ||||||
|         - Avatar |         - Avatar | ||||||
|       operationId: createAvatar |       operationId: createAvatar | ||||||
|       summary: Create an avatar associated with a user ID. |       summary: Create an Avatar associated with a user ID | ||||||
|       parameters: |       parameters: | ||||||
|         - in: path |         - in: path | ||||||
|           name: id |           name: id | ||||||
| @@ -1348,8 +897,8 @@ paths: | |||||||
|   /email: |   /email: | ||||||
|     post: |     post: | ||||||
|       tags: |       tags: | ||||||
|         - Email |         - EMail | ||||||
|       summary: Send test email with the system. |       summary: Send test email with the system | ||||||
|       operationId: Send a test email |       operationId: Send a test email | ||||||
|       requestBody: |       requestBody: | ||||||
|         description: The requested message |         description: The requested message | ||||||
| @@ -1376,266 +925,6 @@ paths: | |||||||
|                     items: |                     items: | ||||||
|                       type: string |                       type: string | ||||||
| 
 | 
 | ||||||
|   /sms: |  | ||||||
|     post: |  | ||||||
|       tags: |  | ||||||
|         - Email |  | ||||||
|       summary: Send test email with the system. |  | ||||||
|       operationId: Send a test SMS |  | ||||||
|       parameters: |  | ||||||
|         - in: query |  | ||||||
|           name: validateNumber |  | ||||||
|           schema: |  | ||||||
|             type: boolean |  | ||||||
|           required: false |  | ||||||
|         - in: query |  | ||||||
|           name: completeValidation |  | ||||||
|           schema: |  | ||||||
|             type: boolean |  | ||||||
|           required: false |  | ||||||
|         - in: query |  | ||||||
|           name: validationCode |  | ||||||
|           schema: |  | ||||||
|             type: string |  | ||||||
|           required: false |  | ||||||
|       requestBody: |  | ||||||
|         description: The requested message |  | ||||||
|         content: |  | ||||||
|           application/json: |  | ||||||
|             schema: |  | ||||||
|               $ref: '#/components/schemas/SMSInfo' |  | ||||||
|       responses: |  | ||||||
|         200: |  | ||||||
|           $ref: '#/components/responses/Success' |  | ||||||
|         403: |  | ||||||
|           $ref: '#/components/responses/Unauthorized' |  | ||||||
|         404: |  | ||||||
|           $ref: '#/components/responses/NotFound' |  | ||||||
|         500: |  | ||||||
|           description: Error description |  | ||||||
|           content: |  | ||||||
|             application/json: |  | ||||||
|               schema: |  | ||||||
|                 type: object |  | ||||||
|                 properties: |  | ||||||
|                   errors: |  | ||||||
|                     type: array |  | ||||||
|                     items: |  | ||||||
|                       type: string |  | ||||||
| 
 |  | ||||||
|   /userPreferences: |  | ||||||
|     get: |  | ||||||
|       tags: |  | ||||||
|         - Preferences |  | ||||||
|       operationId: getUserPreferences |  | ||||||
|       summary: Get the list of recorded preferences for a user |  | ||||||
|       responses: |  | ||||||
|         200: |  | ||||||
|           $ref: '#/components/schemas/Preferences' |  | ||||||
|         400: |  | ||||||
|           $ref: '#/components/responses/BadRequest' |  | ||||||
|     post: |  | ||||||
|       tags: |  | ||||||
|         - Preferences |  | ||||||
|       operationId: setUserPreferences |  | ||||||
|       summary: Set the list of recorded preferences for a user |  | ||||||
|       requestBody: |  | ||||||
|         description: Setting the list of preferences |  | ||||||
|         content: |  | ||||||
|           application/json: |  | ||||||
|             schema: |  | ||||||
|               $ref: '#/components/schemas/Preferences' |  | ||||||
|       responses: |  | ||||||
|         200: |  | ||||||
|           $ref: '#/components/schemas/Preferences' |  | ||||||
|         400: |  | ||||||
|           $ref: '#/components/responses/BadRequest' |  | ||||||
| 
 |  | ||||||
|   /submfa: |  | ||||||
|     get: |  | ||||||
|       tags: |  | ||||||
|         - MFA |  | ||||||
|       summary: Retrieve the cyrrent setting for MFA |  | ||||||
|       operationId: getMFS |  | ||||||
|       responses: |  | ||||||
|         200: |  | ||||||
|           $ref: '#/components/schemas/SubMfaConfig' |  | ||||||
| 
 |  | ||||||
|     put: |  | ||||||
|       tags: |  | ||||||
|         - MFA |  | ||||||
|       summary: Retrieve the cyrrent setting for MFA |  | ||||||
|       operationId: modifyMFS |  | ||||||
|       parameters: |  | ||||||
|         - in: query |  | ||||||
|           name: startValidation |  | ||||||
|           schema: |  | ||||||
|             type: boolean |  | ||||||
|           required: false |  | ||||||
|         - in: query |  | ||||||
|           name: completeValidation |  | ||||||
|           schema: |  | ||||||
|             type: boolean |  | ||||||
|           required: false |  | ||||||
|         - in: query |  | ||||||
|           name: challengeCode |  | ||||||
|           schema: |  | ||||||
|             type: string |  | ||||||
|           required: false |  | ||||||
|       requestBody: |  | ||||||
|         content: |  | ||||||
|           application/json: |  | ||||||
|             schema: |  | ||||||
|               $ref: '#/components/schemas/SubMfaConfig' |  | ||||||
|       responses: |  | ||||||
|         200: |  | ||||||
|           $ref: '#/components/schemas/SubMfaConfig' |  | ||||||
|         400: |  | ||||||
|           $ref: '#/components/responses/BadRequest' |  | ||||||
| 
 |  | ||||||
|   /totp: |  | ||||||
|     get: |  | ||||||
|       tags: |  | ||||||
|         - Security |  | ||||||
|       summary: Retrieve the Authenticator QR Code |  | ||||||
|       operationId: getTotpQrCode |  | ||||||
|       parameters: |  | ||||||
|         - in: query |  | ||||||
|           name: reset |  | ||||||
|           schema: |  | ||||||
|             type: boolean |  | ||||||
|             default: false |  | ||||||
|           required: false |  | ||||||
|       responses: |  | ||||||
|         200: |  | ||||||
|           description: QRCode |  | ||||||
|           content: |  | ||||||
|             image/svg+xml: |  | ||||||
|               schema: |  | ||||||
|                 type: string |  | ||||||
|                 format: binary |  | ||||||
|         400: |  | ||||||
|           $ref: '#/components/responses/BadRequest' |  | ||||||
|         403: |  | ||||||
|           $ref: '#/components/responses/Unauthorized' |  | ||||||
| 
 |  | ||||||
|     put: |  | ||||||
|       tags: |  | ||||||
|         - Security |  | ||||||
|       summary: Send the first security code to validate your setup |  | ||||||
|       operationId: sendToptTestCode |  | ||||||
|       parameters: |  | ||||||
|         - in: query |  | ||||||
|           name: value |  | ||||||
|           schema: |  | ||||||
|             type: integer |  | ||||||
|             format: int64 |  | ||||||
|           required: true |  | ||||||
|         - in: query |  | ||||||
|           name: index |  | ||||||
|           schema: |  | ||||||
|             type: integer |  | ||||||
|             format: int64 |  | ||||||
|             example: 1,2,3 |  | ||||||
|           required: true |  | ||||||
|       responses: |  | ||||||
|         200: |  | ||||||
|           description: Succesful posting of response. |  | ||||||
|           content: |  | ||||||
|             application/json: |  | ||||||
|               schema: |  | ||||||
|                 type: object |  | ||||||
|                 properties: |  | ||||||
|                   nextIndex: |  | ||||||
|                     type: integer |  | ||||||
|                   moreCodes: |  | ||||||
|                     type: boolean |  | ||||||
|         400: |  | ||||||
|           $ref: '#/components/responses/BadRequest' |  | ||||||
|         403: |  | ||||||
|           $ref: '#/components/responses/Unauthorized' |  | ||||||
| 
 |  | ||||||
|   /signup: |  | ||||||
|     post: |  | ||||||
|       tags: |  | ||||||
|         - Subscriber Registration |  | ||||||
|       summary: This call allows a new subscriber to register themselves and their devices. |  | ||||||
|       operationId: postSignup |  | ||||||
|       parameters: |  | ||||||
|         - in: query |  | ||||||
|           name: email |  | ||||||
|           schema: |  | ||||||
|             type: string |  | ||||||
|             format: email |  | ||||||
|           required: true |  | ||||||
|         - in: query |  | ||||||
|           name: signupUUID |  | ||||||
|           schema: |  | ||||||
|             type: string |  | ||||||
|             format: uuid |  | ||||||
|           required: true |  | ||||||
|       responses: |  | ||||||
|         200: |  | ||||||
|           $ref: '#/components/schemas/UserInfo' |  | ||||||
|         400: |  | ||||||
|           $ref: '#/components/responses/BadRequest' |  | ||||||
|         403: |  | ||||||
|           $ref: '#/components/responses/Unauthorized' |  | ||||||
|         404: |  | ||||||
|           $ref: '#/components/responses/NotFound' |  | ||||||
| 
 |  | ||||||
|     put: |  | ||||||
|       tags: |  | ||||||
|         - Subscriber Registration |  | ||||||
|       summary: modify the signup command in play |  | ||||||
|       operationId: modifySignup |  | ||||||
|       parameters: |  | ||||||
|         - in: query |  | ||||||
|           name: signupUUID |  | ||||||
|           schema: |  | ||||||
|             type: string |  | ||||||
|             format: uuid |  | ||||||
|           required: true |  | ||||||
|         - in: query |  | ||||||
|           name: operation |  | ||||||
|           schema: |  | ||||||
|             type: string |  | ||||||
|             enum: |  | ||||||
|               - cancel |  | ||||||
|               - success |  | ||||||
|               - inprogress |  | ||||||
|               - failed |  | ||||||
|               - poll |  | ||||||
|               - emailVerified |  | ||||||
|           required: true |  | ||||||
|       requestBody: |  | ||||||
|         content: |  | ||||||
|           application/json: |  | ||||||
|             schema: |  | ||||||
|               type: object |  | ||||||
|               properties: |  | ||||||
|                 reason: |  | ||||||
|                   type: string |  | ||||||
|                 time: |  | ||||||
|                   type: integer |  | ||||||
|                   format: int64 |  | ||||||
|                 errorCode: |  | ||||||
|                   type: integer |  | ||||||
|                   format: int32 |  | ||||||
|         required: false |  | ||||||
| 
 |  | ||||||
|       responses: |  | ||||||
|         200: |  | ||||||
|           $ref: '#/components/responses/Success' |  | ||||||
|         400: |  | ||||||
|           $ref: '#/components/responses/BadRequest' |  | ||||||
|         403: |  | ||||||
|           $ref: '#/components/responses/Unauthorized' |  | ||||||
|         404: |  | ||||||
|           $ref: '#/components/responses/NotFound' |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
| 
 |  | ||||||
|   ######################################################################################### |   ######################################################################################### | ||||||
|   ## |   ## | ||||||
|   ## These are endpoints that all services in the uCentral stack must provide |   ## These are endpoints that all services in the uCentral stack must provide | ||||||
| @@ -1645,7 +934,7 @@ paths: | |||||||
|     get: |     get: | ||||||
|       tags: |       tags: | ||||||
|         - Security |         - Security | ||||||
|       summary: Retrieve the list of security profiles for a specific service type. |       summary: Retrieve the list of security profiles for a specific service type | ||||||
|       operationId: getSecurituProfiles |       operationId: getSecurituProfiles | ||||||
|       parameters: |       parameters: | ||||||
|         - in: query |         - in: query | ||||||
| @@ -1711,32 +1000,11 @@ paths: | |||||||
|         404: |         404: | ||||||
|           $ref: '#/components/responses/NotFound' |           $ref: '#/components/responses/NotFound' | ||||||
| 
 | 
 | ||||||
|   /validateSubToken: |  | ||||||
|     get: |  | ||||||
|       tags: |  | ||||||
|         - Security |  | ||||||
|         - Subscribers |  | ||||||
|       summary: Allows any microservice to validate a token and get security policy for a specific user. |  | ||||||
|       operationId: validateSubToken |  | ||||||
|       parameters: |  | ||||||
|         - in: query |  | ||||||
|           name: token |  | ||||||
|           schema: |  | ||||||
|             type: string |  | ||||||
|           required: true |  | ||||||
|       responses: |  | ||||||
|         200: |  | ||||||
|           $ref: '#/components/schemas/TokenValidationResult' |  | ||||||
|         403: |  | ||||||
|           $ref: '#/components/responses/Unauthorized' |  | ||||||
|         404: |  | ||||||
|           $ref: '#/components/responses/NotFound' |  | ||||||
| 
 |  | ||||||
|   /system: |   /system: | ||||||
|     post: |     post: | ||||||
|       tags: |       tags: | ||||||
|         - System Commands |         - System Commands | ||||||
|       summary: Perform some system wide commands. |       summary: Perform some systeme wide commands | ||||||
|       operationId: systemCommand |       operationId: systemCommand | ||||||
|       requestBody: |       requestBody: | ||||||
|         description: Command details |         description: Command details | ||||||
| @@ -1751,7 +1019,7 @@ paths: | |||||||
|                 - $ref: '#/components/schemas/SystemCommandGetSubsystemNames' |                 - $ref: '#/components/schemas/SystemCommandGetSubsystemNames' | ||||||
|       responses: |       responses: | ||||||
|         200: |         200: | ||||||
|           description: Successful command execution |           description: Successfull command execution | ||||||
|           content: |           content: | ||||||
|             application/json: |             application/json: | ||||||
|               schema: |               schema: | ||||||
| @@ -1780,7 +1048,7 @@ paths: | |||||||
| 
 | 
 | ||||||
|       responses: |       responses: | ||||||
|         200: |         200: | ||||||
|           description: Successful command execution |           description: Successfull command execution | ||||||
|           content: |           content: | ||||||
|             application/json: |             application/json: | ||||||
|               schema: |               schema: | ||||||
| @@ -40,21 +40,9 @@ openwifi.system.commandchannel = /tmp/app.ucentralsec | |||||||
| openwifi.service.key = $OWSEC_ROOT/certs/restapi-key.pem | openwifi.service.key = $OWSEC_ROOT/certs/restapi-key.pem | ||||||
| openwifi.service.key.password = mypassword | openwifi.service.key.password = mypassword | ||||||
|  |  | ||||||
| smssender.enabled = false |  | ||||||
| smssender.provider = aws |  | ||||||
| smssender.aws.secretkey = *************************************** |  | ||||||
| smssender.aws.accesskey = *************************************** |  | ||||||
| smssender.aws.region = ************** |  | ||||||
|  |  | ||||||
| #smssender.provider = twilio |  | ||||||
| #smssender.twilio.sid = *********************** |  | ||||||
| #smssender.twilio.token = ********************** |  | ||||||
| #smssender.twilio.phonenumber = +18888888888 |  | ||||||
|  |  | ||||||
| # | # | ||||||
| # Security Microservice Specific Section | # Security Microservice Specific Section | ||||||
| # | # | ||||||
| mailer.enabled = false |  | ||||||
| mailer.hostname = smtp.gmail.com | mailer.hostname = smtp.gmail.com | ||||||
| mailer.username = ************************ | mailer.username = ************************ | ||||||
| mailer.password = ************************ | mailer.password = ************************ | ||||||
| @@ -82,16 +70,10 @@ openwifi.kafka.enable = true | |||||||
| openwifi.kafka.brokerlist = a1.arilia.com:9092 | openwifi.kafka.brokerlist = a1.arilia.com:9092 | ||||||
| openwifi.kafka.auto.commit = false | openwifi.kafka.auto.commit = false | ||||||
| openwifi.kafka.queue.buffering.max.ms = 50 | openwifi.kafka.queue.buffering.max.ms = 50 | ||||||
| openwifi.kafka.ssl.ca.location = |  | ||||||
| openwifi.kafka.ssl.certificate.location = |  | ||||||
| openwifi.kafka.ssl.key.location = |  | ||||||
| openwifi.kafka.ssl.key.password = |  | ||||||
|  |  | ||||||
| openwifi.document.policy.access = /wwwassets/access_policy.html | openwifi.document.policy.access = /wwwassets/access_policy.html | ||||||
| openwifi.document.policy.password = /wwwassets/password_policy.html | openwifi.document.policy.password = /wwwassets/password_policy.html | ||||||
| openwifi.avatar.maxsize = 2000000 | openwifi.avatar.maxsize = 2000000 | ||||||
|  |  | ||||||
| totp.issuer = OpenWiFi |  | ||||||
| # | # | ||||||
| # This section select which form of persistence you need | # This section select which form of persistence you need | ||||||
| # Only one selected at a time. If you select multiple, this service will die if a horrible | # Only one selected at a time. If you select multiple, this service will die if a horrible | ||||||
| @@ -124,12 +106,44 @@ storage.type.mysql.database = ucentral | |||||||
| storage.type.mysql.port = 3306 | storage.type.mysql.port = 3306 | ||||||
| storage.type.mysql.connectiontimeout = 60 | storage.type.mysql.connectiontimeout = 60 | ||||||
|  |  | ||||||
|  |  | ||||||
| ######################################################################## | ######################################################################## | ||||||
| ######################################################################## | ######################################################################## | ||||||
| # | # | ||||||
| # Logging: please leave as is for now. | # Logging: please leave as is for now. | ||||||
| # | # | ||||||
| ######################################################################## | ######################################################################## | ||||||
| logging.type = file | logging.formatters.f1.class = PatternFormatter | ||||||
| logging.path = $OWSEC_ROOT/logs | logging.formatters.f1.pattern = %s: [%p] %t | ||||||
| logging.level = debug | logging.formatters.f1.times = UTC | ||||||
|  | logging.channels.c1.class = ConsoleChannel | ||||||
|  | logging.channels.c1.formatter = f1 | ||||||
|  |  | ||||||
|  | # This is where the logs will be written. This path MUST exist | ||||||
|  | logging.channels.c2.class = FileChannel | ||||||
|  | logging.channels.c2.path = $OWSEC_ROOT/logs/log | ||||||
|  | logging.channels.c2.formatter.class = PatternFormatter | ||||||
|  | logging.channels.c2.formatter.pattern = %Y-%m-%d %H:%M:%S %s: [%p] %t | ||||||
|  | logging.channels.c2.rotation = 20 M | ||||||
|  | logging.channels.c2.archive = timestamp | ||||||
|  | logging.channels.c2.purgeCount = 20 | ||||||
|  | logging.channels.c3.class = ConsoleChannel | ||||||
|  | logging.channels.c3.pattern = %s: [%p] %t | ||||||
|  |  | ||||||
|  | # External Channel | ||||||
|  | logging.loggers.root.channel = c2 | ||||||
|  | logging.loggers.root.level = debug | ||||||
|  |  | ||||||
|  | # Inline Channel with PatternFormatter | ||||||
|  | # logging.loggers.l1.name = logger1 | ||||||
|  | # logging.loggers.l1.channel.class = ConsoleChannel | ||||||
|  | # logging.loggers.l1.channel.pattern = %s: [%p] %t | ||||||
|  | # logging.loggers.l1.level = information | ||||||
|  | # SplitterChannel | ||||||
|  | # logging.channels.splitter.class = SplitterChannel | ||||||
|  | # logging.channels.splitter.channels = l1,l2 | ||||||
|  | # logging.loggers.l2.name = logger2 | ||||||
|  | # logging.loggers.l2.channel = splitter | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
| @@ -40,28 +40,16 @@ openwifi.system.commandchannel = /tmp/app.ucentralsec | |||||||
| openwifi.service.key = ${SERVICE_KEY} | openwifi.service.key = ${SERVICE_KEY} | ||||||
| openwifi.service.key.password = ${SERVICE_KEY_PASSWORD} | openwifi.service.key.password = ${SERVICE_KEY_PASSWORD} | ||||||
|  |  | ||||||
| smssender.enabled = ${SMSSENDER_ENABLED} |  | ||||||
| smssender.provider = ${SMSSENDER_PROVIDER} |  | ||||||
|  |  | ||||||
| smssender.aws.secretkey = ${SMSSENDER_AWS_SECRETKEY} |  | ||||||
| smssender.aws.accesskey = ${SMSSENDER_AWS_ACCESSKEY} |  | ||||||
| smssender.aws.region = ${SMSSENDER_AWS_REGION} |  | ||||||
|  |  | ||||||
| smssender.twilio.sid = ${SMSSENDER_TWILIO_SID} |  | ||||||
| smssender.twilio.token = ${SMSSENDER_TWILIO_TOKEN} |  | ||||||
| smssender.twilio.phonenumber = ${SMSSENDER_TWILIO_PHONENUMBER} |  | ||||||
|  |  | ||||||
| # | # | ||||||
| # Security Microservice Specific Section | # Security Microservice Specific Section | ||||||
| # | # | ||||||
| mailer.enabled = ${MAILER_ENABLED} |  | ||||||
| mailer.hostname = ${MAILER_HOSTNAME} | mailer.hostname = ${MAILER_HOSTNAME} | ||||||
| mailer.username = ${MAILER_USERNAME} | mailer.username = ${MAILER_USERNAME} | ||||||
| mailer.password = ${MAILER_PASSWORD} | mailer.password = ${MAILER_PASSWORD} | ||||||
| mailer.sender = ${MAILER_SENDER} | mailer.sender = ${MAILER_SENDER} | ||||||
| mailer.loginmethod = login | mailer.loginmethod = login | ||||||
| mailer.port = ${MAILER_PORT} | mailer.port = ${MAILER_PORT} | ||||||
| mailer.templates = ${MAILER_TEMPLATES} | mailer.templates = $UCENTRALSEC_ROOT/templates | ||||||
|  |  | ||||||
|  |  | ||||||
| ############################# | ############################# | ||||||
| @@ -82,13 +70,9 @@ openwifi.kafka.enable = ${KAFKA_ENABLE} | |||||||
| openwifi.kafka.brokerlist = ${KAFKA_BROKERLIST} | openwifi.kafka.brokerlist = ${KAFKA_BROKERLIST} | ||||||
| openwifi.kafka.auto.commit = false | openwifi.kafka.auto.commit = false | ||||||
| openwifi.kafka.queue.buffering.max.ms = 50 | openwifi.kafka.queue.buffering.max.ms = 50 | ||||||
| openwifi.kafka.ssl.ca.location = ${KAFKA_SSL_CA_LOCATION} |  | ||||||
| openwifi.kafka.ssl.certificate.location = ${KAFKA_SSL_CERTIFICATE_LOCATION} |  | ||||||
| openwifi.kafka.ssl.key.location = ${KAFKA_SSL_KEY_LOCATION} |  | ||||||
| openwifi.kafka.ssl.key.password = ${KAFKA_SSL_KEY_PASSWORD} |  | ||||||
|  |  | ||||||
| openwifi.document.policy.access = ${DOCUMENT_POLICY_ACCESS} | openwifi.document.policy.access = /wwwassets/access_policy.html | ||||||
| openwifi.document.policy.password = ${DOCUMENT_POLICY_PASSWORD} | openwifi.document.policy.password = /wwwassets/password_policy.html | ||||||
| openwifi.avatar.maxsize = 2000000 | openwifi.avatar.maxsize = 2000000 | ||||||
| # | # | ||||||
| # This section select which form of persistence you need | # This section select which form of persistence you need | ||||||
| @@ -126,6 +110,37 @@ storage.type.mysql.connectiontimeout = 60 | |||||||
| # Logging: please leave as is for now. | # Logging: please leave as is for now. | ||||||
| # | # | ||||||
| ######################################################################## | ######################################################################## | ||||||
| logging.type = console | logging.formatters.f1.class = PatternFormatter | ||||||
| logging.path = $OWSEC_ROOT/logs | logging.formatters.f1.pattern = %Y-%m-%d %H:%M:%S %s: [%p] %t | ||||||
| logging.level = debug | logging.formatters.f1.times = UTC | ||||||
|  | logging.channels.c1.class = ConsoleChannel | ||||||
|  | logging.channels.c1.formatter = f1 | ||||||
|  |  | ||||||
|  | # This is where the logs will be written. This path MUST exist | ||||||
|  | logging.channels.c2.class = FileChannel | ||||||
|  | logging.channels.c2.path = $UCENTRALSEC_ROOT/logs/log | ||||||
|  | logging.channels.c2.formatter.class = PatternFormatter | ||||||
|  | logging.channels.c2.formatter.pattern = %Y-%m-%d %H:%M:%S %s: [%p] %t | ||||||
|  | logging.channels.c2.rotation = 20 M | ||||||
|  | logging.channels.c2.archive = timestamp | ||||||
|  | logging.channels.c2.purgeCount = 20 | ||||||
|  | logging.channels.c3.class = ConsoleChannel | ||||||
|  | logging.channels.c3.pattern = %s: [%p] %t | ||||||
|  |  | ||||||
|  | # External Channel | ||||||
|  | logging.loggers.root.channel = c1 | ||||||
|  | logging.loggers.root.level = debug | ||||||
|  |  | ||||||
|  | # Inline Channel with PatternFormatter | ||||||
|  | # logging.loggers.l1.name = logger1 | ||||||
|  | # logging.loggers.l1.channel.class = ConsoleChannel | ||||||
|  | # logging.loggers.l1.channel.pattern = %s: [%p] %t | ||||||
|  | # logging.loggers.l1.level = information | ||||||
|  | # SplitterChannel | ||||||
|  | # logging.channels.splitter.class = SplitterChannel | ||||||
|  | # logging.channels.splitter.channels = l1,l2 | ||||||
|  | # logging.loggers.l2.name = logger2 | ||||||
|  | # logging.loggers.l2.channel = splitter | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,59 +0,0 @@ | |||||||
| #!/bin/bash |  | ||||||
| set -e |  | ||||||
|  |  | ||||||
| if [[ "$(which jq)" == "" ]] |  | ||||||
| then |  | ||||||
|   echo "You need the package jq installed to use this script." |  | ||||||
|   exit 1 |  | ||||||
| fi |  | ||||||
|  |  | ||||||
| if [[ "$(which curl)" == "" ]] |  | ||||||
| then |  | ||||||
|   echo "You need the package curl installed to use this script." |  | ||||||
|   exit 1 |  | ||||||
| fi |  | ||||||
|  |  | ||||||
|  |  | ||||||
| if [[ "${READINESS_METHOD}" == "systeminfo" ]] |  | ||||||
| then |  | ||||||
|   if [[ "${OWSEC_USERNAME}" == "" ]] |  | ||||||
|   then |  | ||||||
|     echo "You must set the variable OWSEC_USERNAME in order to use this script. Something like" |  | ||||||
|     echo "OWSEC_USERNAME=tip@ucentral.com" |  | ||||||
|     exit 1 |  | ||||||
|   fi |  | ||||||
|  |  | ||||||
|   if [[ "${OWSEC_PASSWORD}" == "" ]] |  | ||||||
|   then |  | ||||||
|     echo "You must set the variable OWSEC_PASSWORD in order to use this script. Something like" |  | ||||||
|     echo "OWSEC_PASSWORD=openwifi" |  | ||||||
|     exit 1 |  | ||||||
|   fi |  | ||||||
|  |  | ||||||
|   export RESTAPI_PORT=$(grep 'openwifi.restapi.host.0.port' $OWSEC_CONFIG/owsec.properties | awk -F '=' '{print $2}' | xargs | envsubst) |  | ||||||
|   # Get OAuth token from OWSEC and cache it or use cached one |  | ||||||
|   payload="{ \"userId\" : \"$OWSEC_USERNAME\" , \"password\" : \"$OWSEC_PASSWORD\" }" |  | ||||||
|   if [[ -f "/tmp/token" ]] |  | ||||||
|   then |  | ||||||
|     token=$(cat /tmp/token) |  | ||||||
|   else |  | ||||||
|     token=$(curl ${FLAGS} -k -X POST -H "Content-Type: application/json" -d "$payload" "https://localhost:$RESTAPI_PORT/api/v1/oauth2" | jq -r '.access_token') |  | ||||||
|   fi |  | ||||||
|   if [[ "${token}" == "" ]] |  | ||||||
|   then |  | ||||||
|     echo "Could not login. Please verify the host and username/password." |  | ||||||
|     exit 13 |  | ||||||
|   fi |  | ||||||
|   echo -n $token > /tmp/token |  | ||||||
|  |  | ||||||
|   # Make systeminfo request to the local owsec instance |  | ||||||
|   curl ${FLAGS} -k -X GET "https://localhost:$RESTAPI_PORT/api/v1/system?command=info" \ |  | ||||||
|     -H "accept: application/json" \ |  | ||||||
|     -H "Authorization: Bearer ${token}" > /tmp/result.json |  | ||||||
|   exit_code=$? |  | ||||||
|   jq < /tmp/result.json |  | ||||||
|   exit $exit_code |  | ||||||
| else |  | ||||||
|   export ALB_PORT=$(grep 'alb.port' $OWSEC_CONFIG/owsec.properties | awk -F '=' '{print $2}' | xargs | envsubst) |  | ||||||
|   curl localhost:$ALB_PORT |  | ||||||
| fi |  | ||||||
| @@ -1,90 +0,0 @@ | |||||||
| // |  | ||||||
| // Created by stephane bourque on 2021-11-12. |  | ||||||
| // |  | ||||||
|  |  | ||||||
| #ifndef OWSEC_ACLPROCESSOR_H |  | ||||||
| #define OWSEC_ACLPROCESSOR_H |  | ||||||
|  |  | ||||||
| #include "RESTObjects/RESTAPI_SecurityObjects.h" |  | ||||||
|  |  | ||||||
| namespace OpenWifi { |  | ||||||
|  |  | ||||||
|     class ACLProcessor { |  | ||||||
|     public: |  | ||||||
|         enum ACL_OPS { |  | ||||||
|             READ, |  | ||||||
|             MODIFY, |  | ||||||
|             DELETE, |  | ||||||
|             CREATE |  | ||||||
|         }; |  | ||||||
| /* |  | ||||||
|  *  0) You can only delete yourself if you are a subscriber |  | ||||||
|     1) You cannot delete yourself |  | ||||||
|     2) If you are root, you can do anything. |  | ||||||
|     3) You can do anything to yourself |  | ||||||
|     4) Nobody can touch a root, unless they are a root, unless it is to get information on a ROOT |  | ||||||
|     5) Creation rules: |  | ||||||
|         ROOT -> create anything |  | ||||||
|         PARTNER -> (multi-tenant owner) admin,subs,csr,installer,noc,accounting - matches to an entity in provisioning |  | ||||||
|         ADMIN -> admin-subs-csr-installer-noc-accounting |  | ||||||
|         ACCOUNTING -> subs-installer-csr |  | ||||||
|  |  | ||||||
|  */ |  | ||||||
|         static inline bool Can( const SecurityObjects::UserInfo & User, const SecurityObjects::UserInfo & Target, ACL_OPS Op) { |  | ||||||
|  |  | ||||||
|             // rule 0 |  | ||||||
|             if(User.id == Target.id && User.userRole == SecurityObjects::SUBSCRIBER && Op == DELETE) |  | ||||||
|                 return true; |  | ||||||
|  |  | ||||||
|             //  rule 1 |  | ||||||
|             if(User.id == Target.id && Op==DELETE) |  | ||||||
|                 return false; |  | ||||||
|  |  | ||||||
|             //  rule 2 |  | ||||||
|             if(User.userRole==SecurityObjects::ROOT) |  | ||||||
|                 return true; |  | ||||||
|  |  | ||||||
|             //  rule 3 |  | ||||||
|             if(User.id == Target.id) |  | ||||||
|                 return true; |  | ||||||
|  |  | ||||||
|             //  rule 4 |  | ||||||
|             if(Target.userRole==SecurityObjects::ROOT && Op!=READ) |  | ||||||
|                 return false; |  | ||||||
|  |  | ||||||
|             if(Op==CREATE) { |  | ||||||
|                 if(User.userRole==SecurityObjects::ROOT) |  | ||||||
|                     return true; |  | ||||||
|                 if(User.userRole==SecurityObjects::PARTNER && (Target.userRole==SecurityObjects::ADMIN || |  | ||||||
|                     Target.userRole==SecurityObjects::SUBSCRIBER || |  | ||||||
|                     Target.userRole==SecurityObjects::CSR || |  | ||||||
|                     Target.userRole==SecurityObjects::INSTALLER || |  | ||||||
|                     Target.userRole==SecurityObjects::NOC || |  | ||||||
|                     Target.userRole==SecurityObjects::ACCOUNTING)) |  | ||||||
|                     return true; |  | ||||||
|                 if(User.userRole==SecurityObjects::ADMIN && |  | ||||||
|                     (Target.userRole==SecurityObjects::ADMIN || |  | ||||||
|                     Target.userRole==SecurityObjects::SUBSCRIBER || |  | ||||||
|                     Target.userRole==SecurityObjects::CSR || |  | ||||||
|                     Target.userRole==SecurityObjects::INSTALLER || |  | ||||||
|                     Target.userRole==SecurityObjects::NOC || |  | ||||||
|                     Target.userRole==SecurityObjects::ACCOUNTING)) |  | ||||||
|                     return true; |  | ||||||
|                 if(User.userRole==SecurityObjects::ACCOUNTING && |  | ||||||
|                     (Target.userRole==SecurityObjects::SUBSCRIBER || |  | ||||||
|                     Target.userRole==SecurityObjects::INSTALLER || |  | ||||||
|                     Target.userRole==SecurityObjects::CSR)) |  | ||||||
|                     return true; |  | ||||||
|                 return false; |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             return true; |  | ||||||
|         } |  | ||||||
|     private: |  | ||||||
|  |  | ||||||
|     }; |  | ||||||
|  |  | ||||||
| } |  | ||||||
|  |  | ||||||
|  |  | ||||||
| #endif //OWSEC_ACLPROCESSOR_H |  | ||||||
							
								
								
									
										118
									
								
								src/ALBHealthCheckServer.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										118
									
								
								src/ALBHealthCheckServer.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,118 @@ | |||||||
|  | // | ||||||
|  | //	License type: BSD 3-Clause License | ||||||
|  | //	License copy: https://github.com/Telecominfraproject/wlan-cloud-ucentralgw/blob/master/LICENSE | ||||||
|  | // | ||||||
|  | //	Created by Stephane Bourque on 2021-03-04. | ||||||
|  | //	Arilia Wireless Inc. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef UCENTRALGW_ALBHEALTHCHECKSERVER_H | ||||||
|  | #define UCENTRALGW_ALBHEALTHCHECKSERVER_H | ||||||
|  |  | ||||||
|  | #include <memory> | ||||||
|  | #include <iostream> | ||||||
|  | #include <fstream> | ||||||
|  | #include <sstream> | ||||||
|  |  | ||||||
|  | #include "Poco/Thread.h" | ||||||
|  | #include "Poco/Net/HTTPServer.h" | ||||||
|  | #include "Poco/Net/HTTPServerRequest.h" | ||||||
|  | #include "Poco/Net/HTTPServerResponse.h" | ||||||
|  | #include "Poco/Net/HTTPRequestHandler.h" | ||||||
|  | #include "Poco/Logger.h" | ||||||
|  |  | ||||||
|  | #include "Daemon.h" | ||||||
|  | #include "SubSystemServer.h" | ||||||
|  |  | ||||||
|  | namespace OpenWifi { | ||||||
|  |  | ||||||
|  | 	class ALBRequestHandler: public Poco::Net::HTTPRequestHandler | ||||||
|  | 			/// Return a HTML document with the current date and time. | ||||||
|  | 		{ | ||||||
|  | 		  public: | ||||||
|  | 			explicit ALBRequestHandler(Poco::Logger & L) | ||||||
|  | 				: Logger_(L) | ||||||
|  | 			{ | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			void handleRequest(Poco::Net::HTTPServerRequest& Request, Poco::Net::HTTPServerResponse& Response) override | ||||||
|  | 			{ | ||||||
|  | 				Logger_.information(Poco::format("ALB-REQUEST(%s): New ALB request.",Request.clientAddress().toString())); | ||||||
|  | 				Response.setChunkedTransferEncoding(true); | ||||||
|  | 				Response.setContentType("text/html"); | ||||||
|  | 				Response.setDate(Poco::Timestamp()); | ||||||
|  | 				Response.setStatus(Poco::Net::HTTPResponse::HTTP_OK); | ||||||
|  | 				Response.setKeepAlive(true); | ||||||
|  | 				Response.set("Connection","keep-alive"); | ||||||
|  | 				Response.setVersion(Poco::Net::HTTPMessage::HTTP_1_1); | ||||||
|  | 				std::ostream &Answer = Response.send(); | ||||||
|  | 				Answer << "uCentralGW Alive and kicking!" ; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 	  private: | ||||||
|  | 		Poco::Logger 	& Logger_; | ||||||
|  | 	}; | ||||||
|  |  | ||||||
|  | 	class ALBRequestHandlerFactory: public Poco::Net::HTTPRequestHandlerFactory | ||||||
|  | 		{ | ||||||
|  | 		  public: | ||||||
|  | 			explicit ALBRequestHandlerFactory(Poco::Logger & L): | ||||||
|  | 				Logger_(L) | ||||||
|  | 			{ | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			ALBRequestHandler* createRequestHandler(const Poco::Net::HTTPServerRequest& request) override | ||||||
|  | 			{ | ||||||
|  | 				if (request.getURI() == "/") | ||||||
|  | 					return new ALBRequestHandler(Logger_); | ||||||
|  | 				else | ||||||
|  | 					return nullptr; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 		  private: | ||||||
|  | 			Poco::Logger	&Logger_; | ||||||
|  | 		}; | ||||||
|  |  | ||||||
|  |     class ALBHealthCheckServer : public SubSystemServer { | ||||||
|  |         public: | ||||||
|  |             ALBHealthCheckServer() noexcept: | ||||||
|  |                     SubSystemServer("ALBHealthCheckServer", "ALB-SVR", "alb") | ||||||
|  |             { | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             static ALBHealthCheckServer *instance() { | ||||||
|  |                 if (instance_ == nullptr) { | ||||||
|  |                     instance_ = new ALBHealthCheckServer; | ||||||
|  |                 } | ||||||
|  |                 return instance_; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             int Start() override { | ||||||
|  |                 if(Daemon()->ConfigGetBool("alb.enable",false)) { | ||||||
|  |                     Port_ = (int)Daemon()->ConfigGetInt("alb.port",15015); | ||||||
|  |                     Socket_ = std::make_unique<Poco::Net::ServerSocket>(Port_); | ||||||
|  |                     auto Params = new Poco::Net::HTTPServerParams; | ||||||
|  |                     Server_ = std::make_unique<Poco::Net::HTTPServer>(new ALBRequestHandlerFactory(Logger_), *Socket_, Params); | ||||||
|  |                     Server_->start(); | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 return 0; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             void Stop() override { | ||||||
|  |                 if(Server_) | ||||||
|  |                     Server_->stop(); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |           private: | ||||||
|  |             static ALBHealthCheckServer *instance_; | ||||||
|  |             std::unique_ptr<Poco::Net::HTTPServer>   	Server_; | ||||||
|  |             std::unique_ptr<Poco::Net::ServerSocket> 	Socket_; | ||||||
|  |             int                                     	Port_ = 0; | ||||||
|  |         }; | ||||||
|  |  | ||||||
|  |     inline ALBHealthCheckServer * ALBHealthCheckServer() { return ALBHealthCheckServer::instance(); } | ||||||
|  |     inline class ALBHealthCheckServer * ALBHealthCheckServer::instance_ = nullptr; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #endif // UCENTRALGW_ALBHEALTHCHECKSERVER_H | ||||||
| @@ -1,106 +0,0 @@ | |||||||
| // |  | ||||||
| // Created by stephane bourque on 2021-11-08. |  | ||||||
| // |  | ||||||
|  |  | ||||||
| #include "ActionLinkManager.h" |  | ||||||
| #include "StorageService.h" |  | ||||||
| #include "RESTObjects/RESTAPI_SecurityObjects.h" |  | ||||||
|  |  | ||||||
| namespace OpenWifi { |  | ||||||
|  |  | ||||||
|     int ActionLinkManager::Start() { |  | ||||||
|         if(!Running_) |  | ||||||
|             Thr_.start(*this); |  | ||||||
|         return 0; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     void ActionLinkManager::Stop() { |  | ||||||
|         if(Running_) { |  | ||||||
|             Running_ = false; |  | ||||||
|             Thr_.wakeUp(); |  | ||||||
|             Thr_.join(); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     void ActionLinkManager::run() { |  | ||||||
|         Running_ = true ; |  | ||||||
|  |  | ||||||
|         while(Running_) { |  | ||||||
|             Poco::Thread::trySleep(2000); |  | ||||||
|             if(!Running_) |  | ||||||
|                 break; |  | ||||||
|             std::vector<SecurityObjects::ActionLink>    Links; |  | ||||||
|             { |  | ||||||
|                 std::lock_guard G(Mutex_); |  | ||||||
|                 StorageService()->ActionLinksDB().GetActions(Links); |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             if(Links.empty()) |  | ||||||
|                 continue; |  | ||||||
|  |  | ||||||
|             for(auto &i:Links) { |  | ||||||
|                 if(!Running_) |  | ||||||
|                     break; |  | ||||||
|  |  | ||||||
|                 SecurityObjects::UserInfo UInfo; |  | ||||||
|                 if((i.action==OpenWifi::SecurityObjects::LinkActions::FORGOT_PASSWORD || |  | ||||||
|                     i.action==OpenWifi::SecurityObjects::LinkActions::VERIFY_EMAIL) && !StorageService()->UserDB().GetUserById(i.userId,UInfo)) { |  | ||||||
|                     StorageService()->ActionLinksDB().CancelAction(i.id); |  | ||||||
|                     continue; |  | ||||||
|                 } else if(( i.action==OpenWifi::SecurityObjects::LinkActions::SUB_FORGOT_PASSWORD || |  | ||||||
|                             i.action==OpenWifi::SecurityObjects::LinkActions::SUB_VERIFY_EMAIL || |  | ||||||
|                             i.action==OpenWifi::SecurityObjects::LinkActions::SUB_SIGNUP ) && !StorageService()->SubDB().GetUserById(i.userId,UInfo)) { |  | ||||||
|                     StorageService()->ActionLinksDB().CancelAction(i.id); |  | ||||||
|                     continue; |  | ||||||
|                 } |  | ||||||
|  |  | ||||||
|                 switch(i.action) { |  | ||||||
|                     case OpenWifi::SecurityObjects::LinkActions::FORGOT_PASSWORD: { |  | ||||||
|                             if(AuthService::SendEmailToUser(i.id, UInfo.email, AuthService::FORGOT_PASSWORD)) { |  | ||||||
|                                 Logger().information(fmt::format("Send password reset link to {}",UInfo.email)); |  | ||||||
|                             } |  | ||||||
|                             StorageService()->ActionLinksDB().SentAction(i.id); |  | ||||||
|                         } |  | ||||||
|                         break; |  | ||||||
|  |  | ||||||
|                     case OpenWifi::SecurityObjects::LinkActions::VERIFY_EMAIL: { |  | ||||||
|                             if(AuthService::SendEmailToUser(i.id, UInfo.email, AuthService::EMAIL_VERIFICATION)) { |  | ||||||
|                                 Logger().information(fmt::format("Send email verification link to {}",UInfo.email)); |  | ||||||
|                             } |  | ||||||
|                             StorageService()->ActionLinksDB().SentAction(i.id); |  | ||||||
|                         } |  | ||||||
|                         break; |  | ||||||
|  |  | ||||||
|                     case OpenWifi::SecurityObjects::LinkActions::SUB_FORGOT_PASSWORD: { |  | ||||||
|                             if(AuthService::SendEmailToSubUser(i.id, UInfo.email, AuthService::FORGOT_PASSWORD)) { |  | ||||||
|                                 Logger().information(fmt::format("Send subscriber password reset link to {}",UInfo.email)); |  | ||||||
|                             } |  | ||||||
|                             StorageService()->ActionLinksDB().SentAction(i.id); |  | ||||||
|                         } |  | ||||||
|                         break; |  | ||||||
|  |  | ||||||
|                     case OpenWifi::SecurityObjects::LinkActions::SUB_VERIFY_EMAIL: { |  | ||||||
|                             if(AuthService::SendEmailToSubUser(i.id, UInfo.email, AuthService::EMAIL_VERIFICATION)) { |  | ||||||
|                                 Logger().information(fmt::format("Send subscriber email verification link to {}",UInfo.email)); |  | ||||||
|                             } |  | ||||||
|                             StorageService()->ActionLinksDB().SentAction(i.id); |  | ||||||
|                         } |  | ||||||
|                         break; |  | ||||||
|  |  | ||||||
|                     case OpenWifi::SecurityObjects::LinkActions::SUB_SIGNUP: { |  | ||||||
|                         if(AuthService::SendEmailToSubUser(i.id, UInfo.email, AuthService::SIGNUP_VERIFICATION)) { |  | ||||||
|                             Logger().information(fmt::format("Send new subscriber email verification link to {}",UInfo.email)); |  | ||||||
|                         } |  | ||||||
|                         StorageService()->ActionLinksDB().SentAction(i.id); |  | ||||||
|                         } |  | ||||||
|                         break; |  | ||||||
|  |  | ||||||
|                     default: { |  | ||||||
|                         StorageService()->ActionLinksDB().SentAction(i.id); |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
| } |  | ||||||
| @@ -1,44 +0,0 @@ | |||||||
| // |  | ||||||
| // Created by stephane bourque on 2021-11-08. |  | ||||||
| // |  | ||||||
|  |  | ||||||
| #ifndef OWSEC_ACTIONLINKMANAGER_H |  | ||||||
| #define OWSEC_ACTIONLINKMANAGER_H |  | ||||||
|  |  | ||||||
| #include "framework/MicroService.h" |  | ||||||
|  |  | ||||||
| namespace OpenWifi { |  | ||||||
|  |  | ||||||
|     class ActionLinkManager : public SubSystemServer, Poco::Runnable { |  | ||||||
|     public: |  | ||||||
|  |  | ||||||
| /*        enum Actions { |  | ||||||
|             FORGOT_PASSWORD, |  | ||||||
|             VERIFY_EMAIL, |  | ||||||
|             SUB_FORGOT_PASSWORD, |  | ||||||
|             SUB_VERIFY_EMAIL, |  | ||||||
|             SUB_SIGNUP |  | ||||||
|         }; |  | ||||||
| */ |  | ||||||
|         static ActionLinkManager * instance() { |  | ||||||
|             static auto instance_ = new ActionLinkManager; |  | ||||||
|             return instance_; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         int Start() final; |  | ||||||
|         void Stop() final; |  | ||||||
|         void run() final; |  | ||||||
|  |  | ||||||
|     private: |  | ||||||
|         Poco::Thread        Thr_; |  | ||||||
|         std::atomic_bool    Running_ = false; |  | ||||||
|  |  | ||||||
|         ActionLinkManager() noexcept: |  | ||||||
|             SubSystemServer("ActionLinkManager", "ACTION-SVR", "action.server") |  | ||||||
|                 { |  | ||||||
|                 } |  | ||||||
|     }; |  | ||||||
|     inline ActionLinkManager * ActionLinkManager() { return ActionLinkManager::instance(); } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| #endif //OWSEC_ACTIONLINKMANAGER_H |  | ||||||
							
								
								
									
										93
									
								
								src/AuthClient.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										93
									
								
								src/AuthClient.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,93 @@ | |||||||
|  | // | ||||||
|  | //	License type: BSD 3-Clause License | ||||||
|  | //	License copy: https://github.com/Telecominfraproject/wlan-cloud-ucentralgw/blob/master/LICENSE | ||||||
|  | // | ||||||
|  | //	Created by Stephane Bourque on 2021-03-04. | ||||||
|  | //	Arilia Wireless Inc. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #include <utility> | ||||||
|  |  | ||||||
|  | #include "AuthClient.h" | ||||||
|  | #include "RESTAPI_SecurityObjects.h" | ||||||
|  | #include "Daemon.h" | ||||||
|  | #include "OpenAPIRequest.h" | ||||||
|  |  | ||||||
|  | namespace OpenWifi { | ||||||
|  | 	class AuthClient * AuthClient::instance_ = nullptr; | ||||||
|  |  | ||||||
|  | 	int AuthClient::Start() { | ||||||
|  | 		return 0; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	void AuthClient::Stop() { | ||||||
|  |  | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	void AuthClient::RemovedCachedToken(const std::string &Token) { | ||||||
|  | 		std::lock_guard	G(Mutex_); | ||||||
|  | 		UserCache_.erase(Token); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	bool IsTokenExpired(const SecurityObjects::WebToken &T) { | ||||||
|  | 		return ((T.expires_in_+T.created_)<std::time(nullptr)); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	bool AuthClient::IsAuthorized(Poco::Net::HTTPServerRequest & Request, std::string &SessionToken, SecurityObjects::UserInfoAndPolicy & UInfo ) { | ||||||
|  | 		std::lock_guard G(Mutex_); | ||||||
|  |  | ||||||
|  | 		auto User = UserCache_.find(SessionToken); | ||||||
|  | 		if(User != UserCache_.end() && !IsTokenExpired(User->second.webtoken)) { | ||||||
|  | 			UInfo = User->second; | ||||||
|  | 			return true; | ||||||
|  | 		} else { | ||||||
|  | 			Types::StringPairVec QueryData; | ||||||
|  | 			QueryData.push_back(std::make_pair("token",SessionToken)); | ||||||
|  | 			OpenAPIRequestGet	Req(    uSERVICE_SECURITY, | ||||||
|  | 								  	"/api/v1/validateToken", | ||||||
|  | 									 QueryData, | ||||||
|  | 								  5000); | ||||||
|  | 			Poco::JSON::Object::Ptr Response; | ||||||
|  | 			if(Req.Do(Response)==Poco::Net::HTTPResponse::HTTP_OK) { | ||||||
|  | 				if(Response->has("tokenInfo") && Response->has("userInfo")) { | ||||||
|  | 					SecurityObjects::UserInfoAndPolicy	P; | ||||||
|  | 					P.from_json(Response); | ||||||
|  | 					UserCache_[SessionToken] = P; | ||||||
|  | 					UInfo = P; | ||||||
|  | 				} | ||||||
|  | 				return true; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 		} | ||||||
|  | 		return false; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	bool AuthClient::IsTokenAuthorized(const std::string &SessionToken, SecurityObjects::UserInfoAndPolicy & UInfo) { | ||||||
|  | 		std::lock_guard G(Mutex_); | ||||||
|  |  | ||||||
|  | 		auto User = UserCache_.find(SessionToken); | ||||||
|  | 		if(User != UserCache_.end() && !IsTokenExpired(User->second.webtoken)) { | ||||||
|  | 			UInfo = User->second; | ||||||
|  | 			return true; | ||||||
|  | 		} else { | ||||||
|  | 			Types::StringPairVec QueryData; | ||||||
|  | 			QueryData.push_back(std::make_pair("token",SessionToken)); | ||||||
|  | 			OpenAPIRequestGet	Req(uSERVICE_SECURITY, | ||||||
|  | 									 "/api/v1/validateToken", | ||||||
|  | 									 QueryData, | ||||||
|  | 									 5000); | ||||||
|  | 			Poco::JSON::Object::Ptr Response; | ||||||
|  | 			if(Req.Do(Response)==Poco::Net::HTTPResponse::HTTP_OK) { | ||||||
|  | 				if(Response->has("tokenInfo") && Response->has("userInfo")) { | ||||||
|  | 					SecurityObjects::UserInfoAndPolicy	P; | ||||||
|  | 					P.from_json(Response); | ||||||
|  | 					UserCache_[SessionToken] = P; | ||||||
|  | 					UInfo = P; | ||||||
|  | 				} | ||||||
|  | 				return true; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 		} | ||||||
|  | 		return false; | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										45
									
								
								src/AuthClient.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								src/AuthClient.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,45 @@ | |||||||
|  | // | ||||||
|  | // Created by stephane bourque on 2021-06-30. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef UCENTRALGW_AUTHCLIENT_H | ||||||
|  | #define UCENTRALGW_AUTHCLIENT_H | ||||||
|  |  | ||||||
|  | #include "Poco/JSON/Object.h" | ||||||
|  | #include "Poco/Net/HTTPServerRequest.h" | ||||||
|  | #include "Poco/Net/HTTPServerResponse.h" | ||||||
|  | #include "Poco/JWT/Signer.h" | ||||||
|  | #include "Poco/SHA2Engine.h" | ||||||
|  | #include "RESTAPI_SecurityObjects.h" | ||||||
|  | #include "SubSystemServer.h" | ||||||
|  |  | ||||||
|  | namespace OpenWifi { | ||||||
|  |  | ||||||
|  | class AuthClient : public SubSystemServer { | ||||||
|  | 	  public: | ||||||
|  | 		explicit AuthClient() noexcept: | ||||||
|  | 			SubSystemServer("Authentication", "AUTH-CLNT", "authentication") | ||||||
|  | 		{ | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		static AuthClient *instance() { | ||||||
|  | 			if (instance_ == nullptr) { | ||||||
|  | 				instance_ = new AuthClient; | ||||||
|  | 			} | ||||||
|  | 			return instance_; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		int Start() override; | ||||||
|  | 		void Stop() override; | ||||||
|  | 		bool IsAuthorized(Poco::Net::HTTPServerRequest & Request, std::string &SessionToken, OpenWifi::SecurityObjects::UserInfoAndPolicy & UInfo ); | ||||||
|  | 		void RemovedCachedToken(const std::string &Token); | ||||||
|  | 		bool IsTokenAuthorized(const std::string &Token, SecurityObjects::UserInfoAndPolicy & UInfo); | ||||||
|  | 	  private: | ||||||
|  | 		static AuthClient 					*instance_; | ||||||
|  | 		OpenWifi::SecurityObjects::UserInfoCache 		UserCache_; | ||||||
|  | 	}; | ||||||
|  |  | ||||||
|  | 	inline AuthClient * AuthClient() { return AuthClient::instance(); } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #endif // UCENTRALGW_AUTHCLIENT_H | ||||||
| @@ -11,17 +11,19 @@ | |||||||
| #include "Poco/Net/OAuth20Credentials.h" | #include "Poco/Net/OAuth20Credentials.h" | ||||||
| #include "Poco/JWT/Token.h" | #include "Poco/JWT/Token.h" | ||||||
| #include "Poco/JWT/Signer.h" | #include "Poco/JWT/Signer.h" | ||||||
| #include "Poco/StringTokenizer.h" |  | ||||||
|  |  | ||||||
| #include "framework/MicroService.h" | #include "Daemon.h" | ||||||
|  | #include "RESTAPI_handler.h" | ||||||
| #include "StorageService.h" | #include "StorageService.h" | ||||||
| #include "AuthService.h" | #include "AuthService.h" | ||||||
| #include "framework/KafkaTopics.h" | #include "Utils.h" | ||||||
|  | #include "KafkaManager.h" | ||||||
|  | #include "Kafka_topics.h" | ||||||
|  |  | ||||||
| #include "SMTPMailerService.h" | #include "SMTPMailerService.h" | ||||||
| #include "MFAServer.h" |  | ||||||
|  |  | ||||||
| namespace OpenWifi { | namespace OpenWifi { | ||||||
|  |     class AuthService *AuthService::instance_ = nullptr; | ||||||
|  |  | ||||||
|     AuthService::ACCESS_TYPE AuthService::IntToAccessType(int C) { |     AuthService::ACCESS_TYPE AuthService::IntToAccessType(int C) { | ||||||
| 		switch (C) { | 		switch (C) { | ||||||
| @@ -33,7 +35,6 @@ namespace OpenWifi { | |||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  |  | ||||||
| 	int AuthService::AccessTypeToInt(ACCESS_TYPE T) { | 	int AuthService::AccessTypeToInt(ACCESS_TYPE T) { | ||||||
| 		switch (T) { | 		switch (T) { | ||||||
| 		case USERNAME: return 1; | 		case USERNAME: return 1; | ||||||
| @@ -43,254 +44,102 @@ namespace OpenWifi { | |||||||
| 		return 1;	// some compilers complain... | 		return 1;	// some compilers complain... | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|     static const std::string DefaultPassword_8_u_l_n_1{"^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[\\{\\}\\(\\)~_\\+\\|\\\\\\[\\]\\;\\:\\<\\>\\.\\,\\/\\?\\\"\\'\\`\\=#?!@$%^&*-]).{8,}$"}; |  | ||||||
|  |  | ||||||
|     int AuthService::Start() { |     int AuthService::Start() { | ||||||
| 		Logger().notice("Starting..."); | 		Signer_.setRSAKey(Daemon()->Key()); | ||||||
|         TokenAging_ = (uint64_t) MicroService::instance().ConfigGetInt("authentication.token.ageing", 30 * 24 * 60 * 60); | 		Signer_.addAllAlgorithms(); | ||||||
|         RefreshTokenLifeSpan_ = (uint64_t) MicroService::instance().ConfigGetInt("authentication.refresh_token.lifespan", 90 * 24 * 60 * 600); | 		Logger_.notice("Starting..."); | ||||||
|         HowManyOldPassword_ = MicroService::instance().ConfigGetInt("authentication.oldpasswords", 5); |         Secure_ = Daemon()->ConfigGetBool("authentication.enabled",true); | ||||||
|  |         DefaultPassword_ = Daemon()->ConfigGetString("authentication.default.password",""); | ||||||
|         AccessPolicy_ = MicroService::instance().ConfigPath("openwifi.document.policy.access", "/wwwassets/access_policy.html"); |         DefaultUserName_ = Daemon()->ConfigGetString("authentication.default.username",""); | ||||||
|         PasswordPolicy_ = MicroService::instance().ConfigPath("openwifi.document.policy.password", "/wwwassets/password_policy.html"); |         Mechanism_ = Daemon()->ConfigGetString("authentication.service.type","internal"); | ||||||
|         PasswordValidation_ = PasswordValidationStr_ = MicroService::instance().ConfigGetString("authentication.validation.expression",DefaultPassword_8_u_l_n_1); |         PasswordValidation_ = PasswordValidationStr_ = Daemon()->ConfigGetString("authentication.validation.expression","^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[#?!@$%^&*-]).{8,}$"); | ||||||
|  |         TokenAging_ = (uint64_t) Daemon()->ConfigGetInt("authentication.token.ageing", 30 * 24 * 60 * 60); | ||||||
|         SubPasswordValidation_ = SubPasswordValidationStr_ = MicroService::instance().ConfigGetString("subscriber.validation.expression",DefaultPassword_8_u_l_n_1); |         HowManyOldPassword_ = Daemon()->ConfigGetInt("authentication.oldpasswords", 5); | ||||||
|         SubAccessPolicy_ = MicroService::instance().ConfigPath("subscriber.policy.access", "/wwwassets/access_policy.html"); |  | ||||||
|         SubPasswordPolicy_ = MicroService::instance().ConfigPath("subscriber.policy.password", "/wwwassets/password_policy.html"); |  | ||||||
|  |  | ||||||
|         return 0; |         return 0; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     void AuthService::Stop() { |     void AuthService::Stop() { | ||||||
| 		Logger().notice("Stopping..."); | 		Logger_.notice("Stopping..."); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     bool AuthService::RefreshUserToken(Poco::Net::HTTPServerRequest & Request, const std::string & RefreshToken, SecurityObjects::UserInfoAndPolicy & UI) { | 	bool AuthService::IsAuthorized(Poco::Net::HTTPServerRequest & Request, std::string & SessionToken, SecurityObjects::UserInfoAndPolicy & UInfo ) | ||||||
|         try { |  | ||||||
|             std::string CallToken; |  | ||||||
|             Poco::Net::OAuth20Credentials Auth(Request); |  | ||||||
|             if (Auth.getScheme() == "Bearer") { |  | ||||||
|                 CallToken = Auth.getBearerToken(); |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             if (CallToken.empty()) { |  | ||||||
|                 return false; |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             uint64_t                    RevocationDate=0; |  | ||||||
|             std::string                 UserId; |  | ||||||
|             if(StorageService()->UserTokenDB().GetToken(CallToken, UI.webtoken, UserId, RevocationDate) && UI.webtoken.refresh_token_==RefreshToken) { |  | ||||||
|                 auto now = OpenWifi::Now(); |  | ||||||
|  |  | ||||||
|                 //  Create a new token |  | ||||||
|                 auto NewToken = GenerateTokenHMAC( UI.webtoken.access_token_, CUSTOM); |  | ||||||
|                 auto NewRefreshToken = RefreshToken; |  | ||||||
|                 if(now - UI.webtoken.lastRefresh_ < RefreshTokenLifeSpan_) { |  | ||||||
|                     NewRefreshToken = GenerateTokenHMAC( UI.webtoken.refresh_token_, CUSTOM); |  | ||||||
|                     UI.webtoken.lastRefresh_ = now; |  | ||||||
|                 } |  | ||||||
|  |  | ||||||
|                 StorageService()->UserTokenDB().RefreshToken(CallToken, NewToken, NewRefreshToken, UI.webtoken.lastRefresh_ ); |  | ||||||
|                 UI.webtoken.access_token_ = NewToken; |  | ||||||
|                 UI.webtoken.refresh_token_ = NewRefreshToken; |  | ||||||
|                 return true; |  | ||||||
|             } |  | ||||||
|             return false; |  | ||||||
|  |  | ||||||
|         } catch (...) { |  | ||||||
|  |  | ||||||
|         } |  | ||||||
|         return false; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     bool AuthService::RefreshSubToken(Poco::Net::HTTPServerRequest & Request, const std::string & RefreshToken, SecurityObjects::UserInfoAndPolicy & UI) { |  | ||||||
|         try { |  | ||||||
|             std::string CallToken; |  | ||||||
|             Poco::Net::OAuth20Credentials Auth(Request); |  | ||||||
|             if (Auth.getScheme() == "Bearer") { |  | ||||||
|                 CallToken = Auth.getBearerToken(); |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             if (CallToken.empty()) { |  | ||||||
|                 return false; |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             uint64_t                    RevocationDate=0; |  | ||||||
|             std::string                 UserId; |  | ||||||
|             if(StorageService()->SubTokenDB().GetToken(CallToken, UI.webtoken, UserId, RevocationDate) && UI.webtoken.refresh_token_==RefreshToken) { |  | ||||||
|                 auto now = OpenWifi::Now(); |  | ||||||
|  |  | ||||||
|                 //  Create a new token |  | ||||||
|                 auto NewToken = GenerateTokenHMAC( UI.webtoken.access_token_, CUSTOM); |  | ||||||
|                 auto NewRefreshToken = RefreshToken; |  | ||||||
|                 if(now - UI.webtoken.lastRefresh_ < RefreshTokenLifeSpan_) { |  | ||||||
|                     NewRefreshToken = GenerateTokenHMAC( UI.webtoken.refresh_token_, CUSTOM); |  | ||||||
|                     UI.webtoken.lastRefresh_ = now; |  | ||||||
|                 } |  | ||||||
|  |  | ||||||
|                 StorageService()->SubTokenDB().RefreshToken(CallToken, NewToken, NewRefreshToken, UI.webtoken.lastRefresh_ ); |  | ||||||
|                 UI.webtoken.access_token_ = NewToken; |  | ||||||
|                 UI.webtoken.refresh_token_ = NewRefreshToken; |  | ||||||
|                 return true; |  | ||||||
|             } |  | ||||||
|             return false; |  | ||||||
|  |  | ||||||
|         } catch (...) { |  | ||||||
|  |  | ||||||
|         } |  | ||||||
|         return false; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     bool AuthService::IsAuthorized(Poco::Net::HTTPServerRequest & Request, std::string & SessionToken, SecurityObjects::UserInfoAndPolicy & UInfo, bool & Expired ) |  | ||||||
|     { |     { | ||||||
|  |         if(!Secure_) | ||||||
|  |             return true; | ||||||
|  |  | ||||||
|         std::lock_guard		Guard(Mutex_); |         std::lock_guard		Guard(Mutex_); | ||||||
|         Expired = false; |  | ||||||
| 		try { |  | ||||||
| 		std::string CallToken; | 		std::string CallToken; | ||||||
|  |  | ||||||
|  | 		try { | ||||||
| 			Poco::Net::OAuth20Credentials Auth(Request); | 			Poco::Net::OAuth20Credentials Auth(Request); | ||||||
|  |  | ||||||
| 			if (Auth.getScheme() == "Bearer") { | 			if (Auth.getScheme() == "Bearer") { | ||||||
| 				CallToken = Auth.getBearerToken(); | 				CallToken = Auth.getBearerToken(); | ||||||
| 			} | 			} | ||||||
|  | 		} catch(const Poco::Exception &E) { | ||||||
|             if(CallToken.empty()) { |  | ||||||
|                 return false; |  | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|             SecurityObjects::WebToken   WT; | 		if(!CallToken.empty()) { | ||||||
|             uint64_t                    RevocationDate=0; | 		    if(Storage()->IsTokenRevoked(CallToken)) | ||||||
|             std::string                 UserId; |  | ||||||
|             if(StorageService()->UserTokenDB().GetToken(CallToken, WT, UserId, RevocationDate)) { |  | ||||||
|                 if(RevocationDate!=0) |  | ||||||
| 		        return false; | 		        return false; | ||||||
|                 auto now=OpenWifi::Now(); | 		    auto Client = UserCache_.find(CallToken); | ||||||
|                 Expired = (WT.created_ + WT.expires_in_) < now; | 		    if( Client == UserCache_.end() ) | ||||||
|                 if(StorageService()->UserDB().GetUserById(UserId,UInfo.userinfo)) { | 		        return ValidateToken(CallToken, CallToken, UInfo); | ||||||
|                     UInfo.webtoken = WT; |  | ||||||
|  | 		    if((Client->second.webtoken.created_ + Client->second.webtoken.expires_in_) > time(nullptr)) { | ||||||
| 		        SessionToken = CallToken; | 		        SessionToken = CallToken; | ||||||
|  | 		        UInfo = Client->second ; | ||||||
| 		        return true; | 		        return true; | ||||||
| 		    } | 		    } | ||||||
|             } | 		    UserCache_.erase(CallToken); | ||||||
|             return false; | 		    Storage()->RevokeToken(CallToken); | ||||||
| 		} catch(const Poco::Exception &E) { |  | ||||||
| 		    Logger().log(E); |  | ||||||
| 		} |  | ||||||
| 		    return false; | 		    return false; | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|     bool AuthService::IsSubAuthorized(Poco::Net::HTTPServerRequest & Request, std::string & SessionToken, SecurityObjects::UserInfoAndPolicy & UInfo, bool & Expired ) | 		return false; | ||||||
|     { |     } | ||||||
|  |  | ||||||
|  |     bool AuthService::DeleteUserFromCache(const std::string &UserName) { | ||||||
|         std::lock_guard		Guard(Mutex_); |         std::lock_guard		Guard(Mutex_); | ||||||
|         Expired = false; |  | ||||||
|         try { |  | ||||||
|             std::string CallToken; |  | ||||||
|             Poco::Net::OAuth20Credentials Auth(Request); |  | ||||||
|             if (Auth.getScheme() == "Bearer") { |  | ||||||
|                 CallToken = Auth.getBearerToken(); |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             if(CallToken.empty()) { |         for(auto i=UserCache_.begin();i!=UserCache_.end();) { | ||||||
|                 return false; |             if (i->second.userinfo.email==UserName) { | ||||||
|  |                 Logout(i->first); | ||||||
|  |                 i = UserCache_.erase(i); | ||||||
|  |             } else { | ||||||
|  |                 ++i; | ||||||
|  |             } | ||||||
|         } |         } | ||||||
|  |  | ||||||
|             SecurityObjects::WebToken   WT; |  | ||||||
|             uint64_t                    RevocationDate=0; |  | ||||||
|             std::string                 UserId; |  | ||||||
|             if(StorageService()->SubTokenDB().GetToken(CallToken, WT, UserId, RevocationDate)) { |  | ||||||
|                 if(RevocationDate!=0) |  | ||||||
|                     return false; |  | ||||||
|                 auto now=OpenWifi::Now(); |  | ||||||
|                 Expired = (WT.created_ + WT.expires_in_) < now; |  | ||||||
|                 if(StorageService()->SubDB().GetUserById(UserId,UInfo.userinfo)) { |  | ||||||
|                     UInfo.webtoken = WT; |  | ||||||
|                     SessionToken = CallToken; |  | ||||||
|         return true; |         return true; | ||||||
|     } |     } | ||||||
|             } |  | ||||||
|             return false; |  | ||||||
|         } catch(const Poco::Exception &E) { |  | ||||||
|             Logger().log(E); |  | ||||||
|         } |  | ||||||
|         return false; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     void AuthService::RevokeToken(std::string & Token) { |  | ||||||
|         StorageService()->UserTokenDB().RevokeToken(Token); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     void AuthService::RevokeSubToken(std::string & Token) { |  | ||||||
|         StorageService()->SubTokenDB().RevokeToken(Token); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     bool AuthService::DeleteUserFromCache(const std::string &Id) { |  | ||||||
|         return StorageService()->UserTokenDB().DeleteRecordsFromCache("userName",Id); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     bool AuthService::DeleteSubUserFromCache(const std::string &Id) { |  | ||||||
|         return StorageService()->SubTokenDB().DeleteRecordsFromCache("userName",Id); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     bool AuthService::RequiresMFA(const SecurityObjects::UserInfoAndPolicy &UInfo) { |  | ||||||
|         return (UInfo.userinfo.userTypeProprietaryInfo.mfa.enabled && MFAServer::MethodEnabled(UInfo.userinfo.userTypeProprietaryInfo.mfa.method)); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     bool AuthService::ValidatePassword(const std::string &Password) { |     bool AuthService::ValidatePassword(const std::string &Password) { | ||||||
|         return std::regex_match(Password, PasswordValidation_); |         return std::regex_match(Password, PasswordValidation_); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     bool AuthService::ValidateSubPassword(const std::string &Password) { |     void AuthService::Logout(const std::string &token) { | ||||||
|         return std::regex_match(Password, SubPasswordValidation_); | 		std::lock_guard		Guard(Mutex_); | ||||||
|     } |  | ||||||
|  | 		UserCache_.erase(token); | ||||||
|  |  | ||||||
|     void AuthService::RemoveTokenSystemWide(const std::string &token) { |  | ||||||
|         try { |         try { | ||||||
|             if(KafkaManager()->Enabled()) { |  | ||||||
|             Poco::JSON::Object Obj; |             Poco::JSON::Object Obj; | ||||||
|             Obj.set("event", "remove-token"); |             Obj.set("event", "remove-token"); | ||||||
|                 Obj.set("id", MicroService::instance().ID()); |             Obj.set("id", Daemon()->ID()); | ||||||
|             Obj.set("token", token); |             Obj.set("token", token); | ||||||
|             std::stringstream ResultText; |             std::stringstream ResultText; | ||||||
|             Poco::JSON::Stringifier::stringify(Obj, ResultText); |             Poco::JSON::Stringifier::stringify(Obj, ResultText); | ||||||
|                 KafkaManager()->PostMessage(KafkaTopics::SERVICE_EVENTS, MicroService::instance().PrivateEndPoint(), |             std::string Tmp{token}; | ||||||
|                                             ResultText.str(), |             Storage()->RevokeToken(Tmp); | ||||||
|  |             KafkaManager()->PostMessage(KafkaTopics::SERVICE_EVENTS, Daemon()->PrivateEndPoint(), ResultText.str(), | ||||||
|                                         false); |                                         false); | ||||||
|             } |  | ||||||
|         } catch (const Poco::Exception &E) { |         } catch (const Poco::Exception &E) { | ||||||
|             Logger().log(E); |             Logger_.log(E); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     void AuthService::Logout(const std::string &Token,[[maybe_unused]]  bool EraseFromCache) { |     std::string AuthService::GenerateToken(const std::string & Identity, ACCESS_TYPE Type) { | ||||||
| 		std::lock_guard		Guard(Mutex_); |  | ||||||
|  |  | ||||||
|         try { |  | ||||||
|             auto tToken{Token}; |  | ||||||
|             StorageService()->UserTokenDB().DeleteRecord("token",tToken); |  | ||||||
|             StorageService()->LoginDB().AddLogout(Token); |  | ||||||
|         } catch (const Poco::Exception &E) { |  | ||||||
|             Logger().log(E); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     void AuthService::SubLogout(const std::string &Token, [[maybe_unused]] bool EraseFromCache) { |  | ||||||
|         std::lock_guard		Guard(Mutex_); |  | ||||||
|  |  | ||||||
|         try { |  | ||||||
|             auto tToken{Token}; |  | ||||||
|             StorageService()->SubTokenDB().DeleteRecord("token",tToken); |  | ||||||
|             StorageService()->SubLoginDB().AddLogout(Token); |  | ||||||
|         } catch (const Poco::Exception &E) { |  | ||||||
|             Logger().log(E); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     [[nodiscard]] std::string AuthService::GenerateTokenHMAC(const std::string & UserName, [[maybe_unused]] ACCESS_TYPE Type) { |  | ||||||
|         std::string Identity(UserName + ":" + fmt::format("{}",OpenWifi::Now()) + ":" + std::to_string(rand())); |  | ||||||
|         HMAC_.update(Identity); |  | ||||||
|         return Poco::DigestEngine::digestToHex(HMAC_.digest()); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     std::string AuthService::GenerateTokenJWT(const std::string & Identity, ACCESS_TYPE Type) { |  | ||||||
|         std::lock_guard		Guard(Mutex_); |         std::lock_guard		Guard(Mutex_); | ||||||
|  |  | ||||||
| 		Poco::JWT::Token	T; | 		Poco::JWT::Token	T; | ||||||
| @@ -305,11 +154,34 @@ namespace OpenWifi { | |||||||
| 		T.payload().set("identity", Identity); | 		T.payload().set("identity", Identity); | ||||||
| 		T.setIssuedAt(Poco::Timestamp()); | 		T.setIssuedAt(Poco::Timestamp()); | ||||||
| 		T.setExpiration(Poco::Timestamp() + (long long)TokenAging_); | 		T.setExpiration(Poco::Timestamp() + (long long)TokenAging_); | ||||||
| 		std::string JWT = MicroService::instance().Sign(T,Poco::JWT::Signer::ALGO_RS256); | 		std::string JWT = Signer_.sign(T,Poco::JWT::Signer::ALGO_RS256); | ||||||
|  |  | ||||||
| 		return JWT; | 		return JWT; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  | 	bool AuthService::ValidateToken(const std::string & Token, std::string & SessionToken, SecurityObjects::UserInfoAndPolicy & UInfo  ) { | ||||||
|  |         std::lock_guard		Guard(Mutex_); | ||||||
|  | 		Poco::JWT::Token	DecryptedToken; | ||||||
|  |  | ||||||
|  | 		try { | ||||||
|  |             auto E = UserCache_.find(SessionToken); | ||||||
|  |             if(E == UserCache_.end()) { | ||||||
|  |                 if(Storage()->GetToken(SessionToken,UInfo)) { | ||||||
|  |                     if(Storage()->GetUserById(UInfo.userinfo.email,UInfo.userinfo)) { | ||||||
|  |                         UserCache_[UInfo.webtoken.access_token_] = UInfo; | ||||||
|  |                         return true; | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } else { | ||||||
|  |                 UInfo = E->second; | ||||||
|  |                 return true; | ||||||
|  |             } | ||||||
|  | 		} catch (const Poco::Exception &E ) { | ||||||
|  | 			Logger_.log(E); | ||||||
|  | 		} | ||||||
|  | 		return false; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|     void AuthService::CreateToken(const std::string & UserName, SecurityObjects::UserInfoAndPolicy &UInfo) |     void AuthService::CreateToken(const std::string & UserName, SecurityObjects::UserInfoAndPolicy &UInfo) | ||||||
|     { |     { | ||||||
|         std::lock_guard		Guard(Mutex_); |         std::lock_guard		Guard(Mutex_); | ||||||
| @@ -320,170 +192,50 @@ namespace OpenWifi { | |||||||
|         UInfo.webtoken.expires_in_ = TokenAging_ ; |         UInfo.webtoken.expires_in_ = TokenAging_ ; | ||||||
|         UInfo.webtoken.idle_timeout_ = 5 * 60; |         UInfo.webtoken.idle_timeout_ = 5 * 60; | ||||||
|         UInfo.webtoken.token_type_ = "Bearer"; |         UInfo.webtoken.token_type_ = "Bearer"; | ||||||
|         UInfo.webtoken.access_token_ = GenerateTokenHMAC(UInfo.userinfo.id,USERNAME); |         UInfo.webtoken.access_token_ = GenerateToken(UInfo.userinfo.Id,USERNAME); | ||||||
|         UInfo.webtoken.id_token_ = GenerateTokenHMAC(UInfo.userinfo.id,USERNAME); |         UInfo.webtoken.id_token_ = GenerateToken(UInfo.userinfo.Id,USERNAME); | ||||||
|         UInfo.webtoken.refresh_token_ = GenerateTokenHMAC(UInfo.userinfo.id,CUSTOM); |         UInfo.webtoken.refresh_token_ = GenerateToken(UInfo.userinfo.Id,CUSTOM); | ||||||
|         UInfo.webtoken.created_ = time(nullptr); |         UInfo.webtoken.created_ = time(nullptr); | ||||||
|         UInfo.webtoken.username_ = UserName; |         UInfo.webtoken.username_ = UserName; | ||||||
|         UInfo.webtoken.errorCode = 0; |         UInfo.webtoken.errorCode = 0; | ||||||
|         UInfo.webtoken.userMustChangePassword = false; |         UInfo.webtoken.userMustChangePassword = false; | ||||||
|         StorageService()->UserDB().SetLastLogin(UInfo.userinfo.id); |         UserCache_[UInfo.webtoken.access_token_] = UInfo; | ||||||
|         StorageService()->UserTokenDB().AddToken(UInfo.userinfo.id, UInfo.webtoken.access_token_, |         Storage()->SetLastLogin(UInfo.userinfo.Id); | ||||||
|  |         Storage()->AddToken(UInfo.webtoken.username_, UInfo.webtoken.access_token_, | ||||||
|                             UInfo.webtoken.refresh_token_, UInfo.webtoken.token_type_, |                             UInfo.webtoken.refresh_token_, UInfo.webtoken.token_type_, | ||||||
|                                 UInfo.webtoken.expires_in_, UInfo.webtoken.idle_timeout_); |                                 UInfo.webtoken.expires_in_, UInfo.webtoken.idle_timeout_); | ||||||
|         StorageService()->LoginDB().AddLogin(UInfo.userinfo.id, UInfo.userinfo.email,UInfo.webtoken.access_token_ ); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     void AuthService::CreateSubToken(const std::string & UserName, SecurityObjects::UserInfoAndPolicy &UInfo) |  | ||||||
|     { |  | ||||||
|         std::lock_guard		Guard(Mutex_); |  | ||||||
|  |  | ||||||
|         SecurityObjects::AclTemplate	ACL; |  | ||||||
|         ACL.PortalLogin_ = ACL.Read_ = ACL.ReadWrite_ = ACL.ReadWriteCreate_ = ACL.Delete_ = true; |  | ||||||
|         UInfo.webtoken.acl_template_ = ACL; |  | ||||||
|         UInfo.webtoken.expires_in_ = TokenAging_ ; |  | ||||||
|         UInfo.webtoken.idle_timeout_ = 5 * 60; |  | ||||||
|         UInfo.webtoken.token_type_ = "Bearer"; |  | ||||||
|         UInfo.webtoken.access_token_ = GenerateTokenHMAC(UInfo.userinfo.id,USERNAME); |  | ||||||
|         UInfo.webtoken.id_token_ = GenerateTokenHMAC(UInfo.userinfo.id,USERNAME); |  | ||||||
|         UInfo.webtoken.refresh_token_ = GenerateTokenHMAC(UInfo.userinfo.id,CUSTOM); |  | ||||||
|         UInfo.webtoken.created_ = time(nullptr); |  | ||||||
|         UInfo.webtoken.username_ = UserName; |  | ||||||
|         UInfo.webtoken.errorCode = 0; |  | ||||||
|         UInfo.webtoken.userMustChangePassword = false; |  | ||||||
|         StorageService()->SubDB().SetLastLogin(UInfo.userinfo.id); |  | ||||||
|         StorageService()->SubTokenDB().AddToken(UInfo.userinfo.id, UInfo.webtoken.access_token_, |  | ||||||
|                                    UInfo.webtoken.refresh_token_, UInfo.webtoken.token_type_, |  | ||||||
|                                    UInfo.webtoken.expires_in_, UInfo.webtoken.idle_timeout_); |  | ||||||
|         StorageService()->SubLoginDB().AddLogin(UInfo.userinfo.id, UInfo.userinfo.email,UInfo.webtoken.access_token_ ); |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     bool AuthService::SetPassword(const std::string &NewPassword, SecurityObjects::UserInfo & UInfo) { |     bool AuthService::SetPassword(const std::string &NewPassword, SecurityObjects::UserInfo & UInfo) { | ||||||
|         std::lock_guard     G(Mutex_); |         auto NewPasswordHash = ComputePasswordHash(UInfo.email, NewPassword); | ||||||
|  |         for (auto const &i:UInfo.lastPasswords) { | ||||||
|         Poco::toLowerInPlace(UInfo.email); |             if (i == NewPasswordHash) { | ||||||
|         for (const auto &i:UInfo.lastPasswords) { |  | ||||||
|             auto Tokens = Poco::StringTokenizer(i,"|"); |  | ||||||
|             if(Tokens.count()==2) { |  | ||||||
|                 const auto & Salt = Tokens[0]; |  | ||||||
|                 for(const auto &j:UInfo.lastPasswords) { |  | ||||||
|                     auto OldTokens = Poco::StringTokenizer(j,"|"); |  | ||||||
|                     if(OldTokens.count()==2) { |  | ||||||
|                         SHA2_.update(Salt+NewPassword+UInfo.email); |  | ||||||
|                         if(OldTokens[1]==Utils::ToHex(SHA2_.digest())) |  | ||||||
|                 return false; |                 return false; | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|             } else { |  | ||||||
|                 SHA2_.update(NewPassword+UInfo.email); |  | ||||||
|                 if(Tokens[0]==Utils::ToHex(SHA2_.digest())) |  | ||||||
|                     return false; |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         if(UInfo.lastPasswords.size()==HowManyOldPassword_) { |         if(UInfo.lastPasswords.size()==HowManyOldPassword_) { | ||||||
|             UInfo.lastPasswords.erase(UInfo.lastPasswords.begin()); |             UInfo.lastPasswords.erase(UInfo.lastPasswords.begin()); | ||||||
|         } |         } | ||||||
|  |         UInfo.lastPasswords.push_back(NewPasswordHash); | ||||||
|         auto NewHash = ComputeNewPasswordHash(UInfo.email,NewPassword); |         UInfo.currentPassword = NewPasswordHash; | ||||||
|         UInfo.lastPasswords.push_back(NewHash); |  | ||||||
|         UInfo.currentPassword = NewHash; |  | ||||||
|         UInfo.changePassword = false; |         UInfo.changePassword = false; | ||||||
|         return true; |         return true; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     bool AuthService::SetSubPassword(const std::string &NewPassword, SecurityObjects::UserInfo & UInfo) { |     AuthService::AUTH_ERROR AuthService::Authorize( std::string & UserName, const std::string & Password, const std::string & NewPassword, SecurityObjects::UserInfoAndPolicy & UInfo ) | ||||||
|         std::lock_guard     G(Mutex_); |  | ||||||
|  |  | ||||||
|         Poco::toLowerInPlace(UInfo.email); |  | ||||||
|         for (const auto &i:UInfo.lastPasswords) { |  | ||||||
|             auto Tokens = Poco::StringTokenizer(i,"|"); |  | ||||||
|             if(Tokens.count()==2) { |  | ||||||
|                 const auto & Salt = Tokens[0]; |  | ||||||
|                 for(const auto &j:UInfo.lastPasswords) { |  | ||||||
|                     auto OldTokens = Poco::StringTokenizer(j,"|"); |  | ||||||
|                     if(OldTokens.count()==2) { |  | ||||||
|                         SHA2_.update(Salt+NewPassword+UInfo.email); |  | ||||||
|                         if(OldTokens[1]==Utils::ToHex(SHA2_.digest())) |  | ||||||
|                             return false; |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|             } else { |  | ||||||
|                 SHA2_.update(NewPassword+UInfo.email); |  | ||||||
|                 if(Tokens[0]==Utils::ToHex(SHA2_.digest())) |  | ||||||
|                     return false; |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         if(UInfo.lastPasswords.size()==HowManyOldPassword_) { |  | ||||||
|             UInfo.lastPasswords.erase(UInfo.lastPasswords.begin()); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         auto NewHash = ComputeNewPasswordHash(UInfo.email,NewPassword); |  | ||||||
|         UInfo.lastPasswords.push_back(NewHash); |  | ||||||
|         UInfo.currentPassword = NewHash; |  | ||||||
|         UInfo.changePassword = false; |  | ||||||
|         return true; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     static std::string GetMeSomeSalt() { |  | ||||||
|         auto start = std::chrono::high_resolution_clock::now(); |  | ||||||
|         return std::to_string(start.time_since_epoch().count()); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     std::string AuthService::ComputeNewPasswordHash(const std::string &UserName, const std::string &Password) { |  | ||||||
|         std::string UName = Poco::trim(Poco::toLower(UserName)); |  | ||||||
|         auto Salt = GetMeSomeSalt(); |  | ||||||
|         SHA2_.update(Salt + Password + UName ); |  | ||||||
|         return Salt + "|" + Utils::ToHex(SHA2_.digest()); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     bool AuthService::ValidatePasswordHash(const std::string & UserName, const std::string & Password, const std::string &StoredPassword) { |  | ||||||
|         std::lock_guard G(Mutex_); |  | ||||||
|  |  | ||||||
|         std::string UName = Poco::trim(Poco::toLower(UserName)); |  | ||||||
|         auto Tokens = Poco::StringTokenizer(StoredPassword,"|"); |  | ||||||
|         if(Tokens.count()==1) { |  | ||||||
|             SHA2_.update(Password+UName); |  | ||||||
|             if(Tokens[0]==Utils::ToHex(SHA2_.digest())) |  | ||||||
|                 return true; |  | ||||||
|         } else if (Tokens.count()==2) { |  | ||||||
|             SHA2_.update(Tokens[0]+Password+UName); |  | ||||||
|             if(Tokens[1]==Utils::ToHex(SHA2_.digest())) |  | ||||||
|                 return true; |  | ||||||
|         } |  | ||||||
|         return false; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     bool AuthService::ValidateSubPasswordHash(const std::string & UserName, const std::string & Password, const std::string &StoredPassword) { |  | ||||||
|         std::lock_guard G(Mutex_); |  | ||||||
|  |  | ||||||
|         std::string UName = Poco::trim(Poco::toLower(UserName)); |  | ||||||
|         auto Tokens = Poco::StringTokenizer(StoredPassword,"|"); |  | ||||||
|         if(Tokens.count()==1) { |  | ||||||
|             SHA2_.update(Password+UName); |  | ||||||
|             if(Tokens[0]==Utils::ToHex(SHA2_.digest())) |  | ||||||
|                 return true; |  | ||||||
|         } else if (Tokens.count()==2) { |  | ||||||
|             SHA2_.update(Tokens[0]+Password+UName); |  | ||||||
|             if(Tokens[1]==Utils::ToHex(SHA2_.digest())) |  | ||||||
|                 return true; |  | ||||||
|         } |  | ||||||
|         return false; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     UNAUTHORIZED_REASON AuthService::Authorize( std::string & UserName, const std::string & Password, const std::string & NewPassword, SecurityObjects::UserInfoAndPolicy & UInfo , [[maybe_unused]] bool & Expired ) |  | ||||||
|     { |     { | ||||||
|         std::lock_guard		Guard(Mutex_); |         std::lock_guard		Guard(Mutex_); | ||||||
|  |         SecurityObjects::AclTemplate	ACL; | ||||||
|  |  | ||||||
|         Poco::toLowerInPlace(UserName); |         Poco::toLowerInPlace(UserName); | ||||||
|  |         auto PasswordHash = ComputePasswordHash(UserName, Password); | ||||||
|  |  | ||||||
|         if(StorageService()->UserDB().GetUserByEmail(UserName,UInfo.userinfo)) { |         if(Storage()->GetUserByEmail(UserName,UInfo.userinfo)) { | ||||||
|             if(UInfo.userinfo.waitingForEmailCheck) { |             if(UInfo.userinfo.waitingForEmailCheck) { | ||||||
|                 return USERNAME_PENDING_VERIFICATION; |                 return USERNAME_PENDING_VERIFICATION; | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             if(!ValidatePasswordHash(UserName,Password,UInfo.userinfo.currentPassword)) { |             if(PasswordHash != UInfo.userinfo.currentPassword) { | ||||||
|                 return INVALID_CREDENTIALS; |                 return INVALID_CREDENTIALS; | ||||||
|             } |             } | ||||||
|  |  | ||||||
| @@ -501,91 +253,62 @@ namespace OpenWifi { | |||||||
|                     UInfo.webtoken.errorCode = 1; |                     UInfo.webtoken.errorCode = 1; | ||||||
|                     return PASSWORD_ALREADY_USED; |                     return PASSWORD_ALREADY_USED; | ||||||
|                 } |                 } | ||||||
|                 UInfo.userinfo.lastPasswordChange = OpenWifi::Now(); |                 UInfo.userinfo.lastPasswordChange = std::time(nullptr); | ||||||
|                 UInfo.userinfo.changePassword = false; |                 UInfo.userinfo.changePassword = false; | ||||||
|                 UInfo.userinfo.modified = OpenWifi::Now(); |                 Storage()->UpdateUserInfo(AUTHENTICATION_SYSTEM, UInfo.userinfo.Id,UInfo.userinfo); | ||||||
|                 StorageService()->UserDB().UpdateUserInfo(AUTHENTICATION_SYSTEM, UInfo.userinfo.id,UInfo.userinfo); |  | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             //  so we have a good password, password up date has taken place if need be, now generate the token. |             //  so we have a good password, password up date has taken place if need be, now generate the token. | ||||||
|             UInfo.userinfo.lastLogin=OpenWifi::Now(); |             UInfo.userinfo.lastLogin=std::time(nullptr); | ||||||
|             StorageService()->UserDB().SetLastLogin(UInfo.userinfo.id); |             Storage()->SetLastLogin(UInfo.userinfo.Id); | ||||||
|             CreateToken(UserName, UInfo ); |             CreateToken(UserName, UInfo ); | ||||||
|  |  | ||||||
|             return SUCCESS; |             return SUCCESS; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         return INVALID_CREDENTIALS; |         if(((UserName == DefaultUserName_) && (DefaultPassword_== ComputePasswordHash(UserName,Password))) || !Secure_) | ||||||
|     } |  | ||||||
|  |  | ||||||
|     UNAUTHORIZED_REASON AuthService::AuthorizeSub( std::string & UserName, const std::string & Password, const std::string & NewPassword, SecurityObjects::UserInfoAndPolicy & UInfo , [[maybe_unused]] bool & Expired ) |  | ||||||
|         { |         { | ||||||
|         std::lock_guard		Guard(Mutex_); |             ACL.PortalLogin_ = ACL.Read_ = ACL.ReadWrite_ = ACL.ReadWriteCreate_ = ACL.Delete_ = true; | ||||||
|  |             UInfo.webtoken.acl_template_ = ACL; | ||||||
|         Poco::toLowerInPlace(UserName); |             UInfo.userinfo.email = DefaultUserName_; | ||||||
|  |             UInfo.userinfo.currentPassword = DefaultPassword_; | ||||||
|         if(StorageService()->SubDB().GetUserByEmail(UserName,UInfo.userinfo)) { |             UInfo.userinfo.name = DefaultUserName_; | ||||||
|             if(UInfo.userinfo.waitingForEmailCheck) { |             CreateToken(UserName, UInfo ); | ||||||
|                 return USERNAME_PENDING_VERIFICATION; |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             if(!ValidateSubPasswordHash(UserName,Password,UInfo.userinfo.currentPassword)) { |  | ||||||
|                 return INVALID_CREDENTIALS; |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             if(UInfo.userinfo.changePassword && NewPassword.empty()) { |  | ||||||
|                 UInfo.webtoken.userMustChangePassword = true ; |  | ||||||
|                 return PASSWORD_CHANGE_REQUIRED; |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             if(!NewPassword.empty() && !ValidateSubPassword(NewPassword)) { |  | ||||||
|                 return PASSWORD_INVALID; |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             if(UInfo.userinfo.changePassword || !NewPassword.empty()) { |  | ||||||
|                 if(!SetSubPassword(NewPassword,UInfo.userinfo)) { |  | ||||||
|                     UInfo.webtoken.errorCode = 1; |  | ||||||
|                     return PASSWORD_ALREADY_USED; |  | ||||||
|                 } |  | ||||||
|                 UInfo.userinfo.lastPasswordChange = OpenWifi::Now(); |  | ||||||
|                 UInfo.userinfo.changePassword = false; |  | ||||||
|                 UInfo.userinfo.modified = OpenWifi::Now(); |  | ||||||
|                 StorageService()->SubDB().UpdateUserInfo(AUTHENTICATION_SYSTEM, UInfo.userinfo.id,UInfo.userinfo); |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             //  so we have a good password, password update has taken place if need be, now generate the token. |  | ||||||
|             UInfo.userinfo.lastLogin=OpenWifi::Now(); |  | ||||||
|             StorageService()->SubDB().SetLastLogin(UInfo.userinfo.id); |  | ||||||
|             CreateSubToken(UserName, UInfo ); |  | ||||||
|  |  | ||||||
|             return SUCCESS; |             return SUCCESS; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         return INVALID_CREDENTIALS; |         return INVALID_CREDENTIALS; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     bool AuthService::SendEmailToUser(const std::string &LinkId, std::string &Email, EMAIL_REASON Reason) { |     std::string AuthService::ComputePasswordHash(const std::string &UserName, const std::string &Password) { | ||||||
|  |         std::string UName = Poco::trim(Poco::toLower(UserName)); | ||||||
|  |         SHA2_.update(Password + UName); | ||||||
|  |         return Utils::ToHex(SHA2_.digest()); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     bool AuthService::SendEmailToUser(std::string &Email, EMAIL_REASON Reason) { | ||||||
|         SecurityObjects::UserInfo   UInfo; |         SecurityObjects::UserInfo   UInfo; | ||||||
|  |  | ||||||
|         if(StorageService()->UserDB().GetUserByEmail(Email,UInfo)) { |         if(Storage()->GetUserByEmail(Email,UInfo)) { | ||||||
|             switch (Reason) { |             switch (Reason) { | ||||||
|  |  | ||||||
|                 case FORGOT_PASSWORD: { |                 case FORGOT_PASSWORD: { | ||||||
|                         MessageAttributes Attrs; |                         MessageAttributes Attrs; | ||||||
|  |  | ||||||
|                         Attrs[RECIPIENT_EMAIL] = UInfo.email; |                         Attrs[RECIPIENT_EMAIL] = UInfo.email; | ||||||
|                         Attrs[LOGO] = GetLogoAssetURI(); |                         Attrs[LOGO] = "logo.jpg"; | ||||||
|                         Attrs[SUBJECT] = "Password reset link"; |                         Attrs[SUBJECT] = "Password reset link"; | ||||||
|                         Attrs[ACTION_LINK] = MicroService::instance().GetPublicAPIEndPoint() + "/actionLink?action=password_reset&id=" + LinkId ; |                         Attrs[ACTION_LINK] = | ||||||
|  |                                 Daemon()->GetPublicAPIEndPoint() + "/actionLink?action=password_reset&id=" + UInfo.Id ; | ||||||
|                         SMTPMailerService()->SendMessage(UInfo.email, "password_reset.txt", Attrs); |                         SMTPMailerService()->SendMessage(UInfo.email, "password_reset.txt", Attrs); | ||||||
|                     } |                     } | ||||||
|                     break; |                     break; | ||||||
|  |  | ||||||
|                 case EMAIL_VERIFICATION: { |                 case EMAIL_VERIFICATION: { | ||||||
|                         MessageAttributes Attrs; |                         MessageAttributes Attrs; | ||||||
|  |  | ||||||
|                         Attrs[RECIPIENT_EMAIL] = UInfo.email; |                         Attrs[RECIPIENT_EMAIL] = UInfo.email; | ||||||
|                         Attrs[LOGO] = GetLogoAssetURI(); |                         Attrs[LOGO] = "logo.jpg"; | ||||||
|                         Attrs[SUBJECT] = "e-mail Address Verification"; |                         Attrs[SUBJECT] = "EMail Address Verification"; | ||||||
|                         Attrs[ACTION_LINK] = MicroService::instance().GetPublicAPIEndPoint() + "/actionLink?action=email_verification&id=" + LinkId ; |                         Attrs[ACTION_LINK] = | ||||||
|  |                                 Daemon()->GetPublicAPIEndPoint() + "/actionLink?action=email_verification&id=" + UInfo.Id ; | ||||||
|                         SMTPMailerService()->SendMessage(UInfo.email, "email_verification.txt", Attrs); |                         SMTPMailerService()->SendMessage(UInfo.email, "email_verification.txt", Attrs); | ||||||
|                         UInfo.waitingForEmailCheck = true; |                         UInfo.waitingForEmailCheck = true; | ||||||
|                     } |                     } | ||||||
| @@ -594,126 +317,33 @@ namespace OpenWifi { | |||||||
|                 default: |                 default: | ||||||
|                     break; |                     break; | ||||||
|             } |             } | ||||||
|             return true; |  | ||||||
|         } |  | ||||||
|         return false; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     bool AuthService::SendEmailToSubUser(const std::string &LinkId, std::string &Email, EMAIL_REASON Reason) { |  | ||||||
|         SecurityObjects::UserInfo   UInfo; |  | ||||||
|  |  | ||||||
|         if(StorageService()->SubDB().GetUserByEmail(Email,UInfo)) { |  | ||||||
|             switch (Reason) { |  | ||||||
|  |  | ||||||
|                 case FORGOT_PASSWORD: { |  | ||||||
|                     MessageAttributes Attrs; |  | ||||||
|                     Attrs[RECIPIENT_EMAIL] = UInfo.email; |  | ||||||
|                     Attrs[LOGO] = GetLogoAssetURI(); |  | ||||||
|                     Attrs[SUBJECT] = "Password reset link"; |  | ||||||
|                     Attrs[ACTION_LINK] = MicroService::instance().GetPublicAPIEndPoint() + "/actionLink?action=password_reset&id=" + LinkId ; |  | ||||||
|                     SMTPMailerService()->SendMessage(UInfo.email, "password_reset.txt", Attrs); |  | ||||||
|                 } |  | ||||||
|                 break; |  | ||||||
|  |  | ||||||
|                 case EMAIL_VERIFICATION: { |  | ||||||
|                     MessageAttributes Attrs; |  | ||||||
|                     Attrs[RECIPIENT_EMAIL] = UInfo.email; |  | ||||||
|                     Attrs[LOGO] = GetLogoAssetURI(); |  | ||||||
|                     Attrs[SUBJECT] = "e-mail Address Verification"; |  | ||||||
|                     Attrs[ACTION_LINK] = MicroService::instance().GetPublicAPIEndPoint() + "/actionLink?action=email_verification&id=" + LinkId ; |  | ||||||
|                     SMTPMailerService()->SendMessage(UInfo.email, "email_verification.txt", Attrs); |  | ||||||
|                     UInfo.waitingForEmailCheck = true; |  | ||||||
|                 } |  | ||||||
|                 break; |  | ||||||
|  |  | ||||||
|                 case SIGNUP_VERIFICATION: { |  | ||||||
|                     MessageAttributes Attrs; |  | ||||||
|                     Attrs[RECIPIENT_EMAIL] = UInfo.email; |  | ||||||
|                     Attrs[LOGO] = GetLogoAssetURI(); |  | ||||||
|                     Attrs[SUBJECT] = "Signup e-mail Address Verification"; |  | ||||||
|                     Attrs[ACTION_LINK] = MicroService::instance().GetPublicAPIEndPoint() + "/actionLink?action=signup_verification&id=" + LinkId ; |  | ||||||
|                     SMTPMailerService()->SendMessage(UInfo.email, "signup_verification.txt", Attrs); |  | ||||||
|                     UInfo.waitingForEmailCheck = true; |  | ||||||
|                 } |  | ||||||
|                 break; |  | ||||||
|  |  | ||||||
|                 default: |  | ||||||
|                     break; |  | ||||||
|             } |  | ||||||
|             return true; |  | ||||||
|         } |         } | ||||||
|         return false; |         return false; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     bool AuthService::VerifyEmail(SecurityObjects::UserInfo &UInfo) { |     bool AuthService::VerifyEmail(SecurityObjects::UserInfo &UInfo) { | ||||||
|         SecurityObjects::ActionLink A; |         MessageAttributes Attrs; | ||||||
|  |  | ||||||
|         A.action = OpenWifi::SecurityObjects::LinkActions::VERIFY_EMAIL; |         Attrs[RECIPIENT_EMAIL] = UInfo.email; | ||||||
|         A.userId = UInfo.id; |         Attrs[LOGO] = "logo.jpg"; | ||||||
|         A.id = MicroService::CreateUUID(); |         Attrs[SUBJECT] = "EMail Address Verification"; | ||||||
|         A.created = OpenWifi::Now(); |         Attrs[ACTION_LINK] = | ||||||
|         A.expires = A.created + 24*60*60; |                 Daemon()->GetPublicAPIEndPoint() + "/actionLink?action=email_verification&id=" + UInfo.Id ; | ||||||
|         A.userAction = true; |         SMTPMailerService()->SendMessage(UInfo.email, "email_verification.txt", Attrs); | ||||||
|         StorageService()->ActionLinksDB().CreateAction(A); |  | ||||||
|         UInfo.waitingForEmailCheck = true; |         UInfo.waitingForEmailCheck = true; | ||||||
|         UInfo.validated = false; |  | ||||||
|         return true; |         return true; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     bool AuthService::VerifySubEmail(SecurityObjects::UserInfo &UInfo) { |     bool AuthService::IsValidToken(const std::string &Token, SecurityObjects::WebToken &WebToken, SecurityObjects::UserInfo &UserInfo) { | ||||||
|         SecurityObjects::ActionLink A; |  | ||||||
|  |  | ||||||
|         A.action = OpenWifi::SecurityObjects::LinkActions::SUB_VERIFY_EMAIL; |  | ||||||
|         A.userId = UInfo.id; |  | ||||||
|         A.id = MicroService::CreateUUID(); |  | ||||||
|         A.created = OpenWifi::Now(); |  | ||||||
|         A.expires = A.created + 24*60*60; |  | ||||||
|         A.userAction = false; |  | ||||||
|         StorageService()->ActionLinksDB().CreateAction(A); |  | ||||||
|         UInfo.waitingForEmailCheck = true; |  | ||||||
|         UInfo.validated = false; |  | ||||||
|         return true; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     bool AuthService::IsValidToken(const std::string &Token, SecurityObjects::WebToken &WebToken, SecurityObjects::UserInfo &UserInfo, bool & Expired) { |  | ||||||
|  |  | ||||||
|         std::lock_guard G(Mutex_); |         std::lock_guard G(Mutex_); | ||||||
|         Expired = false; |         auto It = UserCache_.find(Token); | ||||||
|  |  | ||||||
|         std::string TToken{Token}, UserId; |         if(It==UserCache_.end()) | ||||||
|         SecurityObjects::WebToken   WT; |  | ||||||
|         uint64_t RevocationDate=0; |  | ||||||
|         if(StorageService()->UserTokenDB().GetToken(TToken, WT, UserId, RevocationDate)) { |  | ||||||
|             if(RevocationDate!=0) |  | ||||||
|             return false; |             return false; | ||||||
|             Expired = (WT.created_ + WT.expires_in_) < OpenWifi::Now(); |         WebToken = It->second.webtoken; | ||||||
|             if(StorageService()->UserDB().GetUserById(UserId,UserInfo)) { |         UserInfo = It->second.userinfo; | ||||||
|                 WebToken = WT; |  | ||||||
|         return true; |         return true; | ||||||
|     } |     } | ||||||
|             return false; |  | ||||||
|         } |  | ||||||
|         return IsValidSubToken(Token, WebToken, UserInfo, Expired); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     bool AuthService::IsValidSubToken(const std::string &Token, SecurityObjects::WebToken &WebToken, SecurityObjects::UserInfo &UserInfo, bool & Expired) { |  | ||||||
|         std::lock_guard G(Mutex_); |  | ||||||
|         Expired = false; |  | ||||||
|  |  | ||||||
|         std::string TToken{Token}, UserId; |  | ||||||
|         SecurityObjects::WebToken   WT; |  | ||||||
|         uint64_t RevocationDate=0; |  | ||||||
|         if(StorageService()->SubTokenDB().GetToken(TToken, WT, UserId, RevocationDate)) { |  | ||||||
|             if(RevocationDate!=0) |  | ||||||
|                 return false; |  | ||||||
|             Expired = (WT.created_ + WT.expires_in_) < OpenWifi::Now(); |  | ||||||
|             if(StorageService()->SubDB().GetUserById(UserId,UserInfo)) { |  | ||||||
|                 WebToken = WT; |  | ||||||
|                 return true; |  | ||||||
|             } |  | ||||||
|             return false; |  | ||||||
|         } |  | ||||||
|         return false; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
| }  // end of namespace | }  // end of namespace | ||||||
|   | |||||||
| @@ -11,17 +11,15 @@ | |||||||
|  |  | ||||||
| #include <regex> | #include <regex> | ||||||
|  |  | ||||||
|  | #include "SubSystemServer.h" | ||||||
|  |  | ||||||
| #include "Poco/JSON/Object.h" | #include "Poco/JSON/Object.h" | ||||||
| #include "Poco/Net/HTTPServerRequest.h" | #include "Poco/Net/HTTPServerRequest.h" | ||||||
| #include "Poco/Net/HTTPServerResponse.h" | #include "Poco/Net/HTTPServerResponse.h" | ||||||
| #include "Poco/JWT/Signer.h" | #include "Poco/JWT/Signer.h" | ||||||
| #include "Poco/SHA2Engine.h" | #include "Poco/SHA2Engine.h" | ||||||
| #include "Poco/Crypto/DigestEngine.h" |  | ||||||
| #include "Poco/HMACEngine.h" |  | ||||||
| #include "Poco/ExpireLRUCache.h" |  | ||||||
|  |  | ||||||
| #include "framework/MicroService.h" | #include "RESTAPI_SecurityObjects.h" | ||||||
| #include "RESTObjects/RESTAPI_SecurityObjects.h" |  | ||||||
|  |  | ||||||
| namespace OpenWifi{ | namespace OpenWifi{ | ||||||
|  |  | ||||||
| @@ -36,134 +34,76 @@ namespace OpenWifi{ | |||||||
|             CUSTOM |             CUSTOM | ||||||
|         }; |         }; | ||||||
|  |  | ||||||
|  |         enum AUTH_ERROR { | ||||||
|  |             SUCCESS, | ||||||
|  |             PASSWORD_CHANGE_REQUIRED, | ||||||
|  |             INVALID_CREDENTIALS, | ||||||
|  |             PASSWORD_ALREADY_USED, | ||||||
|  |             USERNAME_PENDING_VERIFICATION, | ||||||
|  |             PASSWORD_INVALID, | ||||||
|  |             INTERNAL_ERROR | ||||||
|  |         }; | ||||||
|  |  | ||||||
|         enum EMAIL_REASON { |         enum EMAIL_REASON { | ||||||
|             FORGOT_PASSWORD, |             FORGOT_PASSWORD, | ||||||
|             EMAIL_VERIFICATION, |             EMAIL_VERIFICATION | ||||||
|             SIGNUP_VERIFICATION |  | ||||||
|         }; |         }; | ||||||
|  |  | ||||||
|         static ACCESS_TYPE IntToAccessType(int C); |         static ACCESS_TYPE IntToAccessType(int C); | ||||||
|         static int AccessTypeToInt(ACCESS_TYPE T); |         static int AccessTypeToInt(ACCESS_TYPE T); | ||||||
|  |  | ||||||
|         static auto instance() { |         static AuthService *instance() { | ||||||
|             static auto instance_ = new AuthService; |             if (instance_ == nullptr) { | ||||||
|  |                 instance_ = new AuthService; | ||||||
|  |             } | ||||||
|             return instance_; |             return instance_; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         int Start() override; |         int Start() override; | ||||||
|         void Stop() override; |         void Stop() override; | ||||||
|  |  | ||||||
|         [[nodiscard]] bool IsAuthorized(Poco::Net::HTTPServerRequest & Request,std::string &SessionToken, SecurityObjects::UserInfoAndPolicy & UInfo, bool & Expired); |         [[nodiscard]] bool IsAuthorized(Poco::Net::HTTPServerRequest & Request,std::string &SessionToken, SecurityObjects::UserInfoAndPolicy & UInfo ); | ||||||
|         [[nodiscard]] UNAUTHORIZED_REASON Authorize( std::string & UserName, const std::string & Password, const std::string & NewPassword, SecurityObjects::UserInfoAndPolicy & UInfo, bool & Expired ); |         [[nodiscard]] AUTH_ERROR Authorize( std::string & UserName, const std::string & Password, const std::string & NewPassword, SecurityObjects::UserInfoAndPolicy & UInfo ); | ||||||
|         void CreateToken(const std::string & UserName, SecurityObjects::UserInfoAndPolicy &UInfo); |         void CreateToken(const std::string & UserName, SecurityObjects::UserInfoAndPolicy &UInfo); | ||||||
|  |         [[nodiscard]] bool ValidateToken(const std::string & Token, std::string & SessionToken, SecurityObjects::UserInfoAndPolicy & UserInfo  ); | ||||||
|         [[nodiscard]] bool SetPassword(const std::string &Password, SecurityObjects::UserInfo & UInfo); |         [[nodiscard]] bool SetPassword(const std::string &Password, SecurityObjects::UserInfo & UInfo); | ||||||
|         [[nodiscard]] const std:: string & PasswordValidationExpression() const { return PasswordValidationStr_;}; |         [[nodiscard]] const std:: string & PasswordValidationExpression() const { return PasswordValidationStr_;}; | ||||||
|         void Logout(const std::string &token, bool EraseFromCache=true); |         void Logout(const std::string &token); | ||||||
|  |  | ||||||
|         [[nodiscard]] bool IsSubAuthorized(Poco::Net::HTTPServerRequest & Request,std::string &SessionToken, SecurityObjects::UserInfoAndPolicy & UInfo, bool & Expired); |  | ||||||
|         [[nodiscard]] UNAUTHORIZED_REASON AuthorizeSub( std::string & UserName, const std::string & Password, const std::string & NewPassword, SecurityObjects::UserInfoAndPolicy & UInfo, bool & Expired ); |  | ||||||
|         void CreateSubToken(const std::string & UserName, SecurityObjects::UserInfoAndPolicy &UInfo); |  | ||||||
|         [[nodiscard]] bool SetSubPassword(const std::string &Password, SecurityObjects::UserInfo & UInfo); |  | ||||||
|         [[nodiscard]] const std:: string & SubPasswordValidationExpression() const { return PasswordValidationStr_;}; |  | ||||||
|         void SubLogout(const std::string &token, bool EraseFromCache=true); |  | ||||||
|  |  | ||||||
|         void RemoveTokenSystemWide(const std::string &token); |  | ||||||
|  |  | ||||||
|         bool ValidatePassword(const std::string &pwd); |         bool ValidatePassword(const std::string &pwd); | ||||||
|         bool ValidateSubPassword(const std::string &pwd); |  | ||||||
|  |  | ||||||
|         [[nodiscard]] bool IsValidToken(const std::string &Token, SecurityObjects::WebToken &WebToken, SecurityObjects::UserInfo &UserInfo, bool & Expired); |  | ||||||
|         [[nodiscard]] bool IsValidSubToken(const std::string &Token, SecurityObjects::WebToken &WebToken, SecurityObjects::UserInfo &UserInfo, bool & Expired); |  | ||||||
|         [[nodiscard]] std::string GenerateTokenJWT(const std::string & UserName, ACCESS_TYPE Type); |  | ||||||
|         [[nodiscard]] std::string GenerateTokenHMAC(const std::string & UserName, ACCESS_TYPE Type); |  | ||||||
|  |  | ||||||
|         [[nodiscard]] std::string ComputeNewPasswordHash(const std::string &UserName, const std::string &Password); |  | ||||||
|         [[nodiscard]] bool ValidatePasswordHash(const std::string & UserName, const std::string & Password, const std::string &StoredPassword); |  | ||||||
|         [[nodiscard]] bool ValidateSubPasswordHash(const std::string & UserName, const std::string & Password, const std::string &StoredPassword); |  | ||||||
|  |  | ||||||
|  |         [[nodiscard]] bool IsValidToken(const std::string &Token, SecurityObjects::WebToken &WebToken, SecurityObjects::UserInfo &UserInfo); | ||||||
|  |         [[nodiscard]] bool IsValidAPIKEY(const Poco::Net::HTTPServerRequest &Request); | ||||||
|  |         [[nodiscard]] std::string GenerateToken(const std::string & UserName, ACCESS_TYPE Type); | ||||||
|  |         [[nodiscard]] bool ValidateToken(const std::string & Token, std::string & SessionToken, SecurityObjects::WebToken & UserInfo  ); | ||||||
|  |         [[nodiscard]] std::string ComputePasswordHash(const std::string &UserName, const std::string &Password); | ||||||
|         [[nodiscard]] bool UpdatePassword(const std::string &Admin, const std::string &UserName, const std::string & OldPassword, const std::string &NewPassword); |         [[nodiscard]] bool UpdatePassword(const std::string &Admin, const std::string &UserName, const std::string & OldPassword, const std::string &NewPassword); | ||||||
|         [[nodiscard]] std::string ResetPassword(const std::string &Admin, const std::string &UserName); |         [[nodiscard]] std::string ResetPassword(const std::string &Admin, const std::string &UserName); | ||||||
|  |  | ||||||
|         [[nodiscard]] bool UpdateSubPassword(const std::string &Admin, const std::string &UserName, const std::string & OldPassword, const std::string &NewPassword); |  | ||||||
|         [[nodiscard]] std::string ResetSubPassword(const std::string &Admin, const std::string &UserName); |  | ||||||
|  |  | ||||||
|         [[nodiscard]] static bool VerifyEmail(SecurityObjects::UserInfo &UInfo); |         [[nodiscard]] static bool VerifyEmail(SecurityObjects::UserInfo &UInfo); | ||||||
|         [[nodiscard]] static bool VerifySubEmail(SecurityObjects::UserInfo &UInfo); |         [[nodiscard]] static bool SendEmailToUser(std::string &Email, EMAIL_REASON Reason); | ||||||
|  |         [[nodiscard]] bool DeleteUserFromCache(const std::string &UserName); | ||||||
|         [[nodiscard]] static bool SendEmailToUser(const std::string &LinkId, std::string &Email, EMAIL_REASON Reason); |  | ||||||
|         [[nodiscard]] static bool SendEmailToSubUser(const std::string &LinkId, std::string &Email, EMAIL_REASON Reason); |  | ||||||
|         [[nodiscard]] bool RequiresMFA(const SecurityObjects::UserInfoAndPolicy &UInfo); |  | ||||||
|  |  | ||||||
|         bool DeleteUserFromCache(const std::string &UserName); |  | ||||||
|         bool DeleteSubUserFromCache(const std::string &UserName); |  | ||||||
|         void RevokeToken(std::string & Token); |  | ||||||
|         void RevokeSubToken(std::string & Token); |  | ||||||
|  |  | ||||||
|         [[nodiscard]] static inline const std::string GetLogoAssetURI() { |  | ||||||
|             return MicroService::instance().PublicEndPoint() + "/wwwassets/the_logo.png"; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         [[nodiscard]] static inline const std::string GetLogoAssetFileName() { |  | ||||||
|             return MicroService::instance().WWWAssetsDir() + "/the_logo.png"; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         inline const std::string & GetPasswordPolicy() const { return PasswordPolicy_; } |  | ||||||
|         inline const std::string & GetAccessPolicy() const { return AccessPolicy_; } |  | ||||||
|  |  | ||||||
|         inline const std::string & GetSubPasswordPolicy() const { return SubPasswordPolicy_; } |  | ||||||
|         inline const std::string & GetSubAccessPolicy() const { return SubAccessPolicy_; } |  | ||||||
|  |  | ||||||
|         bool RefreshUserToken(Poco::Net::HTTPServerRequest & Request, const std::string & RefreshToken, SecurityObjects::UserInfoAndPolicy & UI); |  | ||||||
|         bool RefreshSubToken(Poco::Net::HTTPServerRequest & Request, const std::string & RefreshToken, SecurityObjects::UserInfoAndPolicy & UI); |  | ||||||
|  |  | ||||||
|     private: |     private: | ||||||
|  | 		static AuthService *instance_; | ||||||
|  | 		bool    			Secure_ = false ; | ||||||
|  | 		std::string     	DefaultUserName_; | ||||||
|  | 		std::string			DefaultPassword_; | ||||||
|  | 		std::string     	Mechanism_; | ||||||
|  | 		Poco::JWT::Signer	Signer_; | ||||||
| 		Poco::SHA2Engine	SHA2_; | 		Poco::SHA2Engine	SHA2_; | ||||||
|  | 		SecurityObjects::UserInfoCache UserCache_; | ||||||
| 		std::string         AccessPolicy_; |  | ||||||
| 		std::string         PasswordPolicy_; |  | ||||||
| 		std::string         SubAccessPolicy_; |  | ||||||
| 		std::string         SubPasswordPolicy_; |  | ||||||
|         std::string          PasswordValidationStr_; |         std::string          PasswordValidationStr_; | ||||||
|         std::string         SubPasswordValidationStr_; |  | ||||||
| 		std::regex          PasswordValidation_; | 		std::regex          PasswordValidation_; | ||||||
|         std::regex          SubPasswordValidation_; | 		uint64_t            TokenAging_ = 30 * 24 * 60 * 60; | ||||||
|  |  | ||||||
|         uint64_t            TokenAging_ = 15 * 24 * 60 * 60; |  | ||||||
|         uint64_t            HowManyOldPassword_=5; |         uint64_t            HowManyOldPassword_=5; | ||||||
|         uint64_t            RefreshTokenLifeSpan_ = 90 * 24 * 60 * 60 ; |  | ||||||
|  |  | ||||||
|         class SHA256Engine : public Poco::Crypto::DigestEngine |  | ||||||
|                 { |  | ||||||
|                 public: |  | ||||||
|                     enum |  | ||||||
|                     { |  | ||||||
|                         BLOCK_SIZE = 64, |  | ||||||
|                         DIGEST_SIZE = 32 |  | ||||||
|                     }; |  | ||||||
|  |  | ||||||
|                     SHA256Engine() |  | ||||||
|                     : DigestEngine("SHA256") |  | ||||||
|                     { |  | ||||||
|                     } |  | ||||||
|  |  | ||||||
|                 }; |  | ||||||
|  |  | ||||||
|         Poco::HMACEngine<SHA256Engine> HMAC_{"tipopenwifi"}; |  | ||||||
|  |  | ||||||
|         AuthService() noexcept: |         AuthService() noexcept: | ||||||
|             SubSystemServer("Authentication", "AUTH-SVR", "authentication") |             SubSystemServer("Authentication", "AUTH-SVR", "authentication") | ||||||
|         { |         { | ||||||
|         } |         } | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
|     inline auto AuthService() { return AuthService::instance(); } |     inline AuthService * AuthService() { return AuthService::instance(); } | ||||||
|  |  | ||||||
|     [[nodiscard]] inline bool AuthServiceIsAuthorized(Poco::Net::HTTPServerRequest & Request,std::string &SessionToken, SecurityObjects::UserInfoAndPolicy & UInfo , bool & Expired, bool Sub ) { |  | ||||||
|         if(Sub) |  | ||||||
|             return AuthService()->IsSubAuthorized(Request, SessionToken, UInfo, Expired ); |  | ||||||
|         else |  | ||||||
|             return AuthService()->IsAuthorized(Request, SessionToken, UInfo, Expired ); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
| } // end of namespace | } // end of namespace | ||||||
|  |  | ||||||
|   | |||||||
| @@ -10,21 +10,22 @@ | |||||||
| //	Arilia Wireless Inc. | //	Arilia Wireless Inc. | ||||||
| // | // | ||||||
|  |  | ||||||
|  | #include <cstdlib> | ||||||
|  | #include <boost/algorithm/string.hpp> | ||||||
|  |  | ||||||
| #include "Poco/Util/Application.h" | #include "Poco/Util/Application.h" | ||||||
| #include "Poco/Util/Option.h" | #include "Poco/Util/Option.h" | ||||||
| #include "Poco/Environment.h" | #include "Poco/Environment.h" | ||||||
|  |  | ||||||
| #include "Daemon.h" | #include "Daemon.h" | ||||||
|  |  | ||||||
| #include <aws/core/Aws.h> | #include "ALBHealthCheckServer.h" | ||||||
| #include <aws/s3/model/AccessControlPolicy.h> | #include "KafkaManager.h" | ||||||
|  |  | ||||||
| #include "StorageService.h" | #include "StorageService.h" | ||||||
|  | #include "RESTAPI_server.h" | ||||||
| #include "SMTPMailerService.h" | #include "SMTPMailerService.h" | ||||||
|  | #include "RESTAPI_InternalServer.h" | ||||||
| #include "AuthService.h" | #include "AuthService.h" | ||||||
| #include "SMSSender.h" |  | ||||||
| #include "ActionLinkManager.h" |  | ||||||
| #include "TotpCache.h" |  | ||||||
|  |  | ||||||
| namespace OpenWifi { | namespace OpenWifi { | ||||||
|     class Daemon *Daemon::instance_ = nullptr; |     class Daemon *Daemon::instance_ = nullptr; | ||||||
| @@ -36,43 +37,32 @@ namespace OpenWifi { | |||||||
|                                    vDAEMON_CONFIG_ENV_VAR, |                                    vDAEMON_CONFIG_ENV_VAR, | ||||||
|                                    vDAEMON_APP_NAME, |                                    vDAEMON_APP_NAME, | ||||||
|                                    vDAEMON_BUS_TIMER, |                                    vDAEMON_BUS_TIMER, | ||||||
|                                    SubSystemVec{ |                                    Types::SubSystemVec{ | ||||||
|                                            StorageService(), |                                            Storage(), | ||||||
|                                            SMSSender(), |                                            RESTAPI_Server(), | ||||||
|                                            ActionLinkManager(), |                                            RESTAPI_InternalServer(), | ||||||
|                                            SMTPMailerService(), |                                            SMTPMailerService(), | ||||||
|                                            RESTAPI_RateLimiter(), |  | ||||||
|                                            TotpCache(), |  | ||||||
|                                            AuthService() |                                            AuthService() | ||||||
|                                    }); |                                    }); | ||||||
|         } |         } | ||||||
|         return instance_; |         return instance_; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     void Daemon::PostInitialization([[maybe_unused]] Poco::Util::Application &self) { |     void Daemon::initialize(Poco::Util::Application &self) { | ||||||
|         AssetDir_ = MicroService::instance().ConfigPath("openwifi.restapi.wwwassets"); |         MicroService::initialize(*this); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| int main(int argc, char **argv) { | int main(int argc, char **argv) { | ||||||
|     try { |     try { | ||||||
|         SSL_library_init(); |  | ||||||
|         Aws::SDKOptions AwsOptions; |  | ||||||
|         AwsOptions.memoryManagementOptions.memoryManager = nullptr; |  | ||||||
|         AwsOptions.cryptoOptions.initAndCleanupOpenSSL = false; |  | ||||||
|         AwsOptions.httpOptions.initAndCleanupCurl = true; |  | ||||||
|  |  | ||||||
|         Aws::InitAPI(AwsOptions); |  | ||||||
|  |  | ||||||
|         int ExitCode=0; |  | ||||||
|         { |  | ||||||
|         auto App = OpenWifi::Daemon::instance(); |         auto App = OpenWifi::Daemon::instance(); | ||||||
|             ExitCode =  App->run(argc, argv); |         auto ExitCode =  App->run(argc, argv); | ||||||
|         } |         delete App; | ||||||
|         ShutdownAPI(AwsOptions); |  | ||||||
|         return ExitCode; |         return ExitCode; | ||||||
|  |  | ||||||
|     } catch (Poco::Exception &exc) { |     } catch (Poco::Exception &exc) { | ||||||
|         std::cout << exc.displayText() << std::endl; |         std::cerr << exc.displayText() << std::endl; | ||||||
|         return Poco::Util::Application::EXIT_SOFTWARE; |         return Poco::Util::Application::EXIT_SOFTWARE; | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										23
									
								
								src/Daemon.h
									
									
									
									
									
								
							
							
						
						
									
										23
									
								
								src/Daemon.h
									
									
									
									
									
								
							| @@ -20,15 +20,17 @@ | |||||||
| #include "Poco/Crypto/CipherFactory.h" | #include "Poco/Crypto/CipherFactory.h" | ||||||
| #include "Poco/Crypto/Cipher.h" | #include "Poco/Crypto/Cipher.h" | ||||||
|  |  | ||||||
| #include "framework/MicroService.h" |  | ||||||
|  | #include "OpenWifiTypes.h" | ||||||
|  | #include "MicroService.h" | ||||||
|  |  | ||||||
| namespace OpenWifi { | namespace OpenWifi { | ||||||
|  |  | ||||||
|     [[maybe_unused]] static const char * vDAEMON_PROPERTIES_FILENAME = "owsec.properties"; |     static const char * vDAEMON_PROPERTIES_FILENAME = "owsec.properties"; | ||||||
|     [[maybe_unused]] static const char * vDAEMON_ROOT_ENV_VAR = "OWSEC_ROOT"; |     static const char * vDAEMON_ROOT_ENV_VAR = "OWSEC_ROOT"; | ||||||
|     [[maybe_unused]] static const char * vDAEMON_CONFIG_ENV_VAR = "OWSEC_CONFIG"; |     static const char * vDAEMON_CONFIG_ENV_VAR = "OWSEC_CONFIG"; | ||||||
|     [[maybe_unused]] static const char * vDAEMON_APP_NAME = uSERVICE_SECURITY.c_str(); |     static const char * vDAEMON_APP_NAME = uSERVICE_SECURITY.c_str(); | ||||||
|     [[maybe_unused]] static const uint64_t vDAEMON_BUS_TIMER = 5000; |     static const uint64_t vDAEMON_BUS_TIMER = 5000; | ||||||
|  |  | ||||||
|     class Daemon : public MicroService { |     class Daemon : public MicroService { | ||||||
|     public: |     public: | ||||||
| @@ -37,21 +39,16 @@ namespace OpenWifi { | |||||||
|                         const std::string & ConfigEnv, |                         const std::string & ConfigEnv, | ||||||
|                         const std::string & AppName, |                         const std::string & AppName, | ||||||
|                         uint64_t BusTimer, |                         uint64_t BusTimer, | ||||||
|                         const SubSystemVec & SubSystems) : |                         const Types::SubSystemVec & SubSystems) : | ||||||
|                 MicroService( PropFile, RootEnv, ConfigEnv, AppName, BusTimer, SubSystems) {}; |                 MicroService( PropFile, RootEnv, ConfigEnv, AppName, BusTimer, SubSystems) {}; | ||||||
|  |  | ||||||
|         void PostInitialization(Poco::Util::Application &self); |         void initialize(Poco::Util::Application &self) override; | ||||||
|         static Daemon *instance(); |         static Daemon *instance(); | ||||||
|         inline const std::string & AssetDir() { return AssetDir_; } |  | ||||||
|     private: |     private: | ||||||
|         static Daemon 				*instance_; |         static Daemon 				*instance_; | ||||||
|         std::string         AssetDir_; |  | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
|     inline Daemon * Daemon() { return Daemon::instance(); } |     inline Daemon * Daemon() { return Daemon::instance(); } | ||||||
|     inline void DaemonPostInitialization(Poco::Util::Application &self) { |  | ||||||
|         Daemon()->PostInitialization(self); |  | ||||||
|     } |  | ||||||
| } | } | ||||||
|  |  | ||||||
| #endif //UCENTRALSEC_DAEMON_H | #endif //UCENTRALSEC_DAEMON_H | ||||||
|   | |||||||
							
								
								
									
										221
									
								
								src/KafkaManager.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										221
									
								
								src/KafkaManager.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,221 @@ | |||||||
|  | // | ||||||
|  | //	License type: BSD 3-Clause License | ||||||
|  | //	License copy: https://github.com/Telecominfraproject/wlan-cloud-ucentralgw/blob/master/LICENSE | ||||||
|  | // | ||||||
|  | //	Created by Stephane Bourque on 2021-03-04. | ||||||
|  | //	Arilia Wireless Inc. | ||||||
|  | // | ||||||
|  | #include <thread> | ||||||
|  |  | ||||||
|  | #include "KafkaManager.h" | ||||||
|  |  | ||||||
|  | #include "Daemon.h" | ||||||
|  | #include "Utils.h" | ||||||
|  |  | ||||||
|  | namespace OpenWifi { | ||||||
|  |  | ||||||
|  | 	class KafkaManager *KafkaManager::instance_ = nullptr; | ||||||
|  |  | ||||||
|  | 	KafkaManager::KafkaManager() noexcept: | ||||||
|  | 		SubSystemServer("KafkaManager", "KAFKA-SVR", "openwifi.kafka") | ||||||
|  | 	{ | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	void KafkaManager::initialize(Poco::Util::Application & self) { | ||||||
|  | 		SubSystemServer::initialize(self); | ||||||
|  | 		KafkaEnabled_ = Daemon()->ConfigGetBool("openwifi.kafka.enable",false); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | #ifdef SMALL_BUILD | ||||||
|  |  | ||||||
|  | 	int KafkaManager::Start() { | ||||||
|  | 		return 0; | ||||||
|  | 	} | ||||||
|  | 	void KafkaManager::Stop() { | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | #else | ||||||
|  |  | ||||||
|  | 	int KafkaManager::Start() { | ||||||
|  | 		if(!KafkaEnabled_) | ||||||
|  | 			return 0; | ||||||
|  | 		ProducerThr_ = std::make_unique<std::thread>([this]() { this->ProducerThr(); }); | ||||||
|  | 		ConsumerThr_ = std::make_unique<std::thread>([this]() { this->ConsumerThr(); }); | ||||||
|  | 		return 0; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	void KafkaManager::Stop() { | ||||||
|  | 		if(KafkaEnabled_) { | ||||||
|  | 			ProducerRunning_ = ConsumerRunning_ = false; | ||||||
|  | 			ProducerThr_->join(); | ||||||
|  | 			ConsumerThr_->join(); | ||||||
|  | 			return; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	void KafkaManager::ProducerThr() { | ||||||
|  | 		cppkafka::Configuration Config({ | ||||||
|  | 										   { "client.id", Daemon()->ConfigGetString("openwifi.kafka.client.id") }, | ||||||
|  | 										   { "metadata.broker.list", Daemon()->ConfigGetString("openwifi.kafka.brokerlist") } | ||||||
|  | 									   }); | ||||||
|  | 		SystemInfoWrapper_ = 	R"lit({ "system" : { "id" : )lit" + | ||||||
|  | 								  	std::to_string(Daemon()->ID()) + | ||||||
|  | 									R"lit( , "host" : ")lit" + Daemon()->PrivateEndPoint() + | ||||||
|  | 									R"lit(" } , "payload" : )lit" ; | ||||||
|  | 		cppkafka::Producer	Producer(Config); | ||||||
|  | 		ProducerRunning_ = true; | ||||||
|  | 		while(ProducerRunning_) { | ||||||
|  | 			std::this_thread::sleep_for(std::chrono::milliseconds(200)); | ||||||
|  | 			try | ||||||
|  | 			{ | ||||||
|  | 				std::lock_guard G(ProducerMutex_); | ||||||
|  | 				auto Num=0; | ||||||
|  | 				while (!Queue_.empty()) { | ||||||
|  | 					const auto M = Queue_.front(); | ||||||
|  | 					Producer.produce( | ||||||
|  | 						cppkafka::MessageBuilder(M.Topic).key(M.Key).payload(M.PayLoad)); | ||||||
|  | 					Queue_.pop(); | ||||||
|  | 					Num++; | ||||||
|  | 				} | ||||||
|  | 				if(Num) | ||||||
|  | 					Producer.flush(); | ||||||
|  | 			} catch (const cppkafka::HandleException &E ) { | ||||||
|  | 				Logger_.warning(Poco::format("Caught a Kafka exception (producer): %s",std::string{E.what()})); | ||||||
|  | 			} catch (const Poco::Exception &E) { | ||||||
|  | 				Logger_.log(E); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	void KafkaManager::PartitionAssignment(const cppkafka::TopicPartitionList& partitions) { | ||||||
|  | 		Logger_.information(Poco::format("Partition assigned: %Lu...",(uint64_t )partitions.front().get_partition())); | ||||||
|  | 	} | ||||||
|  | 	void KafkaManager::PartitionRevocation(const cppkafka::TopicPartitionList& partitions) { | ||||||
|  | 		Logger_.information(Poco::format("Partition revocation: %Lu...",(uint64_t )partitions.front().get_partition())); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	void KafkaManager::ConsumerThr() { | ||||||
|  | 		cppkafka::Configuration Config({ | ||||||
|  | 										   { "client.id", Daemon()->ConfigGetString("openwifi.kafka.client.id") }, | ||||||
|  | 										   { "metadata.broker.list", Daemon()->ConfigGetString("openwifi.kafka.brokerlist") }, | ||||||
|  | 										   { "group.id", Daemon()->ConfigGetString("openwifi.kafka.group.id") }, | ||||||
|  | 										   { "enable.auto.commit", Daemon()->ConfigGetBool("openwifi.kafka.auto.commit",false) }, | ||||||
|  | 										   { "auto.offset.reset", "latest" } , | ||||||
|  | 										   { "enable.partition.eof", false } | ||||||
|  | 									   }); | ||||||
|  |  | ||||||
|  | 		cppkafka::TopicConfiguration topic_config = { | ||||||
|  | 			{ "auto.offset.reset", "smallest" } | ||||||
|  | 		}; | ||||||
|  |  | ||||||
|  | 		// Now configure it to be the default topic config | ||||||
|  | 		Config.set_default_topic_configuration(topic_config); | ||||||
|  |  | ||||||
|  | 		cppkafka::Consumer Consumer(Config); | ||||||
|  | 		Consumer.set_assignment_callback([this](cppkafka::TopicPartitionList& partitions) { | ||||||
|  | 			if(!partitions.empty()) { | ||||||
|  | 				Logger_.information(Poco::format("Partition assigned: %Lu...", | ||||||
|  | 												 (uint64_t)partitions.front().get_partition())); | ||||||
|  | 			} | ||||||
|  | 		}); | ||||||
|  | 		Consumer.set_revocation_callback([this](const cppkafka::TopicPartitionList& partitions) { | ||||||
|  | 			if(!partitions.empty()) { | ||||||
|  | 				Logger_.information(Poco::format("Partition revocation: %Lu...", | ||||||
|  | 												 (uint64_t)partitions.front().get_partition())); | ||||||
|  | 			} | ||||||
|  | 		}); | ||||||
|  |  | ||||||
|  |         bool AutoCommit = Daemon()->ConfigGetBool("openwifi.kafka.auto.commit",false); | ||||||
|  |         auto BatchSize = Daemon()->ConfigGetInt("openwifi.kafka.consumer.batchsize",20); | ||||||
|  |  | ||||||
|  |         Types::StringVec    Topics; | ||||||
|  | 		for(const auto &i:Notifiers_) | ||||||
|  | 			Topics.push_back(i.first); | ||||||
|  |  | ||||||
|  | 		Consumer.subscribe(Topics); | ||||||
|  |  | ||||||
|  | 		ConsumerRunning_ = true; | ||||||
|  | 		while(ConsumerRunning_) { | ||||||
|  | 			try { | ||||||
|  | 				std::vector<cppkafka::Message> MsgVec = Consumer.poll_batch(BatchSize, std::chrono::milliseconds(200)); | ||||||
|  | 				for(auto const &Msg:MsgVec) { | ||||||
|  |                     if (!Msg) | ||||||
|  |                         continue; | ||||||
|  |                     if (Msg.get_error()) { | ||||||
|  |                         if (!Msg.is_eof()) { | ||||||
|  |                             Logger_.error(Poco::format("Error: %s", Msg.get_error().to_string())); | ||||||
|  |                         }if(!AutoCommit) | ||||||
|  |                             Consumer.async_commit(Msg); | ||||||
|  |                         continue; | ||||||
|  |                     } | ||||||
|  |                     std::lock_guard G(ConsumerMutex_); | ||||||
|  |                     auto It = Notifiers_.find(Msg.get_topic()); | ||||||
|  |                     if (It != Notifiers_.end()) { | ||||||
|  |                         Types::TopicNotifyFunctionList &FL = It->second; | ||||||
|  |                         std::string Key{Msg.get_key()}; | ||||||
|  |                         std::string Payload{Msg.get_payload()}; | ||||||
|  |                         for (auto &F : FL) { | ||||||
|  |                             std::thread T(F.first, Key, Payload); | ||||||
|  |                             T.detach(); | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                     if (!AutoCommit) | ||||||
|  |                         Consumer.async_commit(Msg); | ||||||
|  |                 } | ||||||
|  | 			} catch (const cppkafka::HandleException &E) { | ||||||
|  | 				Logger_.warning(Poco::format("Caught a Kafka exception (consumer): %s",std::string{E.what()})); | ||||||
|  | 			} catch (const Poco::Exception &E) { | ||||||
|  | 				Logger_.log(E); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	std::string KafkaManager::WrapSystemId(const std::string & PayLoad) { | ||||||
|  | 		return std::move( SystemInfoWrapper_ + PayLoad + "}"); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	void KafkaManager::PostMessage(const std::string &topic, const std::string & key, const std::string &PayLoad, bool WrapMessage ) { | ||||||
|  | 		if(KafkaEnabled_) { | ||||||
|  | 			std::lock_guard G(Mutex_); | ||||||
|  | 			KMessage M{ | ||||||
|  | 				.Topic = topic, | ||||||
|  | 				.Key = key, | ||||||
|  | 				.PayLoad = WrapMessage ? WrapSystemId(PayLoad) : PayLoad }; | ||||||
|  | 			Queue_.push(M); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	int KafkaManager::RegisterTopicWatcher(const std::string &Topic, Types::TopicNotifyFunction &F) { | ||||||
|  | 		if(KafkaEnabled_) { | ||||||
|  | 			std::lock_guard G(Mutex_); | ||||||
|  | 			auto It = Notifiers_.find(Topic); | ||||||
|  | 			if(It == Notifiers_.end()) { | ||||||
|  | 				Types::TopicNotifyFunctionList L; | ||||||
|  | 				L.emplace(L.end(),std::make_pair(F,FunctionId_)); | ||||||
|  | 				Notifiers_[Topic] = std::move(L); | ||||||
|  | 			} else { | ||||||
|  | 				It->second.emplace(It->second.end(),std::make_pair(F,FunctionId_)); | ||||||
|  | 			} | ||||||
|  | 			return FunctionId_++; | ||||||
|  | 		} else { | ||||||
|  | 			return 0; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	void KafkaManager::UnregisterTopicWatcher(const std::string &Topic, int Id) { | ||||||
|  | 		if(KafkaEnabled_) { | ||||||
|  | 			std::lock_guard G(Mutex_); | ||||||
|  | 			auto It = Notifiers_.find(Topic); | ||||||
|  | 			if(It != Notifiers_.end()) { | ||||||
|  | 				Types::TopicNotifyFunctionList & L = It->second; | ||||||
|  | 				for(auto it=L.begin(); it!=L.end(); it++) | ||||||
|  | 					if(it->second == Id) { | ||||||
|  | 						L.erase(it); | ||||||
|  | 						break; | ||||||
|  | 					} | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | #endif | ||||||
|  | } // namespace | ||||||
							
								
								
									
										74
									
								
								src/KafkaManager.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										74
									
								
								src/KafkaManager.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,74 @@ | |||||||
|  | // | ||||||
|  | //	License type: BSD 3-Clause License | ||||||
|  | //	License copy: https://github.com/Telecominfraproject/wlan-cloud-ucentralgw/blob/master/LICENSE | ||||||
|  | // | ||||||
|  | //	Created by Stephane Bourque on 2021-03-04. | ||||||
|  | //	Arilia Wireless Inc. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef UCENTRALGW_KAFKAMANAGER_H | ||||||
|  | #define UCENTRALGW_KAFKAMANAGER_H | ||||||
|  |  | ||||||
|  | #include <queue> | ||||||
|  | #include <thread> | ||||||
|  |  | ||||||
|  | #include "SubSystemServer.h" | ||||||
|  | #include "OpenWifiTypes.h" | ||||||
|  |  | ||||||
|  | #include "cppkafka/cppkafka.h" | ||||||
|  |  | ||||||
|  | namespace OpenWifi { | ||||||
|  |  | ||||||
|  | 	class KafkaManager : public SubSystemServer { | ||||||
|  | 	  public: | ||||||
|  |  | ||||||
|  | 		struct KMessage { | ||||||
|  | 					std::string Topic, | ||||||
|  | 								Key, | ||||||
|  | 								PayLoad; | ||||||
|  | 		}; | ||||||
|  |  | ||||||
|  | 		void initialize(Poco::Util::Application & self) override; | ||||||
|  | 		static KafkaManager *instance() { | ||||||
|  | 			if(instance_== nullptr) | ||||||
|  | 				instance_ = new KafkaManager; | ||||||
|  | 			return instance_; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		void ProducerThr(); | ||||||
|  | 		void ConsumerThr(); | ||||||
|  |  | ||||||
|  | 		int Start() override; | ||||||
|  | 		void Stop() override; | ||||||
|  |  | ||||||
|  | 		void PostMessage(const std::string &topic, const std::string & key, const std::string &payload, bool WrapMessage = true); | ||||||
|  | 		[[nodiscard]] std::string WrapSystemId(const std::string & PayLoad); | ||||||
|  | 		[[nodiscard]] bool Enabled() { return KafkaEnabled_; } | ||||||
|  | 		int RegisterTopicWatcher(const std::string &Topic, Types::TopicNotifyFunction & F); | ||||||
|  | 		void UnregisterTopicWatcher(const std::string &Topic, int FunctionId); | ||||||
|  | 		void WakeUp(); | ||||||
|  | 		void PartitionAssignment(const cppkafka::TopicPartitionList& partitions); | ||||||
|  | 		void PartitionRevocation(const cppkafka::TopicPartitionList& partitions); | ||||||
|  |  | ||||||
|  | 	  private: | ||||||
|  | 		static KafkaManager 			*instance_; | ||||||
|  | 		std::mutex 						ProducerMutex_; | ||||||
|  | 		std::mutex						ConsumerMutex_; | ||||||
|  | 		bool 							KafkaEnabled_ = false; | ||||||
|  | 		std::atomic_bool 				ProducerRunning_ = false; | ||||||
|  | 		std::atomic_bool 				ConsumerRunning_ = false; | ||||||
|  | 		std::queue<KMessage>			Queue_; | ||||||
|  | 		std::string 					SystemInfoWrapper_; | ||||||
|  | 		std::unique_ptr<std::thread>	ConsumerThr_; | ||||||
|  | 		std::unique_ptr<std::thread>	ProducerThr_; | ||||||
|  | 		int                       		FunctionId_=1; | ||||||
|  | 		Types::NotifyTable        		Notifiers_; | ||||||
|  | 		std::unique_ptr<cppkafka::Configuration>    Config_; | ||||||
|  |  | ||||||
|  | 		KafkaManager() noexcept; | ||||||
|  | 	}; | ||||||
|  |  | ||||||
|  | 	inline KafkaManager * KafkaManager() { return KafkaManager::instance(); } | ||||||
|  | }	// NameSpace | ||||||
|  |  | ||||||
|  | #endif // UCENTRALGW_KAFKAMANAGER_H | ||||||
| @@ -1,12 +1,9 @@ | |||||||
| //
 | //
 | ||||||
| //	License type: BSD 3-Clause License
 | // Created by stephane bourque on 2021-06-07.
 | ||||||
| //	License copy: https://github.com/Telecominfraproject/wlan-cloud-ucentralgw/blob/master/LICENSE
 |  | ||||||
| //
 |  | ||||||
| //	Created by Stephane Bourque on 2021-03-04.
 |  | ||||||
| //	Arilia Wireless Inc.
 |  | ||||||
| //
 | //
 | ||||||
| 
 | 
 | ||||||
| #pragma once | #ifndef UCENTRALGW_KAFKA_TOPICS_H | ||||||
|  | #define UCENTRALGW_KAFKA_TOPICS_H | ||||||
| 
 | 
 | ||||||
| namespace OpenWifi::KafkaTopics { | namespace OpenWifi::KafkaTopics { | ||||||
| 	static const std::string HEALTHCHECK{"healthcheck"}; | 	static const std::string HEALTHCHECK{"healthcheck"}; | ||||||
| @@ -17,8 +14,6 @@ namespace OpenWifi::KafkaTopics { | |||||||
| 	static const std::string COMMAND{"command"}; | 	static const std::string COMMAND{"command"}; | ||||||
| 	static const std::string SERVICE_EVENTS{"service_events"}; | 	static const std::string SERVICE_EVENTS{"service_events"}; | ||||||
| 	static const std::string DEVICE_EVENT_QUEUE{"device_event_queue"}; | 	static const std::string DEVICE_EVENT_QUEUE{"device_event_queue"}; | ||||||
| 	static const std::string DEVICE_TELEMETRY{"device_telemetry"}; |  | ||||||
|     static const std::string PROVISIONING_CHANGE{"provisioning_change"}; |  | ||||||
| 
 | 
 | ||||||
| 	namespace ServiceEvents { | 	namespace ServiceEvents { | ||||||
| 		static const std::string EVENT_JOIN{"join"}; | 		static const std::string EVENT_JOIN{"join"}; | ||||||
| @@ -39,3 +34,4 @@ namespace OpenWifi::KafkaTopics { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | #endif // UCENTRALGW_KAFKA_TOPICS_H
 | ||||||
| @@ -1,119 +0,0 @@ | |||||||
| // |  | ||||||
| // Created by stephane bourque on 2021-10-11. |  | ||||||
| // |  | ||||||
|  |  | ||||||
| #include "MFAServer.h" |  | ||||||
| #include "SMSSender.h" |  | ||||||
| #include "SMTPMailerService.h" |  | ||||||
| #include "framework/MicroService.h" |  | ||||||
| #include "AuthService.h" |  | ||||||
| #include "TotpCache.h" |  | ||||||
|  |  | ||||||
| namespace OpenWifi { |  | ||||||
|  |  | ||||||
|     int MFAServer::Start() { |  | ||||||
|         return 0; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     void MFAServer::Stop() { |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     bool MFAServer::StartMFAChallenge(const SecurityObjects::UserInfoAndPolicy &UInfo, Poco::JSON::Object &ChallengeStart) { |  | ||||||
|         std::lock_guard G(Mutex_); |  | ||||||
|  |  | ||||||
|         CleanCache(); |  | ||||||
|  |  | ||||||
|         if(!MethodEnabled(UInfo.userinfo.userTypeProprietaryInfo.mfa.method)) |  | ||||||
|             return false; |  | ||||||
|  |  | ||||||
|         std::string Challenge = MakeChallenge(); |  | ||||||
|         std::string uuid = MicroService::CreateUUID(); |  | ||||||
|         uint64_t Created = OpenWifi::Now(); |  | ||||||
|  |  | ||||||
|         ChallengeStart.set("uuid",uuid); |  | ||||||
|         ChallengeStart.set("created", Created); |  | ||||||
|         ChallengeStart.set("question", "mfa challenge"); |  | ||||||
|         ChallengeStart.set("method", UInfo.userinfo.userTypeProprietaryInfo.mfa.method); |  | ||||||
|  |  | ||||||
|         Cache_[uuid] = MFACacheEntry{ .UInfo = UInfo, .Answer=Challenge, .Created=Created, .Method=UInfo.userinfo.userTypeProprietaryInfo.mfa.method }; |  | ||||||
|         return SendChallenge(UInfo, UInfo.userinfo.userTypeProprietaryInfo.mfa.method, Challenge); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     bool MFAServer::SendChallenge(const SecurityObjects::UserInfoAndPolicy &UInfo, const std::string &Method, const std::string &Challenge) { |  | ||||||
|         if(Method==MFAMETHODS::SMS && SMSSender()->Enabled() && !UInfo.userinfo.userTypeProprietaryInfo.mobiles.empty()) { |  | ||||||
|             std::string Message = "This is your login code: " + Challenge + " Please enter this in your login screen."; |  | ||||||
|             return SMSSender()->Send(UInfo.userinfo.userTypeProprietaryInfo.mobiles[0].number, Message); |  | ||||||
|         } else if(Method==MFAMETHODS::EMAIL && SMTPMailerService()->Enabled() && !UInfo.userinfo.email.empty()) { |  | ||||||
|             MessageAttributes Attrs; |  | ||||||
|             Attrs[RECIPIENT_EMAIL] = UInfo.userinfo.email; |  | ||||||
|             Attrs[LOGO] = AuthService::GetLogoAssetURI(); |  | ||||||
|             Attrs[SUBJECT] = "Login validation code"; |  | ||||||
|             Attrs[CHALLENGE_CODE] = Challenge; |  | ||||||
|             return SMTPMailerService()->SendMessage(UInfo.userinfo.email, "verification_code.txt", Attrs); |  | ||||||
|         } else if(Method==MFAMETHODS::AUTHENTICATOR && !UInfo.userinfo.userTypeProprietaryInfo.authenticatorSecret.empty()) { |  | ||||||
|             return true; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         return false; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     bool MFAServer::ResendCode(const std::string &uuid) { |  | ||||||
|         std::lock_guard G(Mutex_); |  | ||||||
|         auto Hint = Cache_.find(uuid); |  | ||||||
|         if(Hint==Cache_.end()) |  | ||||||
|             return false; |  | ||||||
|         return SendChallenge(Hint->second.UInfo, Hint->second.Method, Hint->second.Answer); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     bool MFAServer::CompleteMFAChallenge(const Poco::JSON::Object::Ptr &ChallengeResponse, SecurityObjects::UserInfoAndPolicy &UInfo) { |  | ||||||
|         std::lock_guard G(Mutex_); |  | ||||||
|  |  | ||||||
|         if(!ChallengeResponse->has("uuid") || !ChallengeResponse->has("answer")) |  | ||||||
|             return false; |  | ||||||
|  |  | ||||||
|         auto uuid = ChallengeResponse->get("uuid").toString(); |  | ||||||
|         auto Hint = Cache_.find(uuid); |  | ||||||
|         if(Hint == end(Cache_)) { |  | ||||||
|             return false; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         auto answer = ChallengeResponse->get("answer").toString(); |  | ||||||
|         std::string Expecting; |  | ||||||
|         if(Hint->second.Method==MFAMETHODS::AUTHENTICATOR) { |  | ||||||
|             if(!TotpCache()->ValidateCode(Hint->second.UInfo.userinfo.userTypeProprietaryInfo.authenticatorSecret,answer, Expecting)) { |  | ||||||
|                 return false; |  | ||||||
|             } |  | ||||||
|         } else if(Hint->second.Answer!=answer) { |  | ||||||
|             return false; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         UInfo = Hint->second.UInfo; |  | ||||||
|         Cache_.erase(Hint); |  | ||||||
|         return true; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     bool MFAServer::MethodEnabled(const std::string &Method) { |  | ||||||
|         if(Method==MFAMETHODS::SMS) |  | ||||||
|             return SMSSender()->Enabled(); |  | ||||||
|  |  | ||||||
|         if(Method==MFAMETHODS::EMAIL) |  | ||||||
|             return SMTPMailerService()->Enabled(); |  | ||||||
|  |  | ||||||
|         if(Method==MFAMETHODS::AUTHENTICATOR) |  | ||||||
|             return true; |  | ||||||
|  |  | ||||||
|         return false; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     void MFAServer::CleanCache() { |  | ||||||
|         // it is assumed that you have locked Cache_ at this point. |  | ||||||
|         uint64_t Now = OpenWifi::Now(); |  | ||||||
|         for(auto i=begin(Cache_);i!=end(Cache_);) { |  | ||||||
|             if((Now-i->second.Created)>300) { |  | ||||||
|                 i = Cache_.erase(i); |  | ||||||
|             } else { |  | ||||||
|                 ++i; |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,64 +0,0 @@ | |||||||
| // |  | ||||||
| // Created by stephane bourque on 2021-10-11. |  | ||||||
| // |  | ||||||
|  |  | ||||||
| #pragma once |  | ||||||
|  |  | ||||||
| #include "framework/MicroService.h" |  | ||||||
| #include "Poco/JSON/Object.h" |  | ||||||
| #include "RESTObjects/RESTAPI_SecurityObjects.h" |  | ||||||
|  |  | ||||||
| namespace OpenWifi { |  | ||||||
|  |  | ||||||
|     namespace MFAMETHODS { |  | ||||||
|         inline const static std::string SMS{"sms"}; |  | ||||||
|         inline const static std::string EMAIL{"email"}; |  | ||||||
|         inline const static std::string AUTHENTICATOR{"authenticator"}; |  | ||||||
|         inline const static std::vector<std::string> Methods{ SMS, EMAIL, AUTHENTICATOR }; |  | ||||||
|         inline bool Validate(const std::string &M) { |  | ||||||
|             return std::find(cbegin(Methods), cend(Methods),M)!=Methods.end(); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     struct MFACacheEntry { |  | ||||||
|         SecurityObjects::UserInfoAndPolicy  UInfo; |  | ||||||
|         std::string                         Answer; |  | ||||||
|         uint64_t                            Created; |  | ||||||
|         std::string                         Method; |  | ||||||
|     }; |  | ||||||
|  |  | ||||||
|  |  | ||||||
|     typedef std::map<std::string,MFACacheEntry>     MFAChallengeCache; |  | ||||||
|  |  | ||||||
|     class MFAServer : public SubSystemServer{ |  | ||||||
|     public: |  | ||||||
|         int Start() override; |  | ||||||
|         void Stop() override; |  | ||||||
|         static auto instance() { |  | ||||||
|             static auto instance_ = new MFAServer; |  | ||||||
|             return instance_; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         bool StartMFAChallenge(const SecurityObjects::UserInfoAndPolicy &UInfo, Poco::JSON::Object &Challenge); |  | ||||||
|         bool CompleteMFAChallenge(const Poco::JSON::Object::Ptr &ChallengeResponse, SecurityObjects::UserInfoAndPolicy &UInfo); |  | ||||||
|         static bool MethodEnabled(const std::string &Method); |  | ||||||
|         bool ResendCode(const std::string &uuid); |  | ||||||
|         static bool SendChallenge(const SecurityObjects::UserInfoAndPolicy &UInfo, const std::string &Method, const std::string &Challenge); |  | ||||||
|  |  | ||||||
|         static inline std::string MakeChallenge() { |  | ||||||
|             return fmt::format("{0:06}" , MicroService::instance().Random(1,999999) ); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|     private: |  | ||||||
|         MFAChallengeCache   Cache_; |  | ||||||
|         MFAServer() noexcept: |  | ||||||
|             SubSystemServer("MFServer", "MFA-SVR", "mfa") |  | ||||||
|             { |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|         void CleanCache(); |  | ||||||
|     }; |  | ||||||
|  |  | ||||||
|     inline auto MFAServer() { return MFAServer::instance(); } |  | ||||||
| } |  | ||||||
|  |  | ||||||
							
								
								
									
										532
									
								
								src/MicroService.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										532
									
								
								src/MicroService.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,532 @@ | |||||||
|  | // | ||||||
|  | //	License type: BSD 3-Clause License | ||||||
|  | //	License copy: https://github.com/Telecominfraproject/wlan-cloud-ucentralgw/blob/master/LICENSE | ||||||
|  | // | ||||||
|  | //	Created by Stephane Bourque on 2021-03-04. | ||||||
|  | //	Arilia Wireless Inc. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #include <cstdlib> | ||||||
|  | #include <boost/algorithm/string.hpp> | ||||||
|  |  | ||||||
|  | #include "Poco/Util/Application.h" | ||||||
|  | #include "Poco/Util/ServerApplication.h" | ||||||
|  | #include "Poco/Util/Option.h" | ||||||
|  | #include "Poco/Util/OptionSet.h" | ||||||
|  | #include "Poco/Util/HelpFormatter.h" | ||||||
|  | #include "Poco/Environment.h" | ||||||
|  | #include "Poco/Net/HTTPSStreamFactory.h" | ||||||
|  | #include "Poco/Net/HTTPStreamFactory.h" | ||||||
|  | #include "Poco/Net/FTPSStreamFactory.h" | ||||||
|  | #include "Poco/Net/FTPStreamFactory.h" | ||||||
|  | #include "Poco/Path.h" | ||||||
|  | #include "Poco/File.h" | ||||||
|  | #include "Poco/String.h" | ||||||
|  | #include "Poco/JSON/Object.h" | ||||||
|  | #include "Poco/JSON/Parser.h" | ||||||
|  | #include "Poco/JSON/Stringifier.h" | ||||||
|  |  | ||||||
|  | #include "ALBHealthCheckServer.h" | ||||||
|  | #ifndef SMALL_BUILD | ||||||
|  | #include "KafkaManager.h" | ||||||
|  | #endif | ||||||
|  | #include "Kafka_topics.h" | ||||||
|  |  | ||||||
|  | #include "MicroService.h" | ||||||
|  | #include "Utils.h" | ||||||
|  |  | ||||||
|  | #ifndef TIP_SECURITY_SERVICE | ||||||
|  | #include "AuthClient.h" | ||||||
|  | #endif | ||||||
|  |  | ||||||
|  | namespace OpenWifi { | ||||||
|  |  | ||||||
|  | 	void MyErrorHandler::exception(const Poco::Exception & E) { | ||||||
|  | 		Poco::Thread * CurrentThread = Poco::Thread::current(); | ||||||
|  | 		App_.logger().log(E); | ||||||
|  | 		App_.logger().error(Poco::format("Exception occurred in %s",CurrentThread->getName())); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	void MyErrorHandler::exception(const std::exception & E) { | ||||||
|  | 		Poco::Thread * CurrentThread = Poco::Thread::current(); | ||||||
|  | 		App_.logger().warning(Poco::format("std::exception on %s",CurrentThread->getName())); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	void MyErrorHandler::exception() { | ||||||
|  | 		Poco::Thread * CurrentThread = Poco::Thread::current(); | ||||||
|  | 		App_.logger().warning(Poco::format("exception on %s",CurrentThread->getName())); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	void MicroService::Exit(int Reason) { | ||||||
|  | 		std::exit(Reason); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	void MicroService::BusMessageReceived(const std::string &Key, const std::string & Message) { | ||||||
|  | 		std::lock_guard G(InfraMutex_); | ||||||
|  | 		try { | ||||||
|  | 			Poco::JSON::Parser P; | ||||||
|  | 			auto Object = P.parse(Message).extract<Poco::JSON::Object::Ptr>(); | ||||||
|  | 			if (Object->has(KafkaTopics::ServiceEvents::Fields::ID) && | ||||||
|  | 				Object->has(KafkaTopics::ServiceEvents::Fields::EVENT)) { | ||||||
|  | 				uint64_t 	ID = Object->get(KafkaTopics::ServiceEvents::Fields::ID); | ||||||
|  | 				auto 		Event = Object->get(KafkaTopics::ServiceEvents::Fields::EVENT).toString(); | ||||||
|  | 				if (ID != ID_) { | ||||||
|  | 					if(	Event==KafkaTopics::ServiceEvents::EVENT_JOIN || | ||||||
|  | 						Event==KafkaTopics::ServiceEvents::EVENT_KEEP_ALIVE || | ||||||
|  | 						Event==KafkaTopics::ServiceEvents::EVENT_LEAVE ) { | ||||||
|  | 						if(	Object->has(KafkaTopics::ServiceEvents::Fields::TYPE) && | ||||||
|  | 						   	Object->has(KafkaTopics::ServiceEvents::Fields::PUBLIC) && | ||||||
|  | 							Object->has(KafkaTopics::ServiceEvents::Fields::PRIVATE) && | ||||||
|  | 						   	Object->has(KafkaTopics::ServiceEvents::Fields::VRSN) && | ||||||
|  | 							Object->has(KafkaTopics::ServiceEvents::Fields::KEY)) { | ||||||
|  |  | ||||||
|  | 							if (Event == KafkaTopics::ServiceEvents::EVENT_KEEP_ALIVE && Services_.find(ID) != Services_.end()) { | ||||||
|  | 								Services_[ID].LastUpdate = std::time(nullptr); | ||||||
|  | 							} else if (Event == KafkaTopics::ServiceEvents::EVENT_LEAVE) { | ||||||
|  | 								Services_.erase(ID); | ||||||
|  | 								logger().information(Poco::format("Service %s ID=%Lu leaving system.",Object->get(KafkaTopics::ServiceEvents::Fields::PRIVATE).toString(),ID)); | ||||||
|  | 							} else if (Event == KafkaTopics::ServiceEvents::EVENT_JOIN || Event == KafkaTopics::ServiceEvents::EVENT_KEEP_ALIVE) { | ||||||
|  | 								logger().information(Poco::format("Service %s ID=%Lu joining system.",Object->get(KafkaTopics::ServiceEvents::Fields::PRIVATE).toString(),ID)); | ||||||
|  | 								Services_[ID] = MicroServiceMeta{ | ||||||
|  | 									.Id = ID, | ||||||
|  | 									.Type = Poco::toLower(Object->get(KafkaTopics::ServiceEvents::Fields::TYPE).toString()), | ||||||
|  | 									.PrivateEndPoint = Object->get(KafkaTopics::ServiceEvents::Fields::PRIVATE).toString(), | ||||||
|  | 									.PublicEndPoint = Object->get(KafkaTopics::ServiceEvents::Fields::PUBLIC).toString(), | ||||||
|  | 									.AccessKey = Object->get(KafkaTopics::ServiceEvents::Fields::KEY).toString(), | ||||||
|  | 									.Version = Object->get(KafkaTopics::ServiceEvents::Fields::VRSN).toString(), | ||||||
|  | 									.LastUpdate = (uint64_t)std::time(nullptr)}; | ||||||
|  | 								for (const auto &[Id, Svc] : Services_) { | ||||||
|  | 									logger().information(Poco::format("ID: %Lu Type: %s EndPoint: %s",Id,Svc.Type,Svc.PrivateEndPoint)); | ||||||
|  | 								} | ||||||
|  | 							} | ||||||
|  | 						} else { | ||||||
|  | 							logger().error(Poco::format("KAFKA-MSG: invalid event '%s', missing a field.",Event)); | ||||||
|  | 						} | ||||||
|  | 					} else if (Event==KafkaTopics::ServiceEvents::EVENT_REMOVE_TOKEN) { | ||||||
|  | 						if(Object->has(KafkaTopics::ServiceEvents::Fields::TOKEN)) { | ||||||
|  | #ifndef TIP_SECURITY_SERVICE | ||||||
|  | 							AuthClient()->RemovedCachedToken(Object->get(KafkaTopics::ServiceEvents::Fields::TOKEN).toString()); | ||||||
|  | #endif | ||||||
|  | 						} else { | ||||||
|  | 							logger().error(Poco::format("KAFKA-MSG: invalid event '%s', missing token",Event)); | ||||||
|  | 						} | ||||||
|  | 					} else { | ||||||
|  | 						logger().error(Poco::format("Unknown Event: %s Source: %Lu", Event, ID)); | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  | 			} else { | ||||||
|  | 				logger().error("Bad bus message."); | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 			auto i=Services_.begin(); | ||||||
|  | 			auto Now = (uint64_t )std::time(nullptr); | ||||||
|  | 			for(;i!=Services_.end();) { | ||||||
|  | 			    if((Now - i->second.LastUpdate)>60) { | ||||||
|  | 			        i = Services_.erase(i); | ||||||
|  | 			    } else | ||||||
|  | 			        ++i; | ||||||
|  | 			} | ||||||
|  |  | ||||||
|  | 		} catch (const Poco::Exception &E) { | ||||||
|  | 			logger().log(E); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	MicroServiceMetaVec MicroService::GetServices(const std::string & Type) { | ||||||
|  | 		std::lock_guard G(InfraMutex_); | ||||||
|  |  | ||||||
|  | 		auto T = Poco::toLower(Type); | ||||||
|  | 		MicroServiceMetaVec	Res; | ||||||
|  | 		for(const auto &[Id,ServiceRec]:Services_) { | ||||||
|  | 			if(ServiceRec.Type==T) | ||||||
|  | 				Res.push_back(ServiceRec); | ||||||
|  | 		} | ||||||
|  | 		return Res; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	MicroServiceMetaVec MicroService::GetServices() { | ||||||
|  | 		std::lock_guard G(InfraMutex_); | ||||||
|  |  | ||||||
|  | 		MicroServiceMetaVec	Res; | ||||||
|  | 		for(const auto &[Id,ServiceRec]:Services_) { | ||||||
|  | 			Res.push_back(ServiceRec); | ||||||
|  | 		} | ||||||
|  | 		return Res; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  |     void MicroService::LoadConfigurationFile() { | ||||||
|  | 	    std::string Location = Poco::Environment::get(DAEMON_CONFIG_ENV_VAR,"."); | ||||||
|  | 	    Poco::Path ConfigFile; | ||||||
|  |  | ||||||
|  | 	    ConfigFile = ConfigFileName_.empty() ? Location + "/" + DAEMON_PROPERTIES_FILENAME : ConfigFileName_; | ||||||
|  |  | ||||||
|  | 	    if(!ConfigFile.isFile()) | ||||||
|  | 	    { | ||||||
|  | 	        std::cerr << DAEMON_APP_NAME << ": Configuration " | ||||||
|  | 	        << ConfigFile.toString() << " does not seem to exist. Please set " + DAEMON_CONFIG_ENV_VAR | ||||||
|  | 	        + " env variable the path of the " + DAEMON_PROPERTIES_FILENAME + " file." << std::endl; | ||||||
|  | 	        std::exit(Poco::Util::Application::EXIT_CONFIG); | ||||||
|  | 	    } | ||||||
|  |  | ||||||
|  | 	    loadConfiguration(ConfigFile.toString()); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  |     void MicroService::Reload() { | ||||||
|  | 	    LoadConfigurationFile(); | ||||||
|  | 	    LoadMyConfig(); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  |     void MicroService::LoadMyConfig() { | ||||||
|  | 	    std::string KeyFile = ConfigPath("openwifi.service.key"); | ||||||
|  | 	    std::string KeyFilePassword = ConfigPath("openwifi.service.key.password" , "" ); | ||||||
|  | 	    AppKey_ = Poco::SharedPtr<Poco::Crypto::RSAKey>(new Poco::Crypto::RSAKey("", KeyFile, KeyFilePassword)); | ||||||
|  | 	    Cipher_ = CipherFactory_.createCipher(*AppKey_); | ||||||
|  | 	    ID_ = Utils::GetSystemId(); | ||||||
|  | 	    if(!DebugMode_) | ||||||
|  | 	        DebugMode_ = ConfigGetBool("openwifi.system.debug",false); | ||||||
|  | 	    MyPrivateEndPoint_ = ConfigGetString("openwifi.system.uri.private"); | ||||||
|  | 	    MyPublicEndPoint_ = ConfigGetString("openwifi.system.uri.public"); | ||||||
|  | 	    UIURI_ = ConfigGetString("openwifi.system.uri.ui"); | ||||||
|  | 	    MyHash_ = CreateHash(MyPublicEndPoint_); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	void MicroService::initialize(Poco::Util::Application &self) { | ||||||
|  | 		// add the default services | ||||||
|  | 		SubSystems_.push_back(KafkaManager()); | ||||||
|  | 		SubSystems_.push_back(ALBHealthCheckServer()); | ||||||
|  |  | ||||||
|  | 		Poco::Net::initializeSSL(); | ||||||
|  | 		Poco::Net::HTTPStreamFactory::registerFactory(); | ||||||
|  | 		Poco::Net::HTTPSStreamFactory::registerFactory(); | ||||||
|  | 		Poco::Net::FTPStreamFactory::registerFactory(); | ||||||
|  | 		Poco::Net::FTPSStreamFactory::registerFactory(); | ||||||
|  |  | ||||||
|  | 		LoadConfigurationFile(); | ||||||
|  |  | ||||||
|  |         static const char * LogFilePathKey = "logging.channels.c2.path"; | ||||||
|  |  | ||||||
|  | 		if(LogDir_.empty()) { | ||||||
|  | 			std::string OriginalLogFileValue = ConfigPath(LogFilePathKey); | ||||||
|  | 			config().setString(LogFilePathKey, OriginalLogFileValue); | ||||||
|  | 		} else { | ||||||
|  | 			config().setString(LogFilePathKey, LogDir_); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		Poco::File	DataDir(ConfigPath("openwifi.system.data")); | ||||||
|  | 		DataDir_ = DataDir.path(); | ||||||
|  | 		if(!DataDir.exists()) { | ||||||
|  | 			try { | ||||||
|  | 				DataDir.createDirectory(); | ||||||
|  | 			} catch (const Poco::Exception &E) { | ||||||
|  | 				logger().log(E); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		LoadMyConfig(); | ||||||
|  |  | ||||||
|  | 		InitializeSubSystemServers(); | ||||||
|  | 		ServerApplication::initialize(self); | ||||||
|  |  | ||||||
|  | 		Types::TopicNotifyFunction F = [this](std::string s1,std::string s2) { this->BusMessageReceived(s1,s2); }; | ||||||
|  | 		KafkaManager()->RegisterTopicWatcher(KafkaTopics::SERVICE_EVENTS, F); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	void MicroService::uninitialize() { | ||||||
|  | 		// add your own uninitialization code here | ||||||
|  | 		ServerApplication::uninitialize(); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	void MicroService::reinitialize(Poco::Util::Application &self) { | ||||||
|  | 		ServerApplication::reinitialize(self); | ||||||
|  | 		// add your own reinitialization code here | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	void MicroService::defineOptions(Poco::Util::OptionSet &options) { | ||||||
|  | 		ServerApplication::defineOptions(options); | ||||||
|  |  | ||||||
|  | 		options.addOption( | ||||||
|  | 			Poco::Util::Option("help", "", "display help information on command line arguments") | ||||||
|  | 				.required(false) | ||||||
|  | 				.repeatable(false) | ||||||
|  | 				.callback(Poco::Util::OptionCallback<MicroService>(this, &MicroService::handleHelp))); | ||||||
|  |  | ||||||
|  | 		options.addOption( | ||||||
|  | 			Poco::Util::Option("file", "", "specify the configuration file") | ||||||
|  | 				.required(false) | ||||||
|  | 				.repeatable(false) | ||||||
|  | 				.argument("file") | ||||||
|  | 				.callback(Poco::Util::OptionCallback<MicroService>(this, &MicroService::handleConfig))); | ||||||
|  |  | ||||||
|  | 		options.addOption( | ||||||
|  | 			Poco::Util::Option("debug", "", "to run in debug, set to true") | ||||||
|  | 				.required(false) | ||||||
|  | 				.repeatable(false) | ||||||
|  | 				.callback(Poco::Util::OptionCallback<MicroService>(this, &MicroService::handleDebug))); | ||||||
|  |  | ||||||
|  | 		options.addOption( | ||||||
|  | 			Poco::Util::Option("logs", "", "specify the log directory and file (i.e. dir/file.log)") | ||||||
|  | 				.required(false) | ||||||
|  | 				.repeatable(false) | ||||||
|  | 				.argument("dir") | ||||||
|  | 				.callback(Poco::Util::OptionCallback<MicroService>(this, &MicroService::handleLogs))); | ||||||
|  |  | ||||||
|  | 		options.addOption( | ||||||
|  | 			Poco::Util::Option("version", "", "get the version and quit.") | ||||||
|  | 				.required(false) | ||||||
|  | 				.repeatable(false) | ||||||
|  | 				.callback(Poco::Util::OptionCallback<MicroService>(this, &MicroService::handleVersion))); | ||||||
|  |  | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	void MicroService::handleHelp(const std::string &name, const std::string &value) { | ||||||
|  | 		HelpRequested_ = true; | ||||||
|  | 		displayHelp(); | ||||||
|  | 		stopOptionsProcessing(); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	void MicroService::handleVersion(const std::string &name, const std::string &value) { | ||||||
|  | 		HelpRequested_ = true; | ||||||
|  | 		std::cout << Version() << std::endl; | ||||||
|  | 		stopOptionsProcessing(); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	void MicroService::handleDebug(const std::string &name, const std::string &value) { | ||||||
|  | 		if(value == "true") | ||||||
|  | 			DebugMode_ = true ; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	void MicroService::handleLogs(const std::string &name, const std::string &value) { | ||||||
|  | 		LogDir_ = value; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	void MicroService::handleConfig(const std::string &name, const std::string &value) { | ||||||
|  | 		ConfigFileName_ = value; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	void MicroService::displayHelp() { | ||||||
|  | 		Poco::Util::HelpFormatter helpFormatter(options()); | ||||||
|  | 		helpFormatter.setCommand(commandName()); | ||||||
|  | 		helpFormatter.setUsage("OPTIONS"); | ||||||
|  | 		helpFormatter.setHeader("A " + DAEMON_APP_NAME + " implementation for TIP."); | ||||||
|  | 		helpFormatter.format(std::cout); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	void MicroService::InitializeSubSystemServers() { | ||||||
|  | 		for(auto i:SubSystems_) | ||||||
|  | 			addSubsystem(i); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	void MicroService::StartSubSystemServers() { | ||||||
|  | 		for(auto i:SubSystems_) { | ||||||
|  | 			i->Start(); | ||||||
|  | 		} | ||||||
|  | 		BusEventManager_.Start(); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	void MicroService::StopSubSystemServers() { | ||||||
|  | 		BusEventManager_.Stop(); | ||||||
|  | 		for(auto i=SubSystems_.rbegin(); i!=SubSystems_.rend(); ++i) | ||||||
|  | 			(*i)->Stop(); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	std::string MicroService::CreateUUID() { | ||||||
|  | 		return UUIDGenerator_.create().toString(); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	bool MicroService::SetSubsystemLogLevel(const std::string &SubSystem, const std::string &Level) { | ||||||
|  | 		try { | ||||||
|  | 			auto P = Poco::Logger::parseLevel(Level); | ||||||
|  | 			auto Sub = Poco::toLower(SubSystem); | ||||||
|  |  | ||||||
|  | 			if (Sub == "all") { | ||||||
|  | 				for (auto i : SubSystems_) { | ||||||
|  | 					i->Logger().setLevel(P); | ||||||
|  | 				} | ||||||
|  | 				return true; | ||||||
|  | 			} else { | ||||||
|  | 				// std::cout << "Sub:" << SubSystem << " Level:" << Level << std::endl; | ||||||
|  | 				for (auto i : SubSystems_) { | ||||||
|  | 					if (Sub == Poco::toLower(i->Name())) { | ||||||
|  | 						i->Logger().setLevel(P); | ||||||
|  | 						return true; | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 		} catch (const Poco::Exception & E) { | ||||||
|  | 			std::cout << "Exception" << std::endl; | ||||||
|  | 		} | ||||||
|  | 		return false; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	void MicroService::Reload(const std::string &Sub) { | ||||||
|  | 		for (auto i : SubSystems_) { | ||||||
|  | 			if (Poco::toLower(Sub) == Poco::toLower(i->Name())) { | ||||||
|  | 				i->reinitialize(Poco::Util::Application::instance()); | ||||||
|  | 				return; | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	Types::StringVec MicroService::GetSubSystems() const { | ||||||
|  | 		Types::StringVec Result; | ||||||
|  | 		for(auto i:SubSystems_) | ||||||
|  | 			Result.push_back(Poco::toLower(i->Name())); | ||||||
|  | 		return Result; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	Types::StringPairVec MicroService::GetLogLevels() { | ||||||
|  | 		Types::StringPairVec Result; | ||||||
|  |  | ||||||
|  | 		for(auto &i:SubSystems_) { | ||||||
|  | 			auto P = std::make_pair( i->Name(), Utils::LogLevelToString(i->GetLoggingLevel())); | ||||||
|  | 			Result.push_back(P); | ||||||
|  | 		} | ||||||
|  | 		return Result; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	const Types::StringVec & MicroService::GetLogLevelNames() { | ||||||
|  | 		static Types::StringVec LevelNames{"none", "fatal", "critical", "error", "warning", "notice", "information", "debug", "trace" }; | ||||||
|  | 		return LevelNames; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	uint64_t MicroService::ConfigGetInt(const std::string &Key,uint64_t Default) { | ||||||
|  | 		return (uint64_t) config().getInt64(Key,Default); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	uint64_t MicroService::ConfigGetInt(const std::string &Key) { | ||||||
|  | 		return config().getInt(Key); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	uint64_t MicroService::ConfigGetBool(const std::string &Key,bool Default) { | ||||||
|  | 		return config().getBool(Key,Default); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	uint64_t MicroService::ConfigGetBool(const std::string &Key) { | ||||||
|  | 		return config().getBool(Key); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	std::string MicroService::ConfigGetString(const std::string &Key,const std::string & Default) { | ||||||
|  | 		return config().getString(Key, Default); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	std::string MicroService::ConfigGetString(const std::string &Key) { | ||||||
|  | 		return config().getString(Key); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	std::string MicroService::ConfigPath(const std::string &Key,const std::string & Default) { | ||||||
|  | 		std::string R = config().getString(Key, Default); | ||||||
|  | 		return Poco::Path::expand(R); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	std::string MicroService::ConfigPath(const std::string &Key) { | ||||||
|  | 		std::string R = config().getString(Key); | ||||||
|  | 		return Poco::Path::expand(R); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	std::string MicroService::Encrypt(const std::string &S) { | ||||||
|  | 		return Cipher_->encryptString(S, Poco::Crypto::Cipher::Cipher::ENC_BASE64);; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	std::string MicroService::Decrypt(const std::string &S) { | ||||||
|  | 		return Cipher_->decryptString(S, Poco::Crypto::Cipher::Cipher::ENC_BASE64);; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	std::string MicroService::CreateHash(const std::string &S) { | ||||||
|  | 		SHA2_.update(S); | ||||||
|  | 		return Utils::ToHex(SHA2_.digest()); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	std::string MicroService::MakeSystemEventMessage( const std::string & Type ) const { | ||||||
|  | 		Poco::JSON::Object	Obj; | ||||||
|  | 		Obj.set(KafkaTopics::ServiceEvents::Fields::EVENT,Type); | ||||||
|  | 		Obj.set(KafkaTopics::ServiceEvents::Fields::ID,ID_); | ||||||
|  | 		Obj.set(KafkaTopics::ServiceEvents::Fields::TYPE,Poco::toLower(DAEMON_APP_NAME)); | ||||||
|  | 		Obj.set(KafkaTopics::ServiceEvents::Fields::PUBLIC,MyPublicEndPoint_); | ||||||
|  | 		Obj.set(KafkaTopics::ServiceEvents::Fields::PRIVATE,MyPrivateEndPoint_); | ||||||
|  | 		Obj.set(KafkaTopics::ServiceEvents::Fields::KEY,MyHash_); | ||||||
|  | 		Obj.set(KafkaTopics::ServiceEvents::Fields::VRSN,Version_); | ||||||
|  | 		std::stringstream ResultText; | ||||||
|  | 		Poco::JSON::Stringifier::stringify(Obj, ResultText); | ||||||
|  | 		return ResultText.str(); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	void BusEventManager::run() { | ||||||
|  | 		Running_ = true; | ||||||
|  | 		auto Msg = Daemon()->MakeSystemEventMessage(KafkaTopics::ServiceEvents::EVENT_JOIN); | ||||||
|  | 		KafkaManager()->PostMessage(KafkaTopics::SERVICE_EVENTS,Daemon()->PrivateEndPoint(),Msg, false); | ||||||
|  | 		while(Running_) { | ||||||
|  | 			Poco::Thread::trySleep((unsigned long)Daemon()->DaemonBusTimer()); | ||||||
|  | 			if(!Running_) | ||||||
|  | 				break; | ||||||
|  | 			Msg = Daemon()->MakeSystemEventMessage(KafkaTopics::ServiceEvents::EVENT_KEEP_ALIVE); | ||||||
|  | 			KafkaManager()->PostMessage(KafkaTopics::SERVICE_EVENTS,Daemon()->PrivateEndPoint(),Msg, false); | ||||||
|  | 		} | ||||||
|  | 		Msg = Daemon()->MakeSystemEventMessage(KafkaTopics::ServiceEvents::EVENT_LEAVE); | ||||||
|  | 		KafkaManager()->PostMessage(KafkaTopics::SERVICE_EVENTS,Daemon()->PrivateEndPoint(),Msg, false); | ||||||
|  | 	}; | ||||||
|  |  | ||||||
|  | 	void BusEventManager::Start() { | ||||||
|  | 		if(KafkaManager()->Enabled()) { | ||||||
|  | 			Thread_.start(*this); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	void BusEventManager::Stop() { | ||||||
|  | 		if(KafkaManager()->Enabled()) { | ||||||
|  | 			Running_ = false; | ||||||
|  | 			Thread_.wakeUp(); | ||||||
|  | 			Thread_.join(); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	[[nodiscard]] bool MicroService::IsValidAPIKEY(const Poco::Net::HTTPServerRequest &Request) { | ||||||
|  | 		try { | ||||||
|  | 			auto APIKEY = Request.get("X-API-KEY"); | ||||||
|  | 			return APIKEY == MyHash_; | ||||||
|  | 		} catch (const Poco::Exception &E) { | ||||||
|  | 			logger().log(E); | ||||||
|  | 		} | ||||||
|  | 		return false; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	void MicroService::SavePID() { | ||||||
|  | 		try { | ||||||
|  | 			std::ofstream O; | ||||||
|  | 			O.open(Daemon()->DataDir() + "/pidfile",std::ios::binary | std::ios::trunc); | ||||||
|  | 			O << Poco::Process::id(); | ||||||
|  | 			O.close(); | ||||||
|  | 		} catch (...) | ||||||
|  | 		{ | ||||||
|  | 			std::cout << "Could not save system ID" << std::endl; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	int MicroService::main(const ArgVec &args) { | ||||||
|  |  | ||||||
|  | 		MyErrorHandler	ErrorHandler(*this); | ||||||
|  | 		Poco::ErrorHandler::set(&ErrorHandler); | ||||||
|  |  | ||||||
|  | 		if (!HelpRequested_) { | ||||||
|  | 			SavePID(); | ||||||
|  | 			Poco::Logger &logger = Poco::Logger::get(DAEMON_APP_NAME); | ||||||
|  | 			logger.notice(Poco::format("Starting %s version %s.",DAEMON_APP_NAME, Version())); | ||||||
|  |  | ||||||
|  | 			if(Poco::Net::Socket::supportsIPv6()) | ||||||
|  | 				logger.information("System supports IPv6."); | ||||||
|  | 			else | ||||||
|  | 				logger.information("System does NOT support IPv6."); | ||||||
|  |  | ||||||
|  | 			if (config().getBool("application.runAsDaemon", false)) { | ||||||
|  | 				logger.information("Starting as a daemon."); | ||||||
|  | 			} | ||||||
|  | 			logger.information(Poco::format("System ID set to %Lu",ID_)); | ||||||
|  | 			StartSubSystemServers(); | ||||||
|  | 			waitForTerminationRequest(); | ||||||
|  | 			StopSubSystemServers(); | ||||||
|  |  | ||||||
|  | 			logger.notice(Poco::format("Stopped %s...",DAEMON_APP_NAME)); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		return Application::EXIT_OK; | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										183
									
								
								src/MicroService.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										183
									
								
								src/MicroService.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,183 @@ | |||||||
|  | // | ||||||
|  | //	License type: BSD 3-Clause License | ||||||
|  | //	License copy: https://github.com/Telecominfraproject/wlan-cloud-ucentralgw/blob/master/LICENSE | ||||||
|  | // | ||||||
|  | //	Created by Stephane Bourque on 2021-03-04. | ||||||
|  | //	Arilia Wireless Inc. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef UCENTRALGW_MICROSERVICE_H | ||||||
|  | #define UCENTRALGW_MICROSERVICE_H | ||||||
|  |  | ||||||
|  | #include <array> | ||||||
|  | #include <iostream> | ||||||
|  | #include <cstdlib> | ||||||
|  | #include <vector> | ||||||
|  | #include <set> | ||||||
|  |  | ||||||
|  | #include "Poco/Util/Application.h" | ||||||
|  | #include "Poco/Util/ServerApplication.h" | ||||||
|  | #include "Poco/Util/Option.h" | ||||||
|  | #include "Poco/Util/OptionSet.h" | ||||||
|  | #include "Poco/UUIDGenerator.h" | ||||||
|  | #include "Poco/ErrorHandler.h" | ||||||
|  | #include "Poco/Crypto/RSAKey.h" | ||||||
|  | #include "Poco/Crypto/CipherFactory.h" | ||||||
|  | #include "Poco/Crypto/Cipher.h" | ||||||
|  | #include "Poco/SHA2Engine.h" | ||||||
|  | #include "Poco/Net/HTTPServerRequest.h" | ||||||
|  | #include "Poco/Process.h" | ||||||
|  |  | ||||||
|  | #include "OpenWifiTypes.h" | ||||||
|  | #include "SubSystemServer.h" | ||||||
|  |  | ||||||
|  | namespace OpenWifi { | ||||||
|  |  | ||||||
|  | 	static const std::string uSERVICE_SECURITY{"owsec"}; | ||||||
|  | 	static const std::string uSERVICE_GATEWAY{"owgw"}; | ||||||
|  | 	static const std::string uSERVICE_FIRMWARE{ "owfms"}; | ||||||
|  |     static const std::string uSERVICE_TOPOLOGY{ "owtopo"}; | ||||||
|  |     static const std::string uSERVICE_PROVISIONING{ "owprov"}; | ||||||
|  |     static const std::string uSERVICE_OWLS{ "owls"}; | ||||||
|  |  | ||||||
|  | 	class MyErrorHandler : public Poco::ErrorHandler { | ||||||
|  | 	  public: | ||||||
|  | 		explicit MyErrorHandler(Poco::Util::Application &App) : App_(App) {} | ||||||
|  | 		void exception(const Poco::Exception & E) override; | ||||||
|  | 		void exception(const std::exception & E) override; | ||||||
|  | 		void exception() override; | ||||||
|  | 	  private: | ||||||
|  | 		Poco::Util::Application	&App_; | ||||||
|  | 	}; | ||||||
|  |  | ||||||
|  | 	class BusEventManager : public Poco::Runnable { | ||||||
|  | 	  public: | ||||||
|  | 		void run() override; | ||||||
|  | 		void Start(); | ||||||
|  | 		void Stop(); | ||||||
|  | 	  private: | ||||||
|  | 		std::atomic_bool 	Running_ = false; | ||||||
|  | 		Poco::Thread		Thread_; | ||||||
|  | 	}; | ||||||
|  |  | ||||||
|  | 	struct MicroServiceMeta { | ||||||
|  | 		uint64_t 		Id=0; | ||||||
|  | 		std::string 	Type; | ||||||
|  | 		std::string 	PrivateEndPoint; | ||||||
|  | 		std::string 	PublicEndPoint; | ||||||
|  | 		std::string 	AccessKey; | ||||||
|  | 		std::string		Version; | ||||||
|  | 		uint64_t 		LastUpdate=0; | ||||||
|  | 	}; | ||||||
|  |  | ||||||
|  | 	typedef std::map<uint64_t, MicroServiceMeta>	MicroServiceMetaMap; | ||||||
|  | 	typedef std::vector<MicroServiceMeta>			MicroServiceMetaVec; | ||||||
|  |  | ||||||
|  | 	class MicroService : public Poco::Util::ServerApplication { | ||||||
|  | 	  public: | ||||||
|  | 		explicit MicroService( 	std::string PropFile, | ||||||
|  | 					 	std::string RootEnv, | ||||||
|  | 					 	std::string ConfigVar, | ||||||
|  | 					 	std::string AppName, | ||||||
|  | 					  	uint64_t BusTimer, | ||||||
|  | 					  	Types::SubSystemVec Subsystems) : | ||||||
|  | 			DAEMON_PROPERTIES_FILENAME(std::move(PropFile)), | ||||||
|  | 			DAEMON_ROOT_ENV_VAR(std::move(RootEnv)), | ||||||
|  | 			DAEMON_CONFIG_ENV_VAR(std::move(ConfigVar)), | ||||||
|  | 			DAEMON_APP_NAME(std::move(AppName)), | ||||||
|  | 			DAEMON_BUS_TIMER(BusTimer), | ||||||
|  | 			SubSystems_(std::move(Subsystems)) { | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		int main(const ArgVec &args) override; | ||||||
|  | 		void initialize(Application &self) override; | ||||||
|  | 		void uninitialize() override; | ||||||
|  | 		void reinitialize(Application &self) override; | ||||||
|  | 		void defineOptions(Poco::Util::OptionSet &options) override; | ||||||
|  | 		void handleHelp(const std::string &name, const std::string &value); | ||||||
|  | 		void handleVersion(const std::string &name, const std::string &value); | ||||||
|  | 		void handleDebug(const std::string &name, const std::string &value); | ||||||
|  | 		void handleLogs(const std::string &name, const std::string &value); | ||||||
|  | 		void handleConfig(const std::string &name, const std::string &value); | ||||||
|  | 		void displayHelp(); | ||||||
|  |  | ||||||
|  | 		void InitializeSubSystemServers(); | ||||||
|  | 		void StartSubSystemServers(); | ||||||
|  | 		void StopSubSystemServers(); | ||||||
|  | 		void Exit(int Reason); | ||||||
|  | 		bool SetSubsystemLogLevel(const std::string & SubSystem, const std::string & Level); | ||||||
|  | 		[[nodiscard]] std::string Version() { return Version_; } | ||||||
|  | 		[[nodiscard]] const Poco::SharedPtr<Poco::Crypto::RSAKey> & Key() { return AppKey_; } | ||||||
|  | 		[[nodiscard]] inline const std::string & DataDir() { return DataDir_; } | ||||||
|  | 		[[nodiscard]] std::string CreateUUID(); | ||||||
|  | 		[[nodiscard]] bool Debug() const { return DebugMode_; } | ||||||
|  | 		[[nodiscard]] uint64_t ID() const { return ID_; } | ||||||
|  | 		[[nodiscard]] Types::StringVec GetSubSystems() const; | ||||||
|  | 		[[nodiscard]] Types::StringPairVec GetLogLevels() ; | ||||||
|  | 		[[nodiscard]] static const Types::StringVec & GetLogLevelNames(); | ||||||
|  | 		[[nodiscard]] std::string ConfigGetString(const std::string &Key,const std::string & Default); | ||||||
|  | 		[[nodiscard]] std::string ConfigGetString(const std::string &Key); | ||||||
|  | 		[[nodiscard]] std::string ConfigPath(const std::string &Key,const std::string & Default); | ||||||
|  | 		[[nodiscard]] std::string ConfigPath(const std::string &Key); | ||||||
|  | 		[[nodiscard]] uint64_t ConfigGetInt(const std::string &Key,uint64_t Default); | ||||||
|  | 		[[nodiscard]] uint64_t ConfigGetInt(const std::string &Key); | ||||||
|  | 		[[nodiscard]] uint64_t ConfigGetBool(const std::string &Key,bool Default); | ||||||
|  | 		[[nodiscard]] uint64_t ConfigGetBool(const std::string &Key); | ||||||
|  | 		[[nodiscard]] std::string Encrypt(const std::string &S); | ||||||
|  | 		[[nodiscard]] std::string Decrypt(const std::string &S); | ||||||
|  | 		[[nodiscard]] std::string CreateHash(const std::string &S); | ||||||
|  | 		[[nodiscard]] std::string Hash() const { return MyHash_; }; | ||||||
|  | 		[[nodiscard]] std::string ServiceType() const { return DAEMON_APP_NAME; }; | ||||||
|  | 		[[nodiscard]] std::string PrivateEndPoint() const { return MyPrivateEndPoint_; }; | ||||||
|  | 		[[nodiscard]] std::string PublicEndPoint() const { return MyPublicEndPoint_; }; | ||||||
|  | 		[[nodiscard]] std::string MakeSystemEventMessage( const std::string & Type ) const ; | ||||||
|  | 		[[nodiscard]] const Types::SubSystemVec & GetFullSubSystems() { return SubSystems_; } | ||||||
|  | 		inline uint64_t DaemonBusTimer() const { return DAEMON_BUS_TIMER; }; | ||||||
|  |  | ||||||
|  | 		void BusMessageReceived( const std::string & Key, const std::string & Message); | ||||||
|  | 		[[nodiscard]] MicroServiceMetaVec GetServices(const std::string & type); | ||||||
|  | 		[[nodiscard]] MicroServiceMetaVec GetServices(); | ||||||
|  | 		[[nodiscard]] bool IsValidAPIKEY(const Poco::Net::HTTPServerRequest &Request); | ||||||
|  |  | ||||||
|  | 		static void SavePID(); | ||||||
|  | 		static inline uint64_t GetPID() { return Poco::Process::id(); }; | ||||||
|  | 		[[nodiscard]] inline const std::string GetPublicAPIEndPoint() { return MyPublicEndPoint_ + "/api/v1"; }; | ||||||
|  | 		[[nodiscard]] inline const std::string & GetUIURI() const { return UIURI_;}; | ||||||
|  |  | ||||||
|  | 		void Reload(const std::string &Name);   //  reload a subsystem | ||||||
|  | 		void Reload();                          //  reload the daemon itself | ||||||
|  | 		void LoadMyConfig(); | ||||||
|  |  | ||||||
|  | 		void LoadConfigurationFile(); | ||||||
|  |  | ||||||
|  | 	  private: | ||||||
|  | 		bool                        HelpRequested_ = false; | ||||||
|  | 		std::string                 LogDir_; | ||||||
|  | 		std::string                 ConfigFileName_; | ||||||
|  | 		Poco::UUIDGenerator         UUIDGenerator_; | ||||||
|  | 		uint64_t                    ID_ = 1; | ||||||
|  | 		Poco::SharedPtr<Poco::Crypto::RSAKey>	AppKey_ = nullptr; | ||||||
|  | 		bool                        DebugMode_ = false; | ||||||
|  | 		std::string 				DataDir_; | ||||||
|  | 		Types::SubSystemVec			SubSystems_; | ||||||
|  | 		Poco::Crypto::CipherFactory & CipherFactory_ = Poco::Crypto::CipherFactory::defaultFactory(); | ||||||
|  | 		Poco::Crypto::Cipher        * Cipher_ = nullptr; | ||||||
|  | 		Poco::SHA2Engine			SHA2_; | ||||||
|  | 		MicroServiceMetaMap			Services_; | ||||||
|  | 		std::string 				MyHash_; | ||||||
|  | 		std::string 				MyPrivateEndPoint_; | ||||||
|  | 		std::string 				MyPublicEndPoint_; | ||||||
|  | 		std::string                 UIURI_; | ||||||
|  | 		std::string 				Version_{std::string(APP_VERSION) + "("+ BUILD_NUMBER + ")"}; | ||||||
|  | 		BusEventManager				BusEventManager_; | ||||||
|  | 		std::mutex 					InfraMutex_; | ||||||
|  |  | ||||||
|  | 		std::string DAEMON_PROPERTIES_FILENAME; | ||||||
|  | 		std::string DAEMON_ROOT_ENV_VAR; | ||||||
|  | 		std::string DAEMON_CONFIG_ENV_VAR; | ||||||
|  | 		std::string DAEMON_APP_NAME; | ||||||
|  | 		uint64_t 	DAEMON_BUS_TIMER; | ||||||
|  | 	}; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #endif // UCENTRALGW_MICROSERVICE_H | ||||||
							
								
								
									
										71
									
								
								src/OpenAPIRequest.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								src/OpenAPIRequest.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,71 @@ | |||||||
|  | // | ||||||
|  | //	License type: BSD 3-Clause License | ||||||
|  | //	License copy: https://github.com/Telecominfraproject/wlan-cloud-ucentralgw/blob/master/LICENSE | ||||||
|  | // | ||||||
|  | //	Created by Stephane Bourque on 2021-03-04. | ||||||
|  | //	Arilia Wireless Inc. | ||||||
|  | // | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #include <iostream> | ||||||
|  |  | ||||||
|  | #include "OpenAPIRequest.h" | ||||||
|  |  | ||||||
|  | #include "Poco/Net/HTTPSClientSession.h" | ||||||
|  | #include <Poco/Net/HTTPRequest.h> | ||||||
|  | #include <Poco/Net/HTTPResponse.h> | ||||||
|  | #include <Poco/JSON/Parser.h> | ||||||
|  | #include <Poco/URI.h> | ||||||
|  | #include <Poco/Exception.h> | ||||||
|  | #include "Utils.h" | ||||||
|  | #include "Daemon.h" | ||||||
|  |  | ||||||
|  | namespace OpenWifi { | ||||||
|  |  | ||||||
|  | 	OpenAPIRequestGet::OpenAPIRequestGet( 	std::string ServiceType, | ||||||
|  | 											std::string EndPoint, | ||||||
|  | 									 		Types::StringPairVec & QueryData, | ||||||
|  | 											uint64_t msTimeout): | ||||||
|  |  		Type_(std::move(ServiceType)), | ||||||
|  |  		EndPoint_(std::move(EndPoint)), | ||||||
|  | 		QueryData_(QueryData), | ||||||
|  | 		msTimeout_(msTimeout) { | ||||||
|  |  | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	int OpenAPIRequestGet::Do(Poco::JSON::Object::Ptr &ResponseObject) { | ||||||
|  | 		try { | ||||||
|  | 		    auto Services = Daemon()->GetServices(Type_); | ||||||
|  | 			for(auto const &Svc:Services) { | ||||||
|  | 				Poco::URI	URI(Svc.PrivateEndPoint); | ||||||
|  | 				Poco::Net::HTTPSClientSession Session(URI.getHost(), URI.getPort()); | ||||||
|  |  | ||||||
|  | 				URI.setPath(EndPoint_); | ||||||
|  | 				for (const auto &qp : QueryData_) | ||||||
|  | 					URI.addQueryParameter(qp.first, qp.second); | ||||||
|  |  | ||||||
|  | 				std::string Path(URI.getPathAndQuery()); | ||||||
|  | 				Session.setTimeout(Poco::Timespan(msTimeout_/1000, msTimeout_ % 1000)); | ||||||
|  |  | ||||||
|  | 				Poco::Net::HTTPRequest Request(Poco::Net::HTTPRequest::HTTP_GET, | ||||||
|  | 											   Path, | ||||||
|  | 											   Poco::Net::HTTPMessage::HTTP_1_1); | ||||||
|  | 				Request.add("X-API-KEY", Svc.AccessKey); | ||||||
|  | 				Session.sendRequest(Request); | ||||||
|  |  | ||||||
|  | 				Poco::Net::HTTPResponse Response; | ||||||
|  | 				std::istream &is = Session.receiveResponse(Response); | ||||||
|  | 				if(Response.getStatus()==Poco::Net::HTTPResponse::HTTP_OK) { | ||||||
|  | 					Poco::JSON::Parser	P; | ||||||
|  | 					ResponseObject = P.parse(is).extract<Poco::JSON::Object::Ptr>(); | ||||||
|  | 				} | ||||||
|  | 				return Response.getStatus(); | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 		catch (const Poco::Exception &E) | ||||||
|  | 		{ | ||||||
|  | 			std::cerr << E.displayText() << std::endl; | ||||||
|  | 		} | ||||||
|  | 		return -1; | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										33
									
								
								src/OpenAPIRequest.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								src/OpenAPIRequest.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,33 @@ | |||||||
|  | // | ||||||
|  | //	License type: BSD 3-Clause License | ||||||
|  | //	License copy: https://github.com/Telecominfraproject/wlan-cloud-ucentralgw/blob/master/LICENSE | ||||||
|  | // | ||||||
|  | //	Created by Stephane Bourque on 2021-03-04. | ||||||
|  | //	Arilia Wireless Inc. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef UCENTRALGW_OPENAPIREQUEST_H | ||||||
|  | #define UCENTRALGW_OPENAPIREQUEST_H | ||||||
|  |  | ||||||
|  | #include "Poco/JSON/Object.h" | ||||||
|  |  | ||||||
|  | #include "OpenWifiTypes.h" | ||||||
|  |  | ||||||
|  | namespace OpenWifi { | ||||||
|  |  | ||||||
|  | 	class OpenAPIRequestGet { | ||||||
|  | 	  public: | ||||||
|  | 		explicit OpenAPIRequestGet( std::string Type, | ||||||
|  | 								   	std::string EndPoint, | ||||||
|  | 									Types::StringPairVec & QueryData, | ||||||
|  | 									uint64_t msTimeout); | ||||||
|  | 		int Do(Poco::JSON::Object::Ptr &ResponseObject); | ||||||
|  | 	  private: | ||||||
|  | 		std::string 			Type_; | ||||||
|  | 		std::string 			EndPoint_; | ||||||
|  | 		Types::StringPairVec 	QueryData_; | ||||||
|  | 		uint64_t 				msTimeout_; | ||||||
|  | 	}; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #endif // UCENTRALGW_OPENAPIREQUEST_H | ||||||
							
								
								
									
										106
									
								
								src/OpenWifiTypes.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										106
									
								
								src/OpenWifiTypes.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,106 @@ | |||||||
|  | // | ||||||
|  | //	License type: BSD 3-Clause License | ||||||
|  | //	License copy: https://github.com/Telecominfraproject/wlan-cloud-ucentralgw/blob/master/LICENSE | ||||||
|  | // | ||||||
|  | //	Created by Stephane Bourque on 2021-03-04. | ||||||
|  | //	Arilia Wireless Inc. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef UCENTRALGW_UCENTRALTYPES_H | ||||||
|  | #define UCENTRALGW_UCENTRALTYPES_H | ||||||
|  |  | ||||||
|  | #include "SubSystemServer.h" | ||||||
|  |  | ||||||
|  | #include <vector> | ||||||
|  | #include <string> | ||||||
|  | #include <map> | ||||||
|  | #include <functional> | ||||||
|  | #include <list> | ||||||
|  | #include <utility> | ||||||
|  | #include <queue> | ||||||
|  |  | ||||||
|  | #include "Poco/StringTokenizer.h" | ||||||
|  | #include "Poco/JSON/Parser.h" | ||||||
|  | #include "Poco/JSON/Stringifier.h" | ||||||
|  |  | ||||||
|  | namespace OpenWifi::Types { | ||||||
|  |     typedef std::pair<std::string,std::string>              StringPair; | ||||||
|  | 	typedef std::vector<StringPair>	                        StringPairVec; | ||||||
|  |     typedef std::queue<StringPair>	                        StringPairQueue; | ||||||
|  | 	typedef std::vector<std::string>						StringVec; | ||||||
|  | 	typedef std::set<std::string>                           StringSet; | ||||||
|  | 	typedef std::vector<SubSystemServer*>					SubSystemVec; | ||||||
|  | 	typedef std::map<std::string,std::set<std::string>>		StringMapStringSet; | ||||||
|  | 	typedef std::function<void(std::string, std::string)>   TopicNotifyFunction; | ||||||
|  | 	typedef std::list<std::pair<TopicNotifyFunction,int>>   TopicNotifyFunctionList; | ||||||
|  | 	typedef std::map<std::string, TopicNotifyFunctionList>  NotifyTable; | ||||||
|  |     typedef std::map<std::string,uint64_t>                  CountedMap; | ||||||
|  |  | ||||||
|  |     typedef std::string         UUID_t; | ||||||
|  |     typedef std::vector<UUID_t> UUIDvec_t; | ||||||
|  |  | ||||||
|  |     inline void UpdateCountedMap(CountedMap &M, const std::string &S, uint64_t Increment=1) { | ||||||
|  |         auto it = M.find(S); | ||||||
|  |         if(it==M.end()) | ||||||
|  |             M[S] = Increment; | ||||||
|  |         else | ||||||
|  |             it->second += Increment; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     inline std::string to_string( const StringVec &V) { | ||||||
|  |         Poco::JSON::Array   O; | ||||||
|  |         for(const auto &i:V) { | ||||||
|  |             O.add(i); | ||||||
|  |         } | ||||||
|  |         std::stringstream SS; | ||||||
|  |         Poco::JSON::Stringifier::stringify(O,SS); | ||||||
|  |         return SS.str(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     inline std::string to_string( const StringPairVec &V) { | ||||||
|  |         Poco::JSON::Array   O; | ||||||
|  |         for(const auto &i:V) { | ||||||
|  |             Poco::JSON::Array OO; | ||||||
|  |             OO.add(i.first); | ||||||
|  |             OO.add(i.second); | ||||||
|  |             O.add(OO); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         std::stringstream SS; | ||||||
|  |         Poco::JSON::Stringifier::stringify(O,SS); | ||||||
|  |         return SS.str(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     inline void from_string(const std::string &S, StringPairVec &V) { | ||||||
|  |         try { | ||||||
|  |             Poco::JSON::Parser      P; | ||||||
|  |             auto O = P.parse(S).extract<Poco::JSON::Array::Ptr>(); | ||||||
|  |  | ||||||
|  |             for(const auto &i:*O) { | ||||||
|  |                 auto Inner = i.extract<Poco::JSON::Array::Ptr>(); | ||||||
|  |                 for(const auto &j:*Inner) { | ||||||
|  |                     auto S1 = i[0].toString(); | ||||||
|  |                     auto S2 = i[1].toString(); | ||||||
|  |                     V.push_back(std::make_pair(S1,S2)); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } catch (...) { | ||||||
|  |  | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     inline void from_string(const std::string &S, StringVec &V) { | ||||||
|  |         try { | ||||||
|  |             Poco::JSON::Parser      P; | ||||||
|  |             auto O = P.parse(S).extract<Poco::JSON::Array::Ptr>(); | ||||||
|  |  | ||||||
|  |             for(auto const &i:*O) { | ||||||
|  |                 V.push_back(i.toString()); | ||||||
|  |             } | ||||||
|  |         } catch (...) { | ||||||
|  |  | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | #endif // UCENTRALGW_UCENTRALTYPES_H | ||||||
| @@ -1,265 +0,0 @@ | |||||||
| // |  | ||||||
| // Created by stephane bourque on 2021-06-22. |  | ||||||
| // |  | ||||||
|  |  | ||||||
| #include "Poco/JSON/Parser.h" |  | ||||||
| #include "Poco/Net/HTMLForm.h" |  | ||||||
|  |  | ||||||
| #include "RESTAPI_action_links.h" |  | ||||||
| #include "StorageService.h" |  | ||||||
| #include "framework/MicroService.h" |  | ||||||
| #include "Daemon.h" |  | ||||||
|  |  | ||||||
| namespace OpenWifi { |  | ||||||
|  |  | ||||||
|     void RESTAPI_action_links::DoGet() { |  | ||||||
|  |  | ||||||
|         auto Action = GetParameter("action",""); |  | ||||||
|         auto Id = GetParameter("id",""); |  | ||||||
|  |  | ||||||
|         SecurityObjects::ActionLink Link; |  | ||||||
|         if(!StorageService()->ActionLinksDB().GetActionLink(Id,Link)) |  | ||||||
|             return DoReturnA404(); |  | ||||||
|  |  | ||||||
|         if(Action=="password_reset") |  | ||||||
|             return RequestResetPassword(Link); |  | ||||||
|         else if(Action=="email_verification") |  | ||||||
|             return DoEmailVerification(Link); |  | ||||||
|         else if(Action=="signup_verification") |  | ||||||
|             return DoNewSubVerification(Link); |  | ||||||
|         else |  | ||||||
|             return DoReturnA404(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     void RESTAPI_action_links::DoPost() { |  | ||||||
|         auto Action = GetParameter("action",""); |  | ||||||
|  |  | ||||||
|         if(Action=="password_reset") |  | ||||||
|             return CompleteResetPassword(); |  | ||||||
|         else if(Action=="signup_completion") |  | ||||||
|             return CompleteSubVerification(); |  | ||||||
|         else |  | ||||||
|             return DoReturnA404(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     void RESTAPI_action_links::RequestResetPassword(SecurityObjects::ActionLink &Link) { |  | ||||||
|         Logger_.information(fmt::format("REQUEST-PASSWORD-RESET({}): For ID={}", Request->clientAddress().toString(), Link.userId)); |  | ||||||
|         Poco::File  FormFile{ Daemon()->AssetDir() + "/password_reset.html"}; |  | ||||||
|         Types::StringPairVec    FormVars{ {"UUID", Link.id}, |  | ||||||
|                                           {"PASSWORD_VALIDATION", AuthService()->PasswordValidationExpression()}}; |  | ||||||
|         SendHTMLFileBack(FormFile,FormVars); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     void RESTAPI_action_links::DoNewSubVerification(SecurityObjects::ActionLink &Link) { |  | ||||||
|         Logger_.information(fmt::format("REQUEST-SUB-SIGNUP({}): For ID={}", Request->clientAddress().toString(), Link.userId)); |  | ||||||
|         Poco::File  FormFile{ Daemon()->AssetDir() + "/signup_verification.html"}; |  | ||||||
|         Types::StringPairVec    FormVars{ {"UUID", Link.id}, |  | ||||||
|                                           {"PASSWORD_VALIDATION", AuthService()->PasswordValidationExpression()}}; |  | ||||||
|         SendHTMLFileBack(FormFile,FormVars); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     void RESTAPI_action_links::CompleteResetPassword() { |  | ||||||
|         //  form has been posted... |  | ||||||
|         RESTAPI_PartHandler PartHandler; |  | ||||||
|         Poco::Net::HTMLForm Form(*Request, Request->stream(), PartHandler); |  | ||||||
|         if (!Form.empty()) { |  | ||||||
|  |  | ||||||
|             auto Password1 = Form.get("password1","bla"); |  | ||||||
|             auto Password2 = Form.get("password2","blu"); |  | ||||||
|             auto Id = Form.get("id",""); |  | ||||||
|             auto now = OpenWifi::Now(); |  | ||||||
|  |  | ||||||
|             SecurityObjects::ActionLink Link; |  | ||||||
|             if(!StorageService()->ActionLinksDB().GetActionLink(Id,Link)) |  | ||||||
|                 return DoReturnA404(); |  | ||||||
|  |  | ||||||
|             if(now > Link.expires) { |  | ||||||
|                 StorageService()->ActionLinksDB().CancelAction(Id); |  | ||||||
|                 return DoReturnA404(); |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             if(Password1!=Password2 || !AuthService()->ValidatePassword(Password2) || !AuthService()->ValidatePassword(Password1)) { |  | ||||||
|                 Poco::File  FormFile{ Daemon()->AssetDir() + "/password_reset_error.html"}; |  | ||||||
|                 Types::StringPairVec    FormVars{ {"UUID", Id}, |  | ||||||
|                                                   {"ERROR_TEXT", "For some reason, the passwords entered do not match or they do not comply with" |  | ||||||
|                                                                  " accepted password creation restrictions. Please consult our on-line help" |  | ||||||
|                                                                  " to look at the our password policy. If you would like to contact us, please mention" |  | ||||||
|                                                                  " id(" + Id + ")"}}; |  | ||||||
|                 return SendHTMLFileBack(FormFile,FormVars); |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             SecurityObjects::UserInfo   UInfo; |  | ||||||
|  |  | ||||||
|             bool Found = Link.userAction ? StorageService()->UserDB().GetUserById(Link.userId,UInfo) : StorageService()->SubDB().GetUserById(Link.userId,UInfo); |  | ||||||
|             if(!Found) { |  | ||||||
|                 Poco::File  FormFile{ Daemon()->AssetDir() + "/password_reset_error.html"}; |  | ||||||
|                 Types::StringPairVec    FormVars{ {"UUID", Id}, |  | ||||||
|                                                   {"ERROR_TEXT", "This request does not contain a valid user ID. Please contact your system administrator."}}; |  | ||||||
|                 return SendHTMLFileBack(FormFile,FormVars); |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             if(UInfo.blackListed || UInfo.suspended) { |  | ||||||
|                 Poco::File  FormFile{ Daemon()->AssetDir() + "/password_reset_error.html"}; |  | ||||||
|                 Types::StringPairVec    FormVars{ {"UUID", Id}, |  | ||||||
|                                                   {"ERROR_TEXT", "Please contact our system administrators. We have identified an error in your account that must be resolved first."}}; |  | ||||||
|                 return SendHTMLFileBack(FormFile,FormVars); |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             bool GoodPassword = Link.userAction ? AuthService()->SetPassword(Password1,UInfo) : AuthService()->SetSubPassword(Password1,UInfo); |  | ||||||
|             if(!GoodPassword) { |  | ||||||
|                 Poco::File  FormFile{ Daemon()->AssetDir() + "/password_reset_error.html"}; |  | ||||||
|                 Types::StringPairVec    FormVars{ {"UUID", Id}, |  | ||||||
|                                                   {"ERROR_TEXT", "You cannot reuse one of your recent passwords."}}; |  | ||||||
|                 return SendHTMLFileBack(FormFile,FormVars); |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             UInfo.modified = OpenWifi::Now(); |  | ||||||
|             if(Link.userAction) |  | ||||||
|                 StorageService()->UserDB().UpdateUserInfo(UInfo.email,Link.userId,UInfo); |  | ||||||
|             else |  | ||||||
|                 StorageService()->SubDB().UpdateUserInfo(UInfo.email,Link.userId,UInfo); |  | ||||||
|  |  | ||||||
|             Poco::File  FormFile{ Daemon()->AssetDir() + "/password_reset_success.html"}; |  | ||||||
|             Types::StringPairVec    FormVars{ {"UUID", Id}, |  | ||||||
|                                               {"USERNAME", UInfo.email}, |  | ||||||
|                                               {"ACTION_LINK",MicroService::instance().GetUIURI()}}; |  | ||||||
|             StorageService()->ActionLinksDB().CompleteAction(Id); |  | ||||||
|             SendHTMLFileBack(FormFile,FormVars); |  | ||||||
|         } else { |  | ||||||
|             DoReturnA404(); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     void RESTAPI_action_links::CompleteSubVerification() { |  | ||||||
|         RESTAPI_PartHandler PartHandler; |  | ||||||
|         Poco::Net::HTMLForm Form(*Request, Request->stream(), PartHandler); |  | ||||||
|  |  | ||||||
|         if (!Form.empty()) { |  | ||||||
|             auto Password1 = Form.get("password1","bla"); |  | ||||||
|             auto Password2 = Form.get("password2","blu"); |  | ||||||
|             auto Id = Form.get("id",""); |  | ||||||
|             auto now = OpenWifi::Now(); |  | ||||||
|  |  | ||||||
|             SecurityObjects::ActionLink Link; |  | ||||||
|             if(!StorageService()->ActionLinksDB().GetActionLink(Id,Link)) { |  | ||||||
|                 return DoReturnA404(); |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             if(now > Link.expires) { |  | ||||||
|                 StorageService()->ActionLinksDB().CancelAction(Id); |  | ||||||
|                 return DoReturnA404(); |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             if(Password1!=Password2 || !AuthService()->ValidateSubPassword(Password1)) { |  | ||||||
|                 Poco::File  FormFile{ Daemon()->AssetDir() + "/password_reset_error.html"}; |  | ||||||
|                 Types::StringPairVec    FormVars{ {"UUID", Id}, |  | ||||||
|                                                   {"ERROR_TEXT", "For some reason, the passwords entered do not match or they do not comply with" |  | ||||||
|                                                                  " accepted password creation restrictions. Please consult our on-line help" |  | ||||||
|                                                                  " to look at the our password policy. If you would like to contact us, please mention" |  | ||||||
|                                                                  " id(" + Id + ")"}}; |  | ||||||
|                 return SendHTMLFileBack(FormFile,FormVars); |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             SecurityObjects::UserInfo   UInfo; |  | ||||||
|             bool Found = StorageService()->SubDB().GetUserById(Link.userId,UInfo); |  | ||||||
|             if(!Found) { |  | ||||||
|                 Poco::File  FormFile{ Daemon()->AssetDir() + "/signup_verification_error.html"}; |  | ||||||
|                 Types::StringPairVec    FormVars{ {"UUID", Id}, |  | ||||||
|                                                   {"ERROR_TEXT", "This request does not contain a valid user ID. Please contact your system administrator."}}; |  | ||||||
|                 return SendHTMLFileBack(FormFile,FormVars); |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             if(UInfo.blackListed || UInfo.suspended) { |  | ||||||
|                 Poco::File  FormFile{ Daemon()->AssetDir() + "/signup_verification_error.html"}; |  | ||||||
|                 Types::StringPairVec    FormVars{ {"UUID", Id}, |  | ||||||
|                                                   {"ERROR_TEXT", "Please contact our system administrators. We have identified an error in your account that must be resolved first."}}; |  | ||||||
|                 return SendHTMLFileBack(FormFile,FormVars); |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             bool GoodPassword = AuthService()->SetSubPassword(Password1,UInfo); |  | ||||||
|             if(!GoodPassword) { |  | ||||||
|                 Poco::File  FormFile{ Daemon()->AssetDir() + "/signup_verification_error.html"}; |  | ||||||
|                 Types::StringPairVec    FormVars{ {"UUID", Id}, |  | ||||||
|                                                   {"ERROR_TEXT", "You cannot reuse one of your recent passwords."}}; |  | ||||||
|                 return SendHTMLFileBack(FormFile,FormVars); |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             UInfo.modified = OpenWifi::Now(); |  | ||||||
|             UInfo.changePassword = false; |  | ||||||
|             UInfo.lastEmailCheck = OpenWifi::Now(); |  | ||||||
|             UInfo.waitingForEmailCheck = false; |  | ||||||
|             UInfo.validated = OpenWifi::Now(); |  | ||||||
|  |  | ||||||
|             StorageService()->SubDB().UpdateUserInfo(UInfo.email,Link.userId,UInfo); |  | ||||||
|  |  | ||||||
|             Poco::File  FormFile{ Daemon()->AssetDir() + "/signup_verification_success.html"}; |  | ||||||
|             Types::StringPairVec    FormVars{ {"UUID", Id}, |  | ||||||
|                                               {"USERNAME", UInfo.email} }; |  | ||||||
|             StorageService()->ActionLinksDB().CompleteAction(Id); |  | ||||||
|  |  | ||||||
|             //  Send the update to the provisioning service |  | ||||||
|             Poco::JSON::Object  Body; |  | ||||||
|             Body.set("signupUUID", UInfo.signingUp); |  | ||||||
|             OpenAPIRequestPut   ProvRequest(uSERVICE_PROVISIONING,"/api/v1/signup", |  | ||||||
|                                             { |  | ||||||
|                                                 {"signupUUID", UInfo.signingUp} , |  | ||||||
|                                                 {"operation", "emailVerified"} |  | ||||||
|                                             }, |  | ||||||
|                                             Body,30000); |  | ||||||
|             Logger().information(fmt::format("({}): Completed subscriber e-mail verification and password.",UInfo.email)); |  | ||||||
|             Poco::JSON::Object::Ptr Response; |  | ||||||
|             auto Status = ProvRequest.Do(Response); |  | ||||||
|             std::stringstream ooo; |  | ||||||
|             if(Response!= nullptr) |  | ||||||
|                 Response->stringify(ooo); |  | ||||||
|             Logger().information(fmt::format("({}): Completed subscriber e-mail verification. Provisioning notified, Error={}.", |  | ||||||
|                                              UInfo.email, Status)); |  | ||||||
|             SendHTMLFileBack(FormFile,FormVars); |  | ||||||
|             Logger().information(fmt::format("({}): Completed subscriber e-mail verification. FORM notified.",UInfo.email)); |  | ||||||
|         } else { |  | ||||||
|             DoReturnA404(); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     void RESTAPI_action_links::DoEmailVerification(SecurityObjects::ActionLink &Link) { |  | ||||||
|         auto now = OpenWifi::Now(); |  | ||||||
|  |  | ||||||
|         if(now > Link.expires) { |  | ||||||
|             StorageService()->ActionLinksDB().CancelAction(Link.id); |  | ||||||
|             return DoReturnA404(); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         SecurityObjects::UserInfo UInfo; |  | ||||||
|         bool Found = Link.userAction ? StorageService()->UserDB().GetUserById(Link.userId,UInfo) : StorageService()->SubDB().GetUserById(Link.userId,UInfo); |  | ||||||
|         if (!Found) { |  | ||||||
|             Types::StringPairVec FormVars{{"UUID",       Link.id}, |  | ||||||
|                                           {"ERROR_TEXT", "This does not appear to be a valid email verification link.."}}; |  | ||||||
|             Poco::File FormFile{Daemon()->AssetDir() + "/email_verification_error.html"}; |  | ||||||
|             return SendHTMLFileBack(FormFile, FormVars); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         Logger_.information(fmt::format("EMAIL-VERIFICATION(%s): For ID={}", Request->clientAddress().toString(), UInfo.email)); |  | ||||||
|         UInfo.waitingForEmailCheck = false; |  | ||||||
|         UInfo.validated = true; |  | ||||||
|         UInfo.lastEmailCheck = OpenWifi::Now(); |  | ||||||
|         UInfo.validationDate = OpenWifi::Now(); |  | ||||||
|         UInfo.modified  = OpenWifi::Now(); |  | ||||||
|         if(Link.userAction) |  | ||||||
|             StorageService()->UserDB().UpdateUserInfo(UInfo.email, Link.userId, UInfo); |  | ||||||
|         else |  | ||||||
|             StorageService()->SubDB().UpdateUserInfo(UInfo.email, Link.userId, UInfo); |  | ||||||
|         Types::StringPairVec FormVars{{"UUID",     Link.id}, |  | ||||||
|                                       {"USERNAME", UInfo.email}, |  | ||||||
|                                       {"ACTION_LINK",MicroService::instance().GetUIURI()}}; |  | ||||||
|         Poco::File FormFile{Daemon()->AssetDir() + "/email_verification_success.html"}; |  | ||||||
|         StorageService()->ActionLinksDB().CompleteAction(Link.id); |  | ||||||
|         SendHTMLFileBack(FormFile, FormVars); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     void RESTAPI_action_links::DoReturnA404() { |  | ||||||
|         Types::StringPairVec FormVars; |  | ||||||
|         Poco::File FormFile{Daemon()->AssetDir() + "/404_error.html"}; |  | ||||||
|         SendHTMLFileBack(FormFile, FormVars); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
| } |  | ||||||
| @@ -1,36 +0,0 @@ | |||||||
| // |  | ||||||
| // Created by stephane bourque on 2021-06-22. |  | ||||||
| // |  | ||||||
|  |  | ||||||
| #pragma once |  | ||||||
|  |  | ||||||
| #include "framework/MicroService.h" |  | ||||||
|  |  | ||||||
| namespace OpenWifi { |  | ||||||
|     class RESTAPI_action_links : public RESTAPIHandler { |  | ||||||
|     public: |  | ||||||
|         RESTAPI_action_links(const RESTAPIHandler::BindingMap &bindings, Poco::Logger &L, RESTAPI_GenericServer &Server, uint64_t TransactionId, bool Internal) |  | ||||||
|                 : RESTAPIHandler(bindings, L, |  | ||||||
|              std::vector<std::string>{ |  | ||||||
|                                         Poco::Net::HTTPRequest::HTTP_GET, |  | ||||||
|                                         Poco::Net::HTTPRequest::HTTP_POST, |  | ||||||
|                                         Poco::Net::HTTPRequest::HTTP_OPTIONS}, |  | ||||||
|                                         Server, |  | ||||||
|                                         TransactionId, |  | ||||||
|                                         Internal, |  | ||||||
|                                         false, |  | ||||||
|                                         true, RateLimit{.Interval=1000,.MaxCalls=10}) {} |  | ||||||
|         static auto PathName() { return std::list<std::string>{"/api/v1/actionLink"}; }; |  | ||||||
|         void RequestResetPassword(SecurityObjects::ActionLink &Link); |  | ||||||
|         void CompleteResetPassword(); |  | ||||||
|         void CompleteSubVerification(); |  | ||||||
|         void DoEmailVerification(SecurityObjects::ActionLink &Link); |  | ||||||
|         void DoReturnA404(); |  | ||||||
|         void DoNewSubVerification(SecurityObjects::ActionLink &Link); |  | ||||||
|  |  | ||||||
|         void DoGet() final; |  | ||||||
|         void DoPost() final; |  | ||||||
|         void DoDelete() final {}; |  | ||||||
|         void DoPut() final {}; |  | ||||||
|     }; |  | ||||||
| } |  | ||||||
| @@ -1,82 +0,0 @@ | |||||||
| // |  | ||||||
| // Created by stephane bourque on 2021-07-15. |  | ||||||
| // |  | ||||||
|  |  | ||||||
| #include <fstream> |  | ||||||
| #include <iostream> |  | ||||||
|  |  | ||||||
| #include "RESTAPI_avatar_handler.h" |  | ||||||
| #include "StorageService.h" |  | ||||||
| #include "Poco/Net/HTMLForm.h" |  | ||||||
| #include "framework/MicroService.h" |  | ||||||
|  |  | ||||||
| namespace OpenWifi { |  | ||||||
|  |  | ||||||
|     void AvatarPartHandler::handlePart(const Poco::Net::MessageHeader &Header, std::istream &Stream) { |  | ||||||
|         FileType_ = Header.get(RESTAPI::Protocol::CONTENTTYPE, RESTAPI::Protocol::UNSPECIFIED); |  | ||||||
|         if (Header.has(RESTAPI::Protocol::CONTENTDISPOSITION)) { |  | ||||||
|             std::string Disposition; |  | ||||||
|             Poco::Net::NameValueCollection Parameters; |  | ||||||
|             Poco::Net::MessageHeader::splitParameters(Header[RESTAPI::Protocol::CONTENTDISPOSITION], Disposition, Parameters); |  | ||||||
|             Name_ = Parameters.get(RESTAPI::Protocol::NAME, RESTAPI::Protocol::UNNAMED); |  | ||||||
|         } |  | ||||||
|         Poco::CountingInputStream InputStream(Stream); |  | ||||||
|         Poco::StreamCopier::copyStream(InputStream, OutputStream_); |  | ||||||
|         Length_ = OutputStream_.str().size(); |  | ||||||
|     }; |  | ||||||
|  |  | ||||||
|     void RESTAPI_avatar_handler::DoPost() { |  | ||||||
|         std::string Id = UserInfo_.userinfo.id; |  | ||||||
|         SecurityObjects::UserInfo UInfo; |  | ||||||
|  |  | ||||||
|         std::stringstream SS; |  | ||||||
|         AvatarPartHandler partHandler(Id, Logger_, SS); |  | ||||||
|         Poco::Net::HTMLForm form(*Request, Request->stream(), partHandler); |  | ||||||
|         Poco::JSON::Object Answer; |  | ||||||
|  |  | ||||||
|         if (!partHandler.Name().empty() && partHandler.Length()< MicroService::instance().ConfigGetInt("openwifi.avatar.maxsize",2000000)) { |  | ||||||
|             Answer.set(RESTAPI::Protocol::AVATARID, Id); |  | ||||||
|             Answer.set(RESTAPI::Protocol::ERRORCODE, 0); |  | ||||||
|             Logger_.information(fmt::format("Uploaded avatar: {} Type: {}", partHandler.Name(), partHandler.ContentType())); |  | ||||||
|             StorageService()->AvatarDB().SetAvatar(UserInfo_.userinfo.email, |  | ||||||
|                                  Id, SS.str(), partHandler.ContentType(), partHandler.Name()); |  | ||||||
|             StorageService()->UserDB().SetAvatar(Id,"1"); |  | ||||||
|             Logger().information(fmt::format("Adding avatar for {}",UserInfo_.userinfo.email)); |  | ||||||
|         } else { |  | ||||||
|             Answer.set(RESTAPI::Protocol::AVATARID, Id); |  | ||||||
|             Answer.set(RESTAPI::Protocol::ERRORCODE, 13); |  | ||||||
|             Answer.set(RESTAPI::Protocol::ERRORTEXT, "Avatar upload could not complete."); |  | ||||||
|         } |  | ||||||
|         ReturnObject(Answer); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     void RESTAPI_avatar_handler::DoGet() { |  | ||||||
|         std::string Id = GetBinding(RESTAPI::Protocol::ID, ""); |  | ||||||
|         if (Id.empty()) { |  | ||||||
|             return NotFound(); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         std::string Type, Name, AvatarContent; |  | ||||||
|         if (!StorageService()->AvatarDB().GetAvatar(UserInfo_.userinfo.email, Id, AvatarContent, Type, Name)) { |  | ||||||
|             return NotFound(); |  | ||||||
|         } |  | ||||||
|         Logger().information(fmt::format("Retrieving avatar for {}, size:{}",UserInfo_.userinfo.email,AvatarContent.size())); |  | ||||||
|         return SendFileContent(AvatarContent, Type, Name); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     void RESTAPI_avatar_handler::DoDelete() { |  | ||||||
|         std::string Id = GetBinding(RESTAPI::Protocol::ID, ""); |  | ||||||
|  |  | ||||||
|         if(UserInfo_.userinfo.userRole!=SecurityObjects::ROOT && Id!=UserInfo_.userinfo.id) { |  | ||||||
|             return UnAuthorized(RESTAPI::Errors::ACCESS_DENIED); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         if (!StorageService()->AvatarDB().DeleteAvatar(UserInfo_.userinfo.email, Id)) { |  | ||||||
|             return NotFound(); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         Logger().information(fmt::format("Deleted avatar for {}",UserInfo_.userinfo.email)); |  | ||||||
|         StorageService()->UserDB().SetAvatar(Id,""); |  | ||||||
|         OK(); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,17 +0,0 @@ | |||||||
| // |  | ||||||
| // Created by stephane bourque on 2022-01-01. |  | ||||||
| // |  | ||||||
|  |  | ||||||
| #pragma once |  | ||||||
|  |  | ||||||
| #include "framework/orm.h" |  | ||||||
|  |  | ||||||
| namespace OpenWifi { |  | ||||||
|  |  | ||||||
|     inline void Sanitize([[maybe_unused]] const SecurityObjects::UserInfoAndPolicy &User, SecurityObjects::UserInfo & U) { |  | ||||||
|         U.currentPassword.clear(); |  | ||||||
|         U.lastPasswords.clear(); |  | ||||||
|         U.oauthType.clear(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
| } |  | ||||||
| @@ -1,38 +0,0 @@ | |||||||
| // |  | ||||||
| // Created by stephane bourque on 2021-09-02. |  | ||||||
| // |  | ||||||
|  |  | ||||||
| #include "RESTAPI_email_handler.h" |  | ||||||
|  |  | ||||||
|  |  | ||||||
| #include "Poco/Exception.h" |  | ||||||
| #include "Poco/JSON/Parser.h" |  | ||||||
|  |  | ||||||
| #include "SMTPMailerService.h" |  | ||||||
| #include "framework/ow_constants.h" |  | ||||||
| #include "framework/MicroService.h" |  | ||||||
|  |  | ||||||
| namespace OpenWifi { |  | ||||||
|     void RESTAPI_email_handler::DoPost() { |  | ||||||
|         const auto & Obj = ParsedBody_; |  | ||||||
|         if (Obj->has("subject") && |  | ||||||
|             Obj->has("from") && |  | ||||||
|             Obj->has("text") && |  | ||||||
|             Obj->has("recipients") && |  | ||||||
|             Obj->isArray("recipients")) { |  | ||||||
|  |  | ||||||
|             Poco::JSON::Array::Ptr Recipients = Obj->getArray("recipients"); |  | ||||||
|             auto Recipient = Recipients->get(0).toString(); |  | ||||||
|             MessageAttributes Attrs; |  | ||||||
|             Attrs[RECIPIENT_EMAIL] = Recipient; |  | ||||||
|             Attrs[SUBJECT] = Obj->get("subject").toString(); |  | ||||||
|             Attrs[TEXT] = Obj->get("text").toString(); |  | ||||||
|             Attrs[SENDER] = Obj->get("from").toString(); |  | ||||||
|             if(SMTPMailerService()->SendMessage(Recipient, "password_reset.txt", Attrs)) { |  | ||||||
|                 return OK(); |  | ||||||
|             } |  | ||||||
|             return ReturnStatus(Poco::Net::HTTPResponse::HTTP_SERVICE_UNAVAILABLE); |  | ||||||
|         } |  | ||||||
|         BadRequest(RESTAPI::Errors::MissingOrInvalidParameters); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,178 +0,0 @@ | |||||||
| // |  | ||||||
| //	License type: BSD 3-Clause License |  | ||||||
| //	License copy: https://github.com/Telecominfraproject/wlan-cloud-ucentralgw/blob/master/LICENSE |  | ||||||
| // |  | ||||||
| //	Created by Stephane Bourque on 2021-03-04. |  | ||||||
| //	Arilia Wireless Inc. |  | ||||||
| // |  | ||||||
|  |  | ||||||
| #include "Poco/JSON/Parser.h" |  | ||||||
|  |  | ||||||
| #include "AuthService.h" |  | ||||||
| #include "RESTAPI_oauth2_handler.h" |  | ||||||
| #include "MFAServer.h" |  | ||||||
| #include "framework/ow_constants.h" |  | ||||||
| #include "framework/MicroService.h" |  | ||||||
| #include "StorageService.h" |  | ||||||
| #include "RESTAPI_db_helpers.h" |  | ||||||
|  |  | ||||||
| namespace OpenWifi { |  | ||||||
|  |  | ||||||
|     void RESTAPI_oauth2_handler::DoGet() { |  | ||||||
|         bool Expired = false, Contacted = false; |  | ||||||
|         if (!IsAuthorized(Expired, Contacted)) { |  | ||||||
|             if (Expired) |  | ||||||
|                 return UnAuthorized(RESTAPI::Errors::EXPIRED_TOKEN); |  | ||||||
|             return UnAuthorized(RESTAPI::Errors::INVALID_TOKEN); |  | ||||||
|         } |  | ||||||
|         if (GetBoolParameter(RESTAPI::Protocol::ME)) { |  | ||||||
|             Logger_.information(fmt::format("REQUEST-ME({}): Request for {}", Request->clientAddress().toString(), |  | ||||||
|                                             UserInfo_.userinfo.email)); |  | ||||||
|             Poco::JSON::Object Me; |  | ||||||
|             SecurityObjects::UserInfo ReturnedUser = UserInfo_.userinfo; |  | ||||||
|             Sanitize(UserInfo_, ReturnedUser); |  | ||||||
|             ReturnedUser.to_json(Me); |  | ||||||
|             return ReturnObject(Me); |  | ||||||
|         } |  | ||||||
|         BadRequest(RESTAPI::Errors::UnrecognizedRequest); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     void RESTAPI_oauth2_handler::DoDelete() { |  | ||||||
|         auto Token = GetBinding(RESTAPI::Protocol::TOKEN, ""); |  | ||||||
|         std::string SessionToken; |  | ||||||
|         try { |  | ||||||
|             Poco::Net::OAuth20Credentials Auth(*Request); |  | ||||||
|             if (Auth.getScheme() == "Bearer") { |  | ||||||
|                 SessionToken = Auth.getBearerToken(); |  | ||||||
|             } |  | ||||||
|         } catch (const Poco::Exception &E) { |  | ||||||
|             return BadRequest(RESTAPI::Errors::MissingOrInvalidParameters); |  | ||||||
|         } |  | ||||||
|         if (Token.empty() || (Token != SessionToken)) { |  | ||||||
|             return BadRequest(RESTAPI::Errors::MissingOrInvalidParameters); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         AuthService()->Logout(Token); |  | ||||||
|         return ReturnStatus(Poco::Net::HTTPResponse::HTTP_NO_CONTENT, true); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
| 	void RESTAPI_oauth2_handler::DoPost() { |  | ||||||
|  |  | ||||||
|         const auto & Obj = ParsedBody_; |  | ||||||
|         if(Obj == nullptr) { |  | ||||||
|             return BadRequest(RESTAPI::Errors::InvalidJSONDocument); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         auto userId = GetS(RESTAPI::Protocol::USERID, Obj); |  | ||||||
|         auto password = GetS(RESTAPI::Protocol::PASSWORD, Obj); |  | ||||||
|         auto newPassword = GetS(RESTAPI::Protocol::NEWPASSWORD, Obj); |  | ||||||
|         auto refreshToken = GetS("refreshToken", Obj); |  | ||||||
|         auto grant_type = GetParameter("grant_type"); |  | ||||||
|  |  | ||||||
|         Poco::toLowerInPlace(userId); |  | ||||||
|  |  | ||||||
|         if(!refreshToken.empty() && grant_type == "refresh_token") { |  | ||||||
|             SecurityObjects::UserInfoAndPolicy UInfo; |  | ||||||
|             if(AuthService()->RefreshUserToken(*Request, refreshToken, UInfo)) { |  | ||||||
|                 Poco::JSON::Object  Answer; |  | ||||||
|                 UInfo.webtoken.to_json(Answer); |  | ||||||
|                 return ReturnObject(Answer); |  | ||||||
|             } else { |  | ||||||
|                 return UnAuthorized(RESTAPI::Errors::CANNOT_REFRESH_TOKEN); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         if(GetBoolParameter(RESTAPI::Protocol::REQUIREMENTS)) { |  | ||||||
|             Logger_.information(fmt::format("POLICY-REQUEST({}): Request.", Request->clientAddress().toString())); |  | ||||||
|             Poco::JSON::Object  Answer; |  | ||||||
|             Answer.set(RESTAPI::Protocol::PASSWORDPATTERN, AuthService()->PasswordValidationExpression()); |  | ||||||
|             Answer.set(RESTAPI::Protocol::ACCESSPOLICY, AuthService()->GetAccessPolicy()); |  | ||||||
|             Answer.set(RESTAPI::Protocol::PASSWORDPOLICY, AuthService()->GetPasswordPolicy()); |  | ||||||
|             return ReturnObject(Answer); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         if(GetBoolParameter(RESTAPI::Protocol::FORGOTPASSWORD)) { |  | ||||||
|             SecurityObjects::UserInfo UInfo1; |  | ||||||
|             auto UserExists = StorageService()->UserDB().GetUserByEmail(userId,UInfo1); |  | ||||||
|             if(UserExists) { |  | ||||||
|                 Logger_.information(fmt::format("FORGOTTEN-PASSWORD({}): Request for {}", Request->clientAddress().toString(), userId)); |  | ||||||
|                 SecurityObjects::ActionLink NewLink; |  | ||||||
|  |  | ||||||
|                 NewLink.action = OpenWifi::SecurityObjects::LinkActions::FORGOT_PASSWORD; |  | ||||||
|                 NewLink.id = MicroService::CreateUUID(); |  | ||||||
|                 NewLink.userId = UInfo1.id; |  | ||||||
|                 NewLink.created = OpenWifi::Now(); |  | ||||||
|                 NewLink.expires = NewLink.created + (24*60*60); |  | ||||||
|                 NewLink.userAction = true; |  | ||||||
|                 StorageService()->ActionLinksDB().CreateAction(NewLink); |  | ||||||
|  |  | ||||||
|                 Poco::JSON::Object ReturnObj; |  | ||||||
|                 SecurityObjects::UserInfoAndPolicy UInfo; |  | ||||||
|                 UInfo.webtoken.userMustChangePassword = true; |  | ||||||
|                 UInfo.webtoken.to_json(ReturnObj); |  | ||||||
|                 return ReturnObject(ReturnObj); |  | ||||||
|             } else { |  | ||||||
|                 Poco::JSON::Object ReturnObj; |  | ||||||
|                 SecurityObjects::UserInfoAndPolicy UInfo; |  | ||||||
|                 UInfo.webtoken.userMustChangePassword = true; |  | ||||||
|                 UInfo.webtoken.to_json(ReturnObj); |  | ||||||
|                 return ReturnObject(ReturnObj); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         if(GetBoolParameter(RESTAPI::Protocol::RESENDMFACODE)) { |  | ||||||
|             Logger_.information(fmt::format("RESEND-MFA-CODE({}): Request for {}", Request->clientAddress().toString(), userId)); |  | ||||||
|             if(Obj->has("uuid")) { |  | ||||||
|                 auto uuid = Obj->get("uuid").toString(); |  | ||||||
|                 if(MFAServer()->ResendCode(uuid)) |  | ||||||
|                     return OK(); |  | ||||||
|             } |  | ||||||
|             return UnAuthorized(RESTAPI::Errors::BAD_MFA_TRANSACTION); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         if(GetBoolParameter(RESTAPI::Protocol::COMPLETEMFACHALLENGE,false)) { |  | ||||||
|             Logger_.information(fmt::format("COMPLETE-MFA-CHALLENGE({}): Request for {}", Request->clientAddress().toString(), userId)); |  | ||||||
|             if(Obj->has("uuid")) { |  | ||||||
|                 SecurityObjects::UserInfoAndPolicy UInfo; |  | ||||||
|                 if(MFAServer()->CompleteMFAChallenge(Obj,UInfo)) { |  | ||||||
|                     Poco::JSON::Object ReturnObj; |  | ||||||
|                     UInfo.webtoken.to_json(ReturnObj); |  | ||||||
|                     return ReturnObject(ReturnObj); |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|             return UnAuthorized(RESTAPI::Errors::MFA_FAILURE); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         SecurityObjects::UserInfoAndPolicy UInfo; |  | ||||||
|         bool Expired=false; |  | ||||||
|         auto Code=AuthService()->Authorize(userId, password, newPassword, UInfo, Expired); |  | ||||||
|         if (Code==SUCCESS) { |  | ||||||
|             Poco::JSON::Object ReturnObj; |  | ||||||
|             if(AuthService()->RequiresMFA(UInfo)) { |  | ||||||
|                 if(MFAServer()->StartMFAChallenge(UInfo, ReturnObj)) { |  | ||||||
|                     return ReturnObject(ReturnObj); |  | ||||||
|                 } |  | ||||||
|                 Logger_.warning("MFA Seems to be broken. Please fix. Disabling MFA checking for now."); |  | ||||||
|             } |  | ||||||
|             UInfo.webtoken.to_json(ReturnObj); |  | ||||||
|             return ReturnObject(ReturnObj); |  | ||||||
|         } else { |  | ||||||
|  |  | ||||||
|             switch(Code) { |  | ||||||
|                 case INVALID_CREDENTIALS: |  | ||||||
|                     return UnAuthorized(RESTAPI::Errors::INVALID_CREDENTIALS); |  | ||||||
|                 case PASSWORD_INVALID: |  | ||||||
|                     return UnAuthorized(RESTAPI::Errors::PASSWORD_INVALID); |  | ||||||
|                 case PASSWORD_ALREADY_USED: |  | ||||||
|                     return UnAuthorized(RESTAPI::Errors::PASSWORD_ALREADY_USED); |  | ||||||
|                 case USERNAME_PENDING_VERIFICATION: |  | ||||||
|                     return UnAuthorized(RESTAPI::Errors::USERNAME_PENDING_VERIFICATION); |  | ||||||
|                 case PASSWORD_CHANGE_REQUIRED: |  | ||||||
|                     return UnAuthorized(RESTAPI::Errors::PASSWORD_CHANGE_REQUIRED); |  | ||||||
|                 default: |  | ||||||
|                     return UnAuthorized(RESTAPI::Errors::INVALID_CREDENTIALS); |  | ||||||
|             } |  | ||||||
|             return; |  | ||||||
|         } |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
| @@ -1,36 +0,0 @@ | |||||||
| // |  | ||||||
| // Created by stephane bourque on 2021-11-16. |  | ||||||
| // |  | ||||||
|  |  | ||||||
| #include "RESTAPI_preferences.h" |  | ||||||
| #include "StorageService.h" |  | ||||||
|  |  | ||||||
| namespace OpenWifi { |  | ||||||
|  |  | ||||||
|     void RESTAPI_preferences::DoGet() { |  | ||||||
|         SecurityObjects::Preferences    P; |  | ||||||
|         Poco::JSON::Object  Answer; |  | ||||||
|         StorageService()->PreferencesDB().GetPreferences(UserInfo_.userinfo.id, P); |  | ||||||
|         P.to_json(Answer); |  | ||||||
|         ReturnObject(Answer); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     void RESTAPI_preferences::DoPut() { |  | ||||||
|  |  | ||||||
|         SecurityObjects::Preferences    P; |  | ||||||
|  |  | ||||||
|         const auto & RawObject = ParsedBody_; |  | ||||||
|         if(!P.from_json(RawObject)) { |  | ||||||
|             return BadRequest(RESTAPI::Errors::InvalidJSONDocument); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         P.id = UserInfo_.userinfo.id; |  | ||||||
|         P.modified = OpenWifi::Now(); |  | ||||||
|         StorageService()->PreferencesDB().SetPreferences(P); |  | ||||||
|  |  | ||||||
|         Poco::JSON::Object  Answer; |  | ||||||
|         P.to_json(Answer); |  | ||||||
|         ReturnObject(Answer); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
| } |  | ||||||
| @@ -1,27 +0,0 @@ | |||||||
| // |  | ||||||
| // Created by stephane bourque on 2021-11-16. |  | ||||||
| // |  | ||||||
|  |  | ||||||
| #pragma once |  | ||||||
|  |  | ||||||
| #include "framework/MicroService.h" |  | ||||||
|  |  | ||||||
| namespace OpenWifi { |  | ||||||
|     class RESTAPI_preferences : public RESTAPIHandler { |  | ||||||
|     public: |  | ||||||
|         RESTAPI_preferences(const RESTAPIHandler::BindingMap &bindings, Poco::Logger &L, RESTAPI_GenericServer &Server, uint64_t TransactionId, bool Internal) |  | ||||||
|         : RESTAPIHandler(bindings, L, |  | ||||||
|                          std::vector<std::string>{ |  | ||||||
|             Poco::Net::HTTPRequest::HTTP_GET, |  | ||||||
|             Poco::Net::HTTPRequest::HTTP_PUT, |  | ||||||
|             Poco::Net::HTTPRequest::HTTP_OPTIONS}, |  | ||||||
|             Server, |  | ||||||
|             TransactionId, |  | ||||||
|             Internal) {} |  | ||||||
|         static auto PathName() { return std::list<std::string>{"/api/v1/preferences"}; }; |  | ||||||
|         void DoGet() final; |  | ||||||
|         void DoPut() final; |  | ||||||
|         void DoPost() final {}; |  | ||||||
|         void DoDelete() final {}; |  | ||||||
|     }; |  | ||||||
| } |  | ||||||
| @@ -1,87 +0,0 @@ | |||||||
| // |  | ||||||
| // Created by stephane bourque on 2021-10-23. |  | ||||||
| // |  | ||||||
|  |  | ||||||
| #include "framework/MicroService.h" |  | ||||||
|  |  | ||||||
| #include "RESTAPI/RESTAPI_oauth2_handler.h" |  | ||||||
| #include "RESTAPI/RESTAPI_user_handler.h" |  | ||||||
| #include "RESTAPI/RESTAPI_users_handler.h" |  | ||||||
| #include "RESTAPI/RESTAPI_action_links.h" |  | ||||||
| #include "RESTAPI/RESTAPI_system_endpoints_handler.h" |  | ||||||
| #include "RESTAPI/RESTAPI_asset_server.h" |  | ||||||
| #include "RESTAPI/RESTAPI_avatar_handler.h" |  | ||||||
| #include "RESTAPI/RESTAPI_subavatar_handler.h" |  | ||||||
| #include "RESTAPI/RESTAPI_email_handler.h" |  | ||||||
| #include "RESTAPI/RESTAPI_sms_handler.h" |  | ||||||
| #include "RESTAPI/RESTAPI_validate_token_handler.h" |  | ||||||
| #include "RESTAPI/RESTAPI_preferences.h" |  | ||||||
| #include "RESTAPI/RESTAPI_subpreferences.h" |  | ||||||
| #include "RESTAPI/RESTAPI_suboauth2_handler.h" |  | ||||||
| #include "RESTAPI/RESTAPI_subuser_handler.h" |  | ||||||
| #include "RESTAPI/RESTAPI_subusers_handler.h" |  | ||||||
| #include "RESTAPI/RESTAPI_validate_sub_token_handler.h" |  | ||||||
| #include "RESTAPI/RESTAPI_submfa_handler.h" |  | ||||||
| #include "RESTAPI/RESTAPI_totp_handler.h" |  | ||||||
| #include "RESTAPI/RESTAPI_subtotp_handler.h" |  | ||||||
| #include "RESTAPI/RESTAPI_signup_handler.h" |  | ||||||
|  |  | ||||||
| namespace OpenWifi { |  | ||||||
|  |  | ||||||
|     Poco::Net::HTTPRequestHandler * RESTAPI_ExtRouter(const std::string &Path, RESTAPIHandler::BindingMap &Bindings, |  | ||||||
|                                                             Poco::Logger & L, RESTAPI_GenericServer & S, uint64_t TransactionId) { |  | ||||||
|         return RESTAPI_Router< |  | ||||||
|             RESTAPI_oauth2_handler, |  | ||||||
|             RESTAPI_user_handler, |  | ||||||
|             RESTAPI_users_handler, |  | ||||||
|             RESTAPI_system_command, |  | ||||||
|             RESTAPI_asset_server, |  | ||||||
|             RESTAPI_system_endpoints_handler, |  | ||||||
|             RESTAPI_action_links, |  | ||||||
|             RESTAPI_avatar_handler, |  | ||||||
|             RESTAPI_subavatar_handler, |  | ||||||
|             RESTAPI_email_handler, |  | ||||||
|             RESTAPI_sms_handler, |  | ||||||
|             RESTAPI_preferences, |  | ||||||
|             RESTAPI_subpreferences, |  | ||||||
|             RESTAPI_suboauth2_handler, |  | ||||||
|             RESTAPI_subuser_handler, |  | ||||||
|             RESTAPI_subusers_handler, |  | ||||||
|             RESTAPI_submfa_handler, |  | ||||||
|             RESTAPI_totp_handler, |  | ||||||
|             RESTAPI_subtotp_handler, |  | ||||||
|             RESTAPI_signup_handler, |  | ||||||
|             RESTAPI_validate_sub_token_handler, |  | ||||||
|             RESTAPI_validate_token_handler |  | ||||||
|         >(Path, Bindings, L, S,TransactionId); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     Poco::Net::HTTPRequestHandler * RESTAPI_IntRouter(const std::string &Path, RESTAPIHandler::BindingMap &Bindings, |  | ||||||
|                                                             Poco::Logger & L, RESTAPI_GenericServer & S, uint64_t TransactionId) { |  | ||||||
|  |  | ||||||
|         return RESTAPI_Router_I< |  | ||||||
|             RESTAPI_oauth2_handler, |  | ||||||
|             RESTAPI_user_handler, |  | ||||||
|             RESTAPI_users_handler, |  | ||||||
|             RESTAPI_system_command, |  | ||||||
|             RESTAPI_asset_server, |  | ||||||
|             RESTAPI_system_endpoints_handler, |  | ||||||
|             RESTAPI_action_links, |  | ||||||
|             RESTAPI_avatar_handler, |  | ||||||
|             RESTAPI_subavatar_handler, |  | ||||||
|             RESTAPI_email_handler, |  | ||||||
|             RESTAPI_sms_handler, |  | ||||||
|             RESTAPI_preferences, |  | ||||||
|             RESTAPI_subpreferences, |  | ||||||
|             RESTAPI_suboauth2_handler, |  | ||||||
|             RESTAPI_subuser_handler, |  | ||||||
|             RESTAPI_subusers_handler, |  | ||||||
|             RESTAPI_submfa_handler, |  | ||||||
|             RESTAPI_totp_handler, |  | ||||||
|             RESTAPI_subtotp_handler, |  | ||||||
|             RESTAPI_validate_sub_token_handler, |  | ||||||
|             RESTAPI_validate_token_handler, |  | ||||||
|             RESTAPI_signup_handler |  | ||||||
|         >(Path, Bindings, L, S, TransactionId); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,73 +0,0 @@ | |||||||
| // |  | ||||||
| // Created by stephane bourque on 2022-02-20. |  | ||||||
| // |  | ||||||
|  |  | ||||||
| #include "RESTAPI_signup_handler.h" |  | ||||||
| #include "StorageService.h" |  | ||||||
| #include "RESTObjects/RESTAPI_SecurityObjects.h" |  | ||||||
|  |  | ||||||
| #define __DBG__ std::cout << __LINE__ << std::endl; |  | ||||||
| namespace OpenWifi { |  | ||||||
|  |  | ||||||
|     void RESTAPI_signup_handler::DoPost() { |  | ||||||
|         auto UserName = GetParameter("email"); |  | ||||||
|         auto signupUUID = GetParameter("signupUUID"); |  | ||||||
|         auto owner = GetParameter("owner"); |  | ||||||
|         if(UserName.empty() || signupUUID.empty() || owner.empty()) { |  | ||||||
|             Logger().error("Signup requires: email, signupUUID, and owner."); |  | ||||||
|             return BadRequest(RESTAPI::Errors::MissingOrInvalidParameters); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         if(!Utils::ValidEMailAddress(UserName)) { |  | ||||||
|             return BadRequest(RESTAPI::Errors::InvalidEmailAddress); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         // Do we already exist? Can only signup once... |  | ||||||
|         SecurityObjects::UserInfo   Existing; |  | ||||||
|         if(StorageService()->SubDB().GetUserByEmail(UserName,Existing)) { |  | ||||||
|             if(Existing.signingUp.empty()) { |  | ||||||
|                 return BadRequest(RESTAPI::Errors::SignupAlreadySigned); |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             if(Existing.waitingForEmailCheck) { |  | ||||||
|                 return BadRequest(RESTAPI::Errors::SignupEmailCheck); |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             return BadRequest(RESTAPI::Errors::SignupWaitingForDevice); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         SecurityObjects::UserInfo   NewSub; |  | ||||||
|         NewSub.signingUp = signupUUID; |  | ||||||
|         NewSub.waitingForEmailCheck = true; |  | ||||||
|         NewSub.name = UserName; |  | ||||||
|         NewSub.modified = OpenWifi::Now(); |  | ||||||
|         NewSub.creationDate = OpenWifi::Now(); |  | ||||||
|         NewSub.id = MicroService::instance().CreateUUID(); |  | ||||||
|         NewSub.email = UserName; |  | ||||||
|         NewSub.userRole = SecurityObjects::SUBSCRIBER; |  | ||||||
|         NewSub.changePassword = true; |  | ||||||
|         NewSub.owner = owner; |  | ||||||
|  |  | ||||||
|         StorageService()->SubDB().CreateRecord(NewSub); |  | ||||||
|  |  | ||||||
|         Logger_.information(fmt::format("SIGNUP-PASSWORD({}): Request for {}", Request->clientAddress().toString(), UserName)); |  | ||||||
|         SecurityObjects::ActionLink NewLink; |  | ||||||
|  |  | ||||||
|         NewLink.action = OpenWifi::SecurityObjects::LinkActions::SUB_SIGNUP; |  | ||||||
|         NewLink.id = MicroService::CreateUUID(); |  | ||||||
|         NewLink.userId = NewSub.id; |  | ||||||
|         NewLink.created = OpenWifi::Now(); |  | ||||||
|         NewLink.expires = NewLink.created + (1*60*60);  // 1 hour |  | ||||||
|         NewLink.userAction = false; |  | ||||||
|         StorageService()->ActionLinksDB().CreateAction(NewLink); |  | ||||||
|  |  | ||||||
|         Poco::JSON::Object  Answer; |  | ||||||
|         NewSub.to_json(Answer); |  | ||||||
|         return ReturnObject(Answer); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     void RESTAPI_signup_handler::DoPut() { |  | ||||||
|         // TODO |  | ||||||
|     } |  | ||||||
|  |  | ||||||
| } |  | ||||||
| @@ -1,39 +0,0 @@ | |||||||
| // |  | ||||||
| // Created by stephane bourque on 2022-02-20. |  | ||||||
| // |  | ||||||
|  |  | ||||||
| #pragma once |  | ||||||
|  |  | ||||||
| #include "framework/MicroService.h" |  | ||||||
|  |  | ||||||
| namespace OpenWifi { |  | ||||||
|     class RESTAPI_signup_handler : public RESTAPIHandler { |  | ||||||
|     public: |  | ||||||
|         RESTAPI_signup_handler(const RESTAPIHandler::BindingMap &bindings, Poco::Logger &L, RESTAPI_GenericServer & Server, uint64_t TransactionId, bool Internal) |  | ||||||
|                 : RESTAPIHandler(bindings, L, |  | ||||||
|                                  std::vector<std::string>{ |  | ||||||
|                                          Poco::Net::HTTPRequest::HTTP_POST, |  | ||||||
|                                          Poco::Net::HTTPRequest::HTTP_OPTIONS, |  | ||||||
|                                          Poco::Net::HTTPRequest::HTTP_PUT}, |  | ||||||
|                                  Server, |  | ||||||
|                                  TransactionId, |  | ||||||
|                                  Internal, false, true ){} |  | ||||||
|  |  | ||||||
|         static auto PathName() { return std::list<std::string>{"/api/v1/signup"}; }; |  | ||||||
|  |  | ||||||
| /*        inline bool RoleIsAuthorized(std::string & Reason) { |  | ||||||
|             if(UserInfo_.userinfo.userRole != SecurityObjects::USER_ROLE::SUBSCRIBER) { |  | ||||||
|                 Reason = "User must be a subscriber"; |  | ||||||
|                 return false; |  | ||||||
|             } |  | ||||||
|             return true; |  | ||||||
|         } |  | ||||||
| */ |  | ||||||
|         void DoGet() final {}; |  | ||||||
|         void DoPost() final; |  | ||||||
|         void DoPut() final ; |  | ||||||
|         void DoDelete() final {}; |  | ||||||
|     private: |  | ||||||
|  |  | ||||||
|     }; |  | ||||||
| } |  | ||||||
| @@ -1,55 +0,0 @@ | |||||||
| // |  | ||||||
| // Created by stephane bourque on 2021-10-09. |  | ||||||
| // |  | ||||||
|  |  | ||||||
| #include "RESTAPI_sms_handler.h" |  | ||||||
| #include "SMSSender.h" |  | ||||||
| #include "framework/ow_constants.h" |  | ||||||
| #include "framework/MicroService.h" |  | ||||||
|  |  | ||||||
| namespace OpenWifi { |  | ||||||
|  |  | ||||||
|     void OpenWifi::RESTAPI_sms_handler::DoPost() { |  | ||||||
|         const auto &Obj = ParsedBody_; |  | ||||||
|  |  | ||||||
|         std::string Arg; |  | ||||||
|         if(HasParameter("validateNumber",Arg) && Arg=="true" && Obj->has("to")) { |  | ||||||
|             auto Number = Obj->get("to").toString(); |  | ||||||
|             if(SMSSender()->StartValidation(Number, UserInfo_.userinfo.email)) { |  | ||||||
|                 return OK(); |  | ||||||
|             } |  | ||||||
|             return BadRequest(RESTAPI::Errors::SMSCouldNotBeSentRetry); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         std::string Code; |  | ||||||
|         if( HasParameter("completeValidation",Arg) && |  | ||||||
|             Arg=="true" && |  | ||||||
|             HasParameter("validationCode", Code) && |  | ||||||
|             Obj->has("to")) { |  | ||||||
|             auto Number = Obj->get("to").toString(); |  | ||||||
|             if(SMSSender()->CompleteValidation(Number, Code, UserInfo_.userinfo.email)) { |  | ||||||
|                 return OK(); |  | ||||||
|             } |  | ||||||
|             return BadRequest(RESTAPI::Errors::SMSCouldNotValidate); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         if( UserInfo_.userinfo.userRole!=SecurityObjects::ROOT && |  | ||||||
|             UserInfo_.userinfo.userRole!=SecurityObjects::PARTNER && |  | ||||||
|             UserInfo_.userinfo.userRole!=SecurityObjects::ADMIN) { |  | ||||||
|             return UnAuthorized(RESTAPI::Errors::ACCESS_DENIED); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         if (Obj->has("to") && |  | ||||||
|             Obj->has("text")) { |  | ||||||
|  |  | ||||||
|             std::string PhoneNumber = Obj->get("to").toString(); |  | ||||||
|             std::string Text = Obj->get("text").toString(); |  | ||||||
|             if(SMSSender()->Send(PhoneNumber, Text)) |  | ||||||
|                 return OK(); |  | ||||||
|  |  | ||||||
|             return InternalError(RESTAPI::Errors::SMSCouldNotBeSentRetry); |  | ||||||
|         } |  | ||||||
|         BadRequest(RESTAPI::Errors::MissingOrInvalidParameters); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
| } |  | ||||||
| @@ -1,25 +0,0 @@ | |||||||
| // |  | ||||||
| // Created by stephane bourque on 2021-10-09. |  | ||||||
| // |  | ||||||
|  |  | ||||||
| #pragma once |  | ||||||
|  |  | ||||||
| #include "framework/MicroService.h" |  | ||||||
|  |  | ||||||
| namespace OpenWifi { |  | ||||||
|     class RESTAPI_sms_handler : public RESTAPIHandler { |  | ||||||
|     public: |  | ||||||
|         RESTAPI_sms_handler(const RESTAPIHandler::BindingMap &bindings, Poco::Logger &L, RESTAPI_GenericServer &Server, uint64_t TransactionId, bool Internal) |  | ||||||
|         : RESTAPIHandler(bindings, L, |  | ||||||
|                          std::vector<std::string>{Poco::Net::HTTPRequest::HTTP_POST, |  | ||||||
|                                                   Poco::Net::HTTPRequest::HTTP_OPTIONS}, |  | ||||||
|                                                   Server, |  | ||||||
|                                                   TransactionId, |  | ||||||
|                                                   Internal) {} |  | ||||||
|         static auto PathName() { return std::list<std::string>{"/api/v1/sms"};} |  | ||||||
|         void DoGet() final {}; |  | ||||||
|         void DoPost() final; |  | ||||||
|         void DoDelete() final {}; |  | ||||||
|         void DoPut() final {}; |  | ||||||
|     }; |  | ||||||
| } |  | ||||||
| @@ -1,81 +0,0 @@ | |||||||
| // |  | ||||||
| // Created by stephane bourque on 2021-07-15. |  | ||||||
| // |  | ||||||
|  |  | ||||||
| #include <fstream> |  | ||||||
| #include <iostream> |  | ||||||
|  |  | ||||||
| #include "RESTAPI_subavatar_handler.h" |  | ||||||
| #include "StorageService.h" |  | ||||||
| #include "Poco/Net/HTMLForm.h" |  | ||||||
| #include "framework/MicroService.h" |  | ||||||
|  |  | ||||||
| namespace OpenWifi { |  | ||||||
|  |  | ||||||
|     void SubAvatarPartHandler::handlePart(const Poco::Net::MessageHeader &Header, std::istream &Stream) { |  | ||||||
|         FileType_ = Header.get(RESTAPI::Protocol::CONTENTTYPE, RESTAPI::Protocol::UNSPECIFIED); |  | ||||||
|         if (Header.has(RESTAPI::Protocol::CONTENTDISPOSITION)) { |  | ||||||
|             std::string Disposition; |  | ||||||
|             Poco::Net::NameValueCollection Parameters; |  | ||||||
|             Poco::Net::MessageHeader::splitParameters(Header[RESTAPI::Protocol::CONTENTDISPOSITION], Disposition, Parameters); |  | ||||||
|             Name_ = Parameters.get(RESTAPI::Protocol::NAME, RESTAPI::Protocol::UNNAMED); |  | ||||||
|         } |  | ||||||
|         Poco::CountingInputStream InputStream(Stream); |  | ||||||
|         Poco::StreamCopier::copyStream(InputStream, OutputStream_); |  | ||||||
|         Length_ = OutputStream_.str().size(); |  | ||||||
|     }; |  | ||||||
|  |  | ||||||
|     void RESTAPI_subavatar_handler::DoPost() { |  | ||||||
|         std::string Id = UserInfo_.userinfo.id; |  | ||||||
|         SecurityObjects::UserInfo UInfo; |  | ||||||
|  |  | ||||||
|         std::stringstream SS; |  | ||||||
|         SubAvatarPartHandler partHandler(Id, Logger_, SS); |  | ||||||
|         Poco::Net::HTMLForm form(*Request, Request->stream(), partHandler); |  | ||||||
|         Poco::JSON::Object Answer; |  | ||||||
|  |  | ||||||
|         if (!partHandler.Name().empty() && partHandler.Length()< MicroService::instance().ConfigGetInt("openwifi.avatar.maxsize",2000000)) { |  | ||||||
|             Answer.set(RESTAPI::Protocol::AVATARID, Id); |  | ||||||
|             Answer.set(RESTAPI::Protocol::ERRORCODE, 0); |  | ||||||
|             Logger_.information(fmt::format("Uploaded avatar: {} Type: {}", partHandler.Name(), partHandler.ContentType())); |  | ||||||
|             StorageService()->SubAvatarDB().SetAvatar(UserInfo_.userinfo.email, |  | ||||||
|                                  Id, SS.str(), partHandler.ContentType(), partHandler.Name()); |  | ||||||
|             StorageService()->SubDB().SetAvatar(Id,"1"); |  | ||||||
|             Logger().information(fmt::format("Adding avatar for {}",UserInfo_.userinfo.email)); |  | ||||||
|         } else { |  | ||||||
|             Answer.set(RESTAPI::Protocol::AVATARID, Id); |  | ||||||
|             Answer.set(RESTAPI::Protocol::ERRORCODE, 13); |  | ||||||
|             Answer.set(RESTAPI::Protocol::ERRORTEXT, "Avatar upload could not complete."); |  | ||||||
|         } |  | ||||||
|         ReturnObject(Answer); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     void RESTAPI_subavatar_handler::DoGet() { |  | ||||||
|         std::string Id = GetBinding(RESTAPI::Protocol::ID, ""); |  | ||||||
|         if (Id.empty()) { |  | ||||||
|             return NotFound(); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         std::string Type, Name, AvatarContent; |  | ||||||
|         if (!StorageService()->SubAvatarDB().GetAvatar(UserInfo_.userinfo.email, Id, AvatarContent, Type, Name)) { |  | ||||||
|             return NotFound(); |  | ||||||
|         } |  | ||||||
|         Logger().information(fmt::format("Retrieving avatar for {}",UserInfo_.userinfo.email)); |  | ||||||
|         return SendFileContent(AvatarContent, Type, Name); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     void RESTAPI_subavatar_handler::DoDelete() { |  | ||||||
|         std::string Id = GetBinding(RESTAPI::Protocol::ID, ""); |  | ||||||
|  |  | ||||||
|         if(UserInfo_.userinfo.userRole!=SecurityObjects::ROOT && Id!=UserInfo_.userinfo.id) { |  | ||||||
|             return UnAuthorized(RESTAPI::Errors::ACCESS_DENIED); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         if (!StorageService()->SubAvatarDB().DeleteAvatar(UserInfo_.userinfo.email, Id)) { |  | ||||||
|             return NotFound(); |  | ||||||
|         } |  | ||||||
|         Logger().information(fmt::format("Deleted avatar for {}",UserInfo_.userinfo.email)); |  | ||||||
|         StorageService()->SubDB().SetAvatar(Id,""); |  | ||||||
|         OK(); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,53 +0,0 @@ | |||||||
| // |  | ||||||
| // Created by stephane bourque on 2021-07-15. |  | ||||||
| // |  | ||||||
| #pragma once |  | ||||||
|  |  | ||||||
| #include "framework/MicroService.h" |  | ||||||
|  |  | ||||||
| namespace OpenWifi { |  | ||||||
|  |  | ||||||
|     class SubAvatarPartHandler : public Poco::Net::PartHandler { |  | ||||||
|     public: |  | ||||||
|         SubAvatarPartHandler(std::string Id, Poco::Logger &Logger, std::stringstream & ofs) : |  | ||||||
|                 Id_(std::move(Id)), |  | ||||||
|                 Logger_(Logger), |  | ||||||
|                 OutputStream_(ofs){ |  | ||||||
|         } |  | ||||||
|         void handlePart(const Poco::Net::MessageHeader &Header, std::istream &Stream); |  | ||||||
|         [[nodiscard]] uint64_t Length() const { return Length_; } |  | ||||||
|         [[nodiscard]] std::string &Name() { return Name_; } |  | ||||||
|         [[nodiscard]] std::string &ContentType() { return FileType_; } |  | ||||||
|  |  | ||||||
|     private: |  | ||||||
|         uint64_t        Length_ = 0; |  | ||||||
|         std::string     FileType_; |  | ||||||
|         std::string     Name_; |  | ||||||
|         std::string     Id_; |  | ||||||
|         Poco::Logger    &Logger_; |  | ||||||
|         std::stringstream &OutputStream_; |  | ||||||
|  |  | ||||||
|         inline Poco::Logger & Logger() { return Logger_; } |  | ||||||
|     }; |  | ||||||
|  |  | ||||||
|     class RESTAPI_subavatar_handler : public RESTAPIHandler { |  | ||||||
|     public: |  | ||||||
|         RESTAPI_subavatar_handler(const RESTAPIHandler::BindingMap &bindings, Poco::Logger &L, RESTAPI_GenericServer &Server, uint64_t TransactionId, bool Internal) |  | ||||||
|                 : RESTAPIHandler(bindings, L, |  | ||||||
|                                  std::vector<std::string>{ |  | ||||||
|                                          Poco::Net::HTTPRequest::HTTP_GET, |  | ||||||
|                                          Poco::Net::HTTPRequest::HTTP_POST, |  | ||||||
|                                          Poco::Net::HTTPRequest::HTTP_DELETE, |  | ||||||
|                                          Poco::Net::HTTPRequest::HTTP_OPTIONS}, |  | ||||||
|                                          Server, |  | ||||||
|                                          TransactionId, |  | ||||||
|                                          Internal) {} |  | ||||||
|         static auto PathName() { return std::list<std::string>{"/api/v1/subavatar/{id}"}; }; |  | ||||||
|  |  | ||||||
|         void DoGet() final; |  | ||||||
|         void DoPost() final; |  | ||||||
|         void DoDelete() final; |  | ||||||
|         void DoPut() final {}; |  | ||||||
|  |  | ||||||
|     }; |  | ||||||
| } |  | ||||||
| @@ -1,128 +0,0 @@ | |||||||
| // |  | ||||||
| // Created by stephane bourque on 2021-12-01. |  | ||||||
| // |  | ||||||
|  |  | ||||||
| #include "RESTAPI_submfa_handler.h" |  | ||||||
| #include "StorageService.h" |  | ||||||
| #include "SMSSender.h" |  | ||||||
|  |  | ||||||
| namespace OpenWifi { |  | ||||||
|  |  | ||||||
|     void RESTAPI_submfa_handler::DoGet() { |  | ||||||
|         SecurityObjects::UserInfo   User; |  | ||||||
|  |  | ||||||
|         if (StorageService()->SubDB().GetUserById(UserInfo_.userinfo.id,User)) { |  | ||||||
|             Poco::JSON::Object              Answer; |  | ||||||
|             SecurityObjects::SubMfaConfig   MFC; |  | ||||||
|  |  | ||||||
|             MFC.id = User.id; |  | ||||||
|             if(User.userTypeProprietaryInfo.mfa.enabled) { |  | ||||||
|                 if(User.userTypeProprietaryInfo.mfa.method == "sms") { |  | ||||||
|                     MFC.sms = User.userTypeProprietaryInfo.mobiles[0].number; |  | ||||||
|                     MFC.type = "sms"; |  | ||||||
|                 } else if(User.userTypeProprietaryInfo.mfa.method == "email") { |  | ||||||
|                     MFC.email = User.email; |  | ||||||
|                     MFC.type = "email"; |  | ||||||
|                 } |  | ||||||
|             } else { |  | ||||||
|                 MFC.type = "disabled"; |  | ||||||
|             } |  | ||||||
|             MFC.to_json(Answer); |  | ||||||
|             return ReturnObject(Answer); |  | ||||||
|         } |  | ||||||
|         NotFound(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     void RESTAPI_submfa_handler::DoPut() { |  | ||||||
|  |  | ||||||
|         try { |  | ||||||
|             const auto & Body = ParsedBody_; |  | ||||||
|  |  | ||||||
|             SecurityObjects::SubMfaConfig MFC; |  | ||||||
|  |  | ||||||
|             if (!MFC.from_json(Body)) { |  | ||||||
|                 return BadRequest(RESTAPI::Errors::InvalidJSONDocument); |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             if (MFC.type == "disabled") { |  | ||||||
|                 SecurityObjects::UserInfo User; |  | ||||||
|                 StorageService()->SubDB().GetUserById(UserInfo_.userinfo.id, User); |  | ||||||
|                 User.userTypeProprietaryInfo.mfa.enabled = false; |  | ||||||
|                 StorageService()->SubDB().UpdateUserInfo(UserInfo_.userinfo.email, UserInfo_.userinfo.id, User); |  | ||||||
|  |  | ||||||
|                 Poco::JSON::Object Answer; |  | ||||||
|                 MFC.to_json(Answer); |  | ||||||
|                 return ReturnObject(Answer); |  | ||||||
|             } else if (MFC.type == "email") { |  | ||||||
|                 SecurityObjects::UserInfo User; |  | ||||||
|  |  | ||||||
|                 StorageService()->SubDB().GetUserById(UserInfo_.userinfo.id, User); |  | ||||||
|                 User.userTypeProprietaryInfo.mfa.enabled = true; |  | ||||||
|                 User.userTypeProprietaryInfo.mfa.method = "email"; |  | ||||||
|                 StorageService()->SubDB().UpdateUserInfo(UserInfo_.userinfo.email, UserInfo_.userinfo.id, User); |  | ||||||
|  |  | ||||||
|                 MFC.sms = MFC.sms; |  | ||||||
|                 MFC.type = "email"; |  | ||||||
|                 MFC.email = UserInfo_.userinfo.email; |  | ||||||
|                 MFC.id = MicroService::instance().CreateUUID(); |  | ||||||
|  |  | ||||||
|                 Poco::JSON::Object Answer; |  | ||||||
|                 MFC.to_json(Answer); |  | ||||||
|                 return ReturnObject(Answer); |  | ||||||
|  |  | ||||||
|             } else if (MFC.type == "sms") { |  | ||||||
|                 if (GetBoolParameter("startValidation", false)) { |  | ||||||
|                     if (MFC.sms.empty()) { |  | ||||||
|                         return BadRequest(RESTAPI::Errors::SMSMissingPhoneNumber); |  | ||||||
|                     } |  | ||||||
|  |  | ||||||
|                     if (SMSSender()->StartValidation(MFC.sms, UserInfo_.userinfo.email)) { |  | ||||||
|                         return OK(); |  | ||||||
|                     } else { |  | ||||||
|                         return InternalError(RESTAPI::Errors::SMSTryLater); |  | ||||||
|                     } |  | ||||||
|                 } else if (GetBoolParameter("completeValidation", false)) { |  | ||||||
|                     auto ChallengeCode = GetParameter("challengeCode", ""); |  | ||||||
|                     if (ChallengeCode.empty()) { |  | ||||||
|                         return BadRequest(RESTAPI::Errors::SMSMissingChallenge); |  | ||||||
|                     } |  | ||||||
|                     if (MFC.sms.empty()) { |  | ||||||
|                         return BadRequest(RESTAPI::Errors::SMSMissingPhoneNumber); |  | ||||||
|                     } |  | ||||||
|                     if (SMSSender()->CompleteValidation(MFC.sms, ChallengeCode, UserInfo_.userinfo.email)) { |  | ||||||
|                         SecurityObjects::UserInfo User; |  | ||||||
|  |  | ||||||
|                         StorageService()->SubDB().GetUserById(UserInfo_.userinfo.id, User); |  | ||||||
|                         User.userTypeProprietaryInfo.mfa.enabled = true; |  | ||||||
|                         User.userTypeProprietaryInfo.mfa.method = "sms"; |  | ||||||
|                         SecurityObjects::MobilePhoneNumber PhoneNumber; |  | ||||||
|                         PhoneNumber.number = MFC.sms; |  | ||||||
|                         PhoneNumber.primary = true; |  | ||||||
|                         PhoneNumber.verified = true; |  | ||||||
|                         User.userTypeProprietaryInfo.mobiles.clear(); |  | ||||||
|                         User.userTypeProprietaryInfo.mobiles.push_back(PhoneNumber); |  | ||||||
|  |  | ||||||
|                         StorageService()->SubDB().UpdateUserInfo(UserInfo_.userinfo.email, UserInfo_.userinfo.id, User); |  | ||||||
|  |  | ||||||
|                         MFC.sms = MFC.sms; |  | ||||||
|                         MFC.type = "sms"; |  | ||||||
|                         MFC.email = UserInfo_.userinfo.email; |  | ||||||
|                         MFC.id = MicroService::instance().CreateUUID(); |  | ||||||
|  |  | ||||||
|                         Poco::JSON::Object Answer; |  | ||||||
|                         MFC.to_json(Answer); |  | ||||||
|  |  | ||||||
|                         return ReturnObject(Answer); |  | ||||||
|  |  | ||||||
|                     } else { |  | ||||||
|                         return InternalError(RESTAPI::Errors::SMSTryLater); |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         } catch (const Poco::Exception &E) { |  | ||||||
|             Logger_.log(E); |  | ||||||
|         } |  | ||||||
|         return BadRequest(RESTAPI::Errors::MissingOrInvalidParameters); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
| } |  | ||||||
| @@ -1,27 +0,0 @@ | |||||||
| // |  | ||||||
| // Created by stephane bourque on 2021-12-01. |  | ||||||
| // |  | ||||||
|  |  | ||||||
| #pragma once |  | ||||||
|  |  | ||||||
| #include "framework/MicroService.h" |  | ||||||
|  |  | ||||||
| namespace OpenWifi { |  | ||||||
|     class RESTAPI_submfa_handler : public RESTAPIHandler { |  | ||||||
|     public: |  | ||||||
|         RESTAPI_submfa_handler(const RESTAPIHandler::BindingMap &bindings, Poco::Logger &L, RESTAPI_GenericServer &Server, uint64_t TransactionId, bool Internal) |  | ||||||
|         : RESTAPIHandler(bindings, L, |  | ||||||
|                          std::vector<std::string>{Poco::Net::HTTPRequest::HTTP_PUT, |  | ||||||
|                                                   Poco::Net::HTTPRequest::HTTP_GET, |  | ||||||
|                                                   Poco::Net::HTTPRequest::HTTP_OPTIONS}, |  | ||||||
|                                                   Server, |  | ||||||
|                                                   TransactionId, |  | ||||||
|                                                   Internal, true, false , RateLimit{.Interval=1000,.MaxCalls=10}, |  | ||||||
|                                                   true) {} |  | ||||||
|         static auto PathName() { return std::list<std::string>{"/api/v1/submfa"}; }; |  | ||||||
|         void DoGet() final; |  | ||||||
|         void DoPost() final {}; |  | ||||||
|         void DoDelete() final {}; |  | ||||||
|         void DoPut() final ; |  | ||||||
|     }; |  | ||||||
| } |  | ||||||
| @@ -1,165 +0,0 @@ | |||||||
| // |  | ||||||
| // Created by stephane bourque on 2021-11-30. |  | ||||||
| // |  | ||||||
|  |  | ||||||
| #include "RESTAPI_suboauth2_handler.h" |  | ||||||
| #include "AuthService.h" |  | ||||||
| #include "MFAServer.h" |  | ||||||
| #include "framework/MicroService.h" |  | ||||||
| #include "StorageService.h" |  | ||||||
| #include "RESTAPI/RESTAPI_db_helpers.h" |  | ||||||
|  |  | ||||||
| namespace OpenWifi { |  | ||||||
|  |  | ||||||
|     void RESTAPI_suboauth2_handler::DoGet() { |  | ||||||
|         bool Expired = false, Contacted = false; |  | ||||||
|         if (!IsAuthorized(Expired, Contacted, true)) { |  | ||||||
|             if(Expired) |  | ||||||
|                 return UnAuthorized(RESTAPI::Errors::EXPIRED_TOKEN); |  | ||||||
|             return UnAuthorized(RESTAPI::Errors::INVALID_TOKEN); |  | ||||||
|         } |  | ||||||
|         bool GetMe = GetBoolParameter(RESTAPI::Protocol::ME, false); |  | ||||||
|         if(GetMe) { |  | ||||||
|             Logger_.information(fmt::format("REQUEST-ME({}): Request for {}", Request->clientAddress().toString(), |  | ||||||
|                                              UserInfo_.userinfo.email)); |  | ||||||
|             Poco::JSON::Object Me; |  | ||||||
|             SecurityObjects::UserInfo   ReturnedUser = UserInfo_.userinfo; |  | ||||||
|             Sanitize(UserInfo_, ReturnedUser); |  | ||||||
|             ReturnedUser.to_json(Me); |  | ||||||
|             return ReturnObject(Me); |  | ||||||
|         } |  | ||||||
|         BadRequest(RESTAPI::Errors::UnrecognizedRequest); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     void RESTAPI_suboauth2_handler::DoDelete() { |  | ||||||
|         auto Token = GetBinding(RESTAPI::Protocol::TOKEN, ""); |  | ||||||
|         std::string SessionToken; |  | ||||||
|         try { |  | ||||||
|             Poco::Net::OAuth20Credentials Auth(*Request); |  | ||||||
|             if (Auth.getScheme() == "Bearer") { |  | ||||||
|                 SessionToken = Auth.getBearerToken(); |  | ||||||
|             } |  | ||||||
|         } catch (const Poco::Exception &E) { |  | ||||||
|             return BadRequest(RESTAPI::Errors::MissingOrInvalidParameters); |  | ||||||
|         } |  | ||||||
|         if (Token.empty() || (Token != SessionToken)) { |  | ||||||
|             return BadRequest(RESTAPI::Errors::MissingOrInvalidParameters); |  | ||||||
|         } |  | ||||||
|         AuthService()->SubLogout(Token); |  | ||||||
|         return ReturnStatus(Poco::Net::HTTPResponse::HTTP_NO_CONTENT, true); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     void RESTAPI_suboauth2_handler::DoPost() { |  | ||||||
|         const auto & Obj = ParsedBody_; |  | ||||||
|         auto userId = GetS(RESTAPI::Protocol::USERID, Obj); |  | ||||||
|         auto password = GetS(RESTAPI::Protocol::PASSWORD, Obj); |  | ||||||
|         auto newPassword = GetS(RESTAPI::Protocol::NEWPASSWORD, Obj); |  | ||||||
|         auto refreshToken = GetS("refreshToken", Obj); |  | ||||||
|         auto grant_type = GetParameter("grant_type"); |  | ||||||
|  |  | ||||||
|         Poco::toLowerInPlace(userId); |  | ||||||
|  |  | ||||||
|         if(!refreshToken.empty() && grant_type == "refresh_token") { |  | ||||||
|             SecurityObjects::UserInfoAndPolicy UInfo; |  | ||||||
|             if(AuthService()->RefreshSubToken(*Request, refreshToken, UInfo)) { |  | ||||||
|                 Poco::JSON::Object  Answer; |  | ||||||
|                 UInfo.webtoken.to_json(Answer); |  | ||||||
|                 return ReturnObject(Answer); |  | ||||||
|             } else { |  | ||||||
|                 return UnAuthorized(RESTAPI::Errors::CANNOT_REFRESH_TOKEN); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         if(GetBoolParameter(RESTAPI::Protocol::REQUIREMENTS)) { |  | ||||||
|             Logger_.information(fmt::format("POLICY-REQUEST({}): Request.", Request->clientAddress().toString())); |  | ||||||
|             Poco::JSON::Object  Answer; |  | ||||||
|             Answer.set(RESTAPI::Protocol::PASSWORDPATTERN, AuthService()->SubPasswordValidationExpression()); |  | ||||||
|             Answer.set(RESTAPI::Protocol::ACCESSPOLICY, AuthService()->GetSubAccessPolicy()); |  | ||||||
|             Answer.set(RESTAPI::Protocol::PASSWORDPOLICY, AuthService()->GetSubPasswordPolicy()); |  | ||||||
|             return ReturnObject(Answer); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         if(GetBoolParameter(RESTAPI::Protocol::FORGOTPASSWORD)) { |  | ||||||
|             SecurityObjects::UserInfo UInfo1; |  | ||||||
|             auto UserExists = StorageService()->SubDB().GetUserByEmail(userId,UInfo1); |  | ||||||
|             if(UserExists) { |  | ||||||
|                 Logger_.information(fmt::format("FORGOTTEN-PASSWORD({}): Request for {}", Request->clientAddress().toString(), userId)); |  | ||||||
|                 SecurityObjects::ActionLink NewLink; |  | ||||||
|  |  | ||||||
|                 NewLink.action = OpenWifi::SecurityObjects::LinkActions::SUB_FORGOT_PASSWORD; |  | ||||||
|                 NewLink.id = MicroService::CreateUUID(); |  | ||||||
|                 NewLink.userId = UInfo1.id; |  | ||||||
|                 NewLink.created = OpenWifi::Now(); |  | ||||||
|                 NewLink.expires = NewLink.created + (24*60*60); |  | ||||||
|                 NewLink.userAction = false; |  | ||||||
|                 StorageService()->ActionLinksDB().CreateAction(NewLink); |  | ||||||
|  |  | ||||||
|                 Poco::JSON::Object ReturnObj; |  | ||||||
|                 SecurityObjects::UserInfoAndPolicy UInfo; |  | ||||||
|                 UInfo.webtoken.userMustChangePassword = true; |  | ||||||
|                 UInfo.webtoken.to_json(ReturnObj); |  | ||||||
|                 return ReturnObject(ReturnObj); |  | ||||||
|             } else { |  | ||||||
|                 Poco::JSON::Object ReturnObj; |  | ||||||
|                 SecurityObjects::UserInfoAndPolicy UInfo; |  | ||||||
|                 UInfo.webtoken.userMustChangePassword = true; |  | ||||||
|                 UInfo.webtoken.to_json(ReturnObj); |  | ||||||
|                 return ReturnObject(ReturnObj); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         if(GetBoolParameter(RESTAPI::Protocol::RESENDMFACODE)) { |  | ||||||
|             Logger_.information(fmt::format("RESEND-MFA-CODE({}): Request for {}", Request->clientAddress().toString(), userId)); |  | ||||||
|             if(Obj->has("uuid")) { |  | ||||||
|                 auto uuid = Obj->get("uuid").toString(); |  | ||||||
|                 if(MFAServer()->ResendCode(uuid)) |  | ||||||
|                     return OK(); |  | ||||||
|             } |  | ||||||
|             return UnAuthorized(RESTAPI::Errors::BAD_MFA_TRANSACTION); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         if(GetBoolParameter(RESTAPI::Protocol::COMPLETEMFACHALLENGE)) { |  | ||||||
|             Logger_.information(fmt::format("COMPLETE-MFA-CHALLENGE({}): Request for {}", Request->clientAddress().toString(), userId)); |  | ||||||
|             if(Obj->has("uuid") && Obj->has("answer")) { |  | ||||||
|                 SecurityObjects::UserInfoAndPolicy UInfo; |  | ||||||
|                 if(MFAServer()->CompleteMFAChallenge(Obj,UInfo)) { |  | ||||||
|                     Poco::JSON::Object ReturnObj; |  | ||||||
|                     UInfo.webtoken.to_json(ReturnObj); |  | ||||||
|                     return ReturnObject(ReturnObj); |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|             return UnAuthorized(RESTAPI::Errors::MFA_FAILURE); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         SecurityObjects::UserInfoAndPolicy UInfo; |  | ||||||
|         bool Expired=false; |  | ||||||
|         auto Code=AuthService()->AuthorizeSub(userId, password, newPassword, UInfo, Expired); |  | ||||||
|         if (Code==SUCCESS) { |  | ||||||
|             Poco::JSON::Object ReturnObj; |  | ||||||
|             if(AuthService()->RequiresMFA(UInfo)) { |  | ||||||
|                 if(MFAServer()->StartMFAChallenge(UInfo, ReturnObj)) { |  | ||||||
|                     return ReturnObject(ReturnObj); |  | ||||||
|                 } |  | ||||||
|                 Logger_.warning("MFA Seems to be broken. Please fix. Disabling MFA checking for now."); |  | ||||||
|             } |  | ||||||
|             UInfo.webtoken.to_json(ReturnObj); |  | ||||||
|             return ReturnObject(ReturnObj); |  | ||||||
|         } else { |  | ||||||
|             switch(Code) { |  | ||||||
|                 case INVALID_CREDENTIALS: |  | ||||||
|                     return UnAuthorized(RESTAPI::Errors::INVALID_CREDENTIALS); |  | ||||||
|                 case PASSWORD_INVALID: |  | ||||||
|                     return UnAuthorized(RESTAPI::Errors::PASSWORD_INVALID); |  | ||||||
|                 case PASSWORD_ALREADY_USED: |  | ||||||
|                     return UnAuthorized(RESTAPI::Errors::PASSWORD_ALREADY_USED); |  | ||||||
|                 case USERNAME_PENDING_VERIFICATION: |  | ||||||
|                     return UnAuthorized(RESTAPI::Errors::USERNAME_PENDING_VERIFICATION); |  | ||||||
|                 case PASSWORD_CHANGE_REQUIRED: |  | ||||||
|                     return UnAuthorized(RESTAPI::Errors::PASSWORD_CHANGE_REQUIRED); |  | ||||||
|                 default: |  | ||||||
|                     return UnAuthorized(RESTAPI::Errors::INVALID_CREDENTIALS); break; |  | ||||||
|             } |  | ||||||
|             return; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,27 +0,0 @@ | |||||||
| // |  | ||||||
| // Created by stephane bourque on 2021-11-30. |  | ||||||
| // |  | ||||||
|  |  | ||||||
| #pragma once |  | ||||||
| #include "framework/MicroService.h" |  | ||||||
|  |  | ||||||
| namespace OpenWifi { |  | ||||||
|     class RESTAPI_suboauth2_handler : public RESTAPIHandler { |  | ||||||
|     public: |  | ||||||
|         RESTAPI_suboauth2_handler(const RESTAPIHandler::BindingMap &bindings, Poco::Logger &L, RESTAPI_GenericServer &Server, uint64_t TransactionId, bool Internal) |  | ||||||
|         : RESTAPIHandler(bindings, L, |  | ||||||
|                          std::vector<std::string>{Poco::Net::HTTPRequest::HTTP_POST, |  | ||||||
|                                                   Poco::Net::HTTPRequest::HTTP_DELETE, |  | ||||||
|                                                   Poco::Net::HTTPRequest::HTTP_GET, |  | ||||||
|                                                   Poco::Net::HTTPRequest::HTTP_OPTIONS}, |  | ||||||
|                                                   Server, |  | ||||||
|                                                   TransactionId, |  | ||||||
|                                                   Internal, false, false , RateLimit{.Interval=1000,.MaxCalls=10}, |  | ||||||
|                                                   false) {} |  | ||||||
|         static auto PathName() { return std::list<std::string>{"/api/v1/suboauth2/{token}","/api/v1/suboauth2"}; }; |  | ||||||
|         void DoGet() final; |  | ||||||
|         void DoPost() final; |  | ||||||
|         void DoDelete() final; |  | ||||||
|         void DoPut() final {}; |  | ||||||
|     }; |  | ||||||
| } |  | ||||||
| @@ -1,36 +0,0 @@ | |||||||
| // |  | ||||||
| // Created by stephane bourque on 2021-11-16. |  | ||||||
| // |  | ||||||
|  |  | ||||||
| #include "RESTAPI_subpreferences.h" |  | ||||||
| #include "StorageService.h" |  | ||||||
|  |  | ||||||
| namespace OpenWifi { |  | ||||||
|  |  | ||||||
|     void RESTAPI_subpreferences::DoGet() { |  | ||||||
|         SecurityObjects::Preferences    P; |  | ||||||
|         Poco::JSON::Object  Answer; |  | ||||||
|         StorageService()->SubPreferencesDB().GetPreferences(UserInfo_.userinfo.id, P); |  | ||||||
|         P.to_json(Answer); |  | ||||||
|         ReturnObject(Answer); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     void RESTAPI_subpreferences::DoPut() { |  | ||||||
|  |  | ||||||
|         SecurityObjects::Preferences    P; |  | ||||||
|  |  | ||||||
|         const auto & RawObject = ParsedBody_; |  | ||||||
|         if(!P.from_json(RawObject)) { |  | ||||||
|             return BadRequest(RESTAPI::Errors::InvalidJSONDocument); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         P.id = UserInfo_.userinfo.id; |  | ||||||
|         P.modified = OpenWifi::Now(); |  | ||||||
|         StorageService()->SubPreferencesDB().SetPreferences(P); |  | ||||||
|  |  | ||||||
|         Poco::JSON::Object  Answer; |  | ||||||
|         P.to_json(Answer); |  | ||||||
|         ReturnObject(Answer); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
| } |  | ||||||
| @@ -1,27 +0,0 @@ | |||||||
| // |  | ||||||
| // Created by stephane bourque on 2021-11-16. |  | ||||||
| // |  | ||||||
|  |  | ||||||
| #pragma once |  | ||||||
|  |  | ||||||
| #include "framework/MicroService.h" |  | ||||||
|  |  | ||||||
| namespace OpenWifi { |  | ||||||
|     class RESTAPI_subpreferences : public RESTAPIHandler { |  | ||||||
|     public: |  | ||||||
|         RESTAPI_subpreferences(const RESTAPIHandler::BindingMap &bindings, Poco::Logger &L, RESTAPI_GenericServer &Server, uint64_t TransactionId, bool Internal) |  | ||||||
|         : RESTAPIHandler(bindings, L, |  | ||||||
|                          std::vector<std::string>{ |  | ||||||
|             Poco::Net::HTTPRequest::HTTP_GET, |  | ||||||
|             Poco::Net::HTTPRequest::HTTP_PUT, |  | ||||||
|             Poco::Net::HTTPRequest::HTTP_OPTIONS}, |  | ||||||
|             Server, |  | ||||||
|             TransactionId, |  | ||||||
|             Internal) {} |  | ||||||
|         static auto PathName() { return std::list<std::string>{"/api/v1/subpreferences"}; }; |  | ||||||
|         void DoGet() final; |  | ||||||
|         void DoPut() final; |  | ||||||
|         void DoPost() final {}; |  | ||||||
|         void DoDelete() final {}; |  | ||||||
|     }; |  | ||||||
| } |  | ||||||
| @@ -1,37 +0,0 @@ | |||||||
| // |  | ||||||
| // Created by stephane bourque on 2022-01-31. |  | ||||||
| // |  | ||||||
|  |  | ||||||
| #include "RESTAPI_subtotp_handler.h" |  | ||||||
|  |  | ||||||
| #include "TotpCache.h" |  | ||||||
|  |  | ||||||
| namespace OpenWifi { |  | ||||||
|  |  | ||||||
|     void RESTAPI_subtotp_handler::DoGet() { |  | ||||||
|  |  | ||||||
|         auto Reset = GetBoolParameter("reset",false); |  | ||||||
|         std::string QRCode; |  | ||||||
|  |  | ||||||
|         if(TotpCache()->StartValidation(UserInfo_.userinfo,true,QRCode,Reset)) { |  | ||||||
|             return SendFileContent(QRCode, "image/svg+xml","qrcode.svg"); |  | ||||||
|         } |  | ||||||
|         return BadRequest(RESTAPI::Errors::InvalidCommand); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     void RESTAPI_subtotp_handler::DoPut() { |  | ||||||
|         auto Value = GetParameter("value",""); |  | ||||||
|         auto nextIndex = GetParameter("index",0); |  | ||||||
|         bool moreCodes=false; |  | ||||||
|  |  | ||||||
|         RESTAPI::Errors::msg    Error; |  | ||||||
|         if(TotpCache()->ContinueValidation(UserInfo_.userinfo,true,Value,nextIndex,moreCodes, Error )) { |  | ||||||
|             Poco::JSON::Object Answer; |  | ||||||
|             Answer.set("nextIndex", nextIndex); |  | ||||||
|             Answer.set("moreCodes", moreCodes); |  | ||||||
|             return ReturnObject(Answer); |  | ||||||
|         } |  | ||||||
|         return BadRequest(Error); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
| } |  | ||||||
| @@ -1,29 +0,0 @@ | |||||||
| // |  | ||||||
| // Created by stephane bourque on 2022-01-31. |  | ||||||
| // |  | ||||||
|  |  | ||||||
| #include "framework/MicroService.h" |  | ||||||
|  |  | ||||||
| namespace OpenWifi { |  | ||||||
|     class RESTAPI_subtotp_handler : public RESTAPIHandler { |  | ||||||
|     public: |  | ||||||
|         RESTAPI_subtotp_handler(const RESTAPIHandler::BindingMap &bindings, Poco::Logger &L, RESTAPI_GenericServer &Server, uint64_t TransactionId, bool Internal) |  | ||||||
|                 : RESTAPIHandler(bindings, L, |  | ||||||
|                                  std::vector<std::string> |  | ||||||
|                                          { |  | ||||||
|                                                  Poco::Net::HTTPRequest::HTTP_GET, |  | ||||||
|                                                  Poco::Net::HTTPRequest::HTTP_PUT, |  | ||||||
|                                                  Poco::Net::HTTPRequest::HTTP_OPTIONS |  | ||||||
|                                          }, |  | ||||||
|                                  Server, |  | ||||||
|                                  TransactionId, |  | ||||||
|                                  Internal) {} |  | ||||||
|         static auto PathName() { return std::list<std::string>{"/api/v1/subtotp"}; }; |  | ||||||
|         void DoGet() final; |  | ||||||
|         void DoPost() final {}; |  | ||||||
|         void DoDelete() final {}; |  | ||||||
|         void DoPut() final; |  | ||||||
|     private: |  | ||||||
|  |  | ||||||
|     }; |  | ||||||
| } |  | ||||||
| @@ -1,316 +0,0 @@ | |||||||
| // |  | ||||||
| // Created by stephane bourque on 2021-11-30. |  | ||||||
| // |  | ||||||
|  |  | ||||||
| #include "RESTAPI_subuser_handler.h" |  | ||||||
| #include "StorageService.h" |  | ||||||
| #include "framework/ow_constants.h" |  | ||||||
| #include "SMSSender.h" |  | ||||||
| #include "SMTPMailerService.h" |  | ||||||
| #include "ACLProcessor.h" |  | ||||||
| #include "AuthService.h" |  | ||||||
| #include "RESTAPI/RESTAPI_db_helpers.h" |  | ||||||
| #include "MFAServer.h" |  | ||||||
| #include "TotpCache.h" |  | ||||||
|  |  | ||||||
| namespace OpenWifi { |  | ||||||
|  |  | ||||||
|     void RESTAPI_subuser_handler::DoGet() { |  | ||||||
|         std::string Id = GetBinding("id", ""); |  | ||||||
|         if(Id.empty()) { |  | ||||||
|             return BadRequest(RESTAPI::Errors::MissingUserID); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         Poco::toLowerInPlace(Id); |  | ||||||
|         std::string Arg; |  | ||||||
|         SecurityObjects::UserInfo   UInfo; |  | ||||||
|         if(HasParameter("byEmail",Arg) && Arg=="true") { |  | ||||||
|             if(!StorageService()->SubDB().GetUserByEmail(Id,UInfo)) { |  | ||||||
|                 return NotFound(); |  | ||||||
|             } |  | ||||||
|         } else if(!StorageService()->SubDB().GetUserById(Id,UInfo)) { |  | ||||||
|             return NotFound(); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         Poco::JSON::Object  UserInfoObject; |  | ||||||
|         Sanitize(UserInfo_, UInfo); |  | ||||||
|         UInfo.to_json(UserInfoObject); |  | ||||||
|         ReturnObject(UserInfoObject); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     void RESTAPI_subuser_handler::DoDelete() { |  | ||||||
|         std::string Id = GetBinding("id", ""); |  | ||||||
|         if(Id.empty()) { |  | ||||||
|             return BadRequest(RESTAPI::Errors::MissingUserID); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         SecurityObjects::UserInfo TargetUser; |  | ||||||
|         if(!StorageService()->SubDB().GetUserById(Id,TargetUser)) { |  | ||||||
|             return NotFound(); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         if(TargetUser.userRole != SecurityObjects::SUBSCRIBER) { |  | ||||||
|             return BadRequest(RESTAPI::Errors::InvalidUserRole); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         if(!Internal_ && !ACLProcessor::Can(UserInfo_.userinfo, TargetUser,ACLProcessor::DELETE)) { |  | ||||||
|             return UnAuthorized(RESTAPI::Errors::ACCESS_DENIED); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         if(!StorageService()->SubDB().DeleteUser(UserInfo_.userinfo.email,Id)) { |  | ||||||
|             return NotFound(); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         AuthService()->DeleteSubUserFromCache(Id); |  | ||||||
|         StorageService()->SubTokenDB().RevokeAllTokens(TargetUser.email); |  | ||||||
|         StorageService()->SubPreferencesDB().DeleteRecord("id", Id); |  | ||||||
|         StorageService()->SubAvatarDB().DeleteRecord("id", Id); |  | ||||||
|         Logger_.information(fmt::format("User '{}' deleted by '{}'.",Id,UserInfo_.userinfo.email)); |  | ||||||
|         OK(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     void RESTAPI_subuser_handler::DoPost() { |  | ||||||
|         std::string Id = GetBinding("id", ""); |  | ||||||
|         if(Id!="0") { |  | ||||||
|             return BadRequest(RESTAPI::Errors::IdMustBe0); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         SecurityObjects::UserInfo   NewUser; |  | ||||||
|         const auto & RawObject = ParsedBody_; |  | ||||||
|         if(!NewUser.from_json(RawObject)) { |  | ||||||
|             return BadRequest(RESTAPI::Errors::InvalidJSONDocument); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         if(NewUser.userRole == SecurityObjects::UNKNOWN || NewUser.userRole != SecurityObjects::SUBSCRIBER) { |  | ||||||
|             return BadRequest(RESTAPI::Errors::InvalidUserRole); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         Poco::toLowerInPlace(NewUser.email); |  | ||||||
|         SecurityObjects::UserInfo   Existing; |  | ||||||
|         if(StorageService()->SubDB().GetUserByEmail(NewUser.email,Existing)) { |  | ||||||
|             return BadRequest(RESTAPI::Errors::UserAlreadyExists); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         if(!Internal_ && !ACLProcessor::Can(UserInfo_.userinfo,NewUser,ACLProcessor::CREATE)) { |  | ||||||
|             return UnAuthorized(RESTAPI::Errors::ACCESS_DENIED); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         Poco::toLowerInPlace(NewUser.email); |  | ||||||
|         if(!Utils::ValidEMailAddress(NewUser.email)) { |  | ||||||
|             return BadRequest(RESTAPI::Errors::InvalidEmailAddress); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         if(!NewUser.currentPassword.empty()) { |  | ||||||
|             if(!AuthService()->ValidateSubPassword(NewUser.currentPassword)) { |  | ||||||
|                 return BadRequest(RESTAPI::Errors::InvalidPassword); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         if(NewUser.name.empty()) |  | ||||||
|             NewUser.name = NewUser.email; |  | ||||||
|  |  | ||||||
|         //  You cannot enable MFA during user creation |  | ||||||
|         NewUser.userTypeProprietaryInfo.mfa.enabled = false; |  | ||||||
|         NewUser.userTypeProprietaryInfo.mfa.method = ""; |  | ||||||
|         NewUser.userTypeProprietaryInfo.mobiles.clear(); |  | ||||||
|         NewUser.userTypeProprietaryInfo.authenticatorSecret.clear(); |  | ||||||
|  |  | ||||||
|         if(!StorageService()->SubDB().CreateUser(UserInfo_.userinfo.email, NewUser)) { |  | ||||||
|             Logger_.information(fmt::format("Could not add user '{}'.",NewUser.email)); |  | ||||||
|             return BadRequest(RESTAPI::Errors::RecordNotCreated); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         if(GetParameter("email_verification","false")=="true") { |  | ||||||
|             if(AuthService::VerifySubEmail(NewUser)) |  | ||||||
|                 Logger_.information(fmt::format("Verification e-mail requested for {}",NewUser.email)); |  | ||||||
|             StorageService()->SubDB().UpdateUserInfo(UserInfo_.userinfo.email,NewUser.id,NewUser); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         if(!StorageService()->SubDB().GetUserByEmail(NewUser.email, NewUser)) { |  | ||||||
|             Logger_.information(fmt::format("User '{}' but not retrieved.",NewUser.email)); |  | ||||||
|             return NotFound(); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         Poco::JSON::Object  UserInfoObject; |  | ||||||
|         Sanitize(UserInfo_, NewUser); |  | ||||||
|         NewUser.to_json(UserInfoObject); |  | ||||||
|         ReturnObject(UserInfoObject); |  | ||||||
|         Logger_.information(fmt::format("User '{}' has been added by '{}')",NewUser.email, UserInfo_.userinfo.email)); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     void RESTAPI_subuser_handler::DoPut() { |  | ||||||
|         std::string Id = GetBinding("id", ""); |  | ||||||
|         if(Id.empty()) { |  | ||||||
|             return BadRequest(RESTAPI::Errors::MissingUserID); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         SecurityObjects::UserInfo   Existing; |  | ||||||
|         if(!StorageService()->SubDB().GetUserById(Id,Existing)) { |  | ||||||
|             return NotFound(); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         if(!Internal_ && !ACLProcessor::Can(UserInfo_.userinfo,Existing,ACLProcessor::MODIFY)) { |  | ||||||
|             return UnAuthorized(RESTAPI::Errors::ACCESS_DENIED); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         if(GetBoolParameter("resetMFA")) { |  | ||||||
|             if( (UserInfo_.userinfo.userRole == SecurityObjects::ROOT) || |  | ||||||
|                 (UserInfo_.userinfo.userRole == SecurityObjects::ADMIN && Existing.userRole!=SecurityObjects::ROOT) || |  | ||||||
|                 (UserInfo_.userinfo.id == Id)) { |  | ||||||
|                 Existing.userTypeProprietaryInfo.mfa.enabled = false; |  | ||||||
|                 Existing.userTypeProprietaryInfo.mfa.method.clear(); |  | ||||||
|                 Existing.userTypeProprietaryInfo.mobiles.clear(); |  | ||||||
|                 Existing.modified = OpenWifi::Now(); |  | ||||||
|                 Existing.notes.push_back( SecurityObjects::NoteInfo{ |  | ||||||
|                         .created=OpenWifi::Now(), |  | ||||||
|                         .createdBy=UserInfo_.userinfo.email, |  | ||||||
|                         .note="MFA Reset by " + UserInfo_.userinfo.email}); |  | ||||||
|                 StorageService()->SubDB().UpdateUserInfo(UserInfo_.userinfo.email,Id,Existing); |  | ||||||
|                 SecurityObjects::UserInfo   NewUserInfo; |  | ||||||
|                 StorageService()->SubDB().GetUserByEmail(UserInfo_.userinfo.email,NewUserInfo); |  | ||||||
|                 Poco::JSON::Object  ModifiedObject; |  | ||||||
|                 Sanitize(UserInfo_, NewUserInfo); |  | ||||||
|                 NewUserInfo.to_json(ModifiedObject); |  | ||||||
|                 return ReturnObject(ModifiedObject); |  | ||||||
|             } else { |  | ||||||
|                 return UnAuthorized(RESTAPI::Errors::ACCESS_DENIED); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         if(GetBoolParameter("forgotPassword")) { |  | ||||||
|             Existing.changePassword = true; |  | ||||||
|             Logger_.information(fmt::format("FORGOTTEN-PASSWORD({}): Request for {}", Request->clientAddress().toString(), Existing.email)); |  | ||||||
|  |  | ||||||
|             SecurityObjects::ActionLink NewLink; |  | ||||||
|             NewLink.action = OpenWifi::SecurityObjects::LinkActions::SUB_FORGOT_PASSWORD; |  | ||||||
|             NewLink.id = MicroService::CreateUUID(); |  | ||||||
|             NewLink.userId = Existing.id; |  | ||||||
|             NewLink.created = OpenWifi::Now(); |  | ||||||
|             NewLink.expires = NewLink.created + (24*60*60); |  | ||||||
|             NewLink.userAction = false; |  | ||||||
|             StorageService()->ActionLinksDB().CreateAction(NewLink); |  | ||||||
|  |  | ||||||
|             return OK(); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         SecurityObjects::UserInfo   NewUser; |  | ||||||
|         const auto & RawObject = ParsedBody_; |  | ||||||
|         if(!NewUser.from_json(RawObject)) { |  | ||||||
|             return BadRequest(RESTAPI::Errors::InvalidJSONDocument); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         // some basic validations |  | ||||||
|         if(RawObject->has("userRole") && |  | ||||||
|             (SecurityObjects::UserTypeFromString(RawObject->get("userRole").toString())==SecurityObjects::UNKNOWN || |  | ||||||
|             SecurityObjects::UserTypeFromString(RawObject->get("userRole").toString())==SecurityObjects::SUBSCRIBER)) { |  | ||||||
|             return BadRequest(RESTAPI::Errors::InvalidUserRole); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         // The only valid things to change are: changePassword, name, |  | ||||||
|         AssignIfPresent(RawObject,"name", Existing.name); |  | ||||||
|         AssignIfPresent(RawObject,"description", Existing.description); |  | ||||||
|         AssignIfPresent(RawObject,"owner", Existing.owner); |  | ||||||
|         AssignIfPresent(RawObject,"location", Existing.location); |  | ||||||
|         AssignIfPresent(RawObject,"locale", Existing.locale); |  | ||||||
|         AssignIfPresent(RawObject,"changePassword", Existing.changePassword); |  | ||||||
|         AssignIfPresent(RawObject,"suspended", Existing.suspended); |  | ||||||
|         AssignIfPresent(RawObject,"blackListed", Existing.blackListed); |  | ||||||
|  |  | ||||||
|         if(RawObject->has("userRole")) { |  | ||||||
|             auto NewRole = SecurityObjects::UserTypeFromString(RawObject->get("userRole").toString()); |  | ||||||
|             if(NewRole!=Existing.userRole) { |  | ||||||
|                 if(UserInfo_.userinfo.userRole!=SecurityObjects::ROOT && NewRole==SecurityObjects::ROOT) { |  | ||||||
|                     return UnAuthorized(RESTAPI::Errors::ACCESS_DENIED); |  | ||||||
|                 } |  | ||||||
|                 if(Id==UserInfo_.userinfo.id) { |  | ||||||
|                     return UnAuthorized(RESTAPI::Errors::ACCESS_DENIED); |  | ||||||
|                 } |  | ||||||
|                 Existing.userRole = NewRole; |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         if(RawObject->has("notes")) { |  | ||||||
|             SecurityObjects::NoteInfoVec NIV; |  | ||||||
|             NIV = RESTAPI_utils::to_object_array<SecurityObjects::NoteInfo>(RawObject->get("notes").toString()); |  | ||||||
|             for(auto const &i:NIV) { |  | ||||||
|                 SecurityObjects::NoteInfo   ii{.created=(uint64_t)OpenWifi::Now(), .createdBy=UserInfo_.userinfo.email, .note=i.note}; |  | ||||||
|                 Existing.notes.push_back(ii); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         if(RawObject->has("currentPassword")) { |  | ||||||
|             if(!AuthService()->ValidateSubPassword(RawObject->get("currentPassword").toString())) { |  | ||||||
|                 return BadRequest(RESTAPI::Errors::InvalidPassword); |  | ||||||
|             } |  | ||||||
|             if(!AuthService()->SetPassword(RawObject->get("currentPassword").toString(),Existing)) { |  | ||||||
|                 return BadRequest(RESTAPI::Errors::PasswordRejected); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         if(GetParameter("email_verification","false")=="true") { |  | ||||||
|             if(AuthService::VerifySubEmail(Existing)) |  | ||||||
|                 Logger_.information(fmt::format("Verification e-mail requested for {}",Existing.email)); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         if(RawObject->has("userTypeProprietaryInfo")) { |  | ||||||
|             if(NewUser.userTypeProprietaryInfo.mfa.enabled) { |  | ||||||
|                 if (!MFAMETHODS::Validate(NewUser.userTypeProprietaryInfo.mfa.method)) { |  | ||||||
|                     return BadRequest(RESTAPI::Errors::BadMFAMethod); |  | ||||||
|                 } |  | ||||||
|  |  | ||||||
|                 if( NewUser.userTypeProprietaryInfo.mfa.enabled && |  | ||||||
|                     NewUser.userTypeProprietaryInfo.mfa.method == MFAMETHODS::SMS && |  | ||||||
|                     !SMSSender()->Enabled()) { |  | ||||||
|                     return BadRequest(RESTAPI::Errors::SMSMFANotEnabled); |  | ||||||
|                 } |  | ||||||
|  |  | ||||||
|                 if( NewUser.userTypeProprietaryInfo.mfa.enabled && |  | ||||||
|                     NewUser.userTypeProprietaryInfo.mfa.method == MFAMETHODS::EMAIL && |  | ||||||
|                     !SMTPMailerService()->Enabled()) { |  | ||||||
|                     return BadRequest(RESTAPI::Errors::EMailMFANotEnabled); |  | ||||||
|                 } |  | ||||||
|  |  | ||||||
|                 Existing.userTypeProprietaryInfo.mfa.method = NewUser.userTypeProprietaryInfo.mfa.method; |  | ||||||
|                 Existing.userTypeProprietaryInfo.mfa.enabled = true; |  | ||||||
|  |  | ||||||
|                 if (NewUser.userTypeProprietaryInfo.mfa.method == MFAMETHODS::SMS) { |  | ||||||
|                     if(NewUser.userTypeProprietaryInfo.mobiles.empty()) { |  | ||||||
|                         return BadRequest(RESTAPI::Errors::NeedMobileNumber); |  | ||||||
|                     } |  | ||||||
|                     if (!SMSSender()->IsNumberValid(NewUser.userTypeProprietaryInfo.mobiles[0].number,UserInfo_.userinfo.email)) { |  | ||||||
|                         return BadRequest(RESTAPI::Errors::NeedMobileNumber); |  | ||||||
|                     } |  | ||||||
|                     Existing.userTypeProprietaryInfo.mobiles = NewUser.userTypeProprietaryInfo.mobiles; |  | ||||||
|                     Existing.userTypeProprietaryInfo.mobiles[0].verified = true; |  | ||||||
|                     Existing.userTypeProprietaryInfo.authenticatorSecret.clear(); |  | ||||||
|                 } else if (NewUser.userTypeProprietaryInfo.mfa.method == MFAMETHODS::AUTHENTICATOR) { |  | ||||||
|                     std::string Secret; |  | ||||||
|                     Existing.userTypeProprietaryInfo.mobiles.clear(); |  | ||||||
|                     if(Existing.userTypeProprietaryInfo.authenticatorSecret.empty() && TotpCache()->CompleteValidation(UserInfo_.userinfo,false,Secret)) { |  | ||||||
|                         Existing.userTypeProprietaryInfo.authenticatorSecret = Secret; |  | ||||||
|                     } else if (!Existing.userTypeProprietaryInfo.authenticatorSecret.empty()) { |  | ||||||
|                         // we allow someone to use their old secret |  | ||||||
|                     } else { |  | ||||||
|                         return BadRequest(RESTAPI::Errors::AuthenticatorVerificationIncomplete); |  | ||||||
|                     } |  | ||||||
|                 } else if (NewUser.userTypeProprietaryInfo.mfa.method == MFAMETHODS::EMAIL) { |  | ||||||
|                     Existing.userTypeProprietaryInfo.mobiles.clear(); |  | ||||||
|                     Existing.userTypeProprietaryInfo.authenticatorSecret.clear(); |  | ||||||
|                 } |  | ||||||
|             } else { |  | ||||||
|                 Existing.userTypeProprietaryInfo.authenticatorSecret.clear(); |  | ||||||
|                 Existing.userTypeProprietaryInfo.mobiles.clear(); |  | ||||||
|                 Existing.userTypeProprietaryInfo.mfa.enabled = false; |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         if(StorageService()->SubDB().UpdateUserInfo(UserInfo_.userinfo.email,Id,Existing)) { |  | ||||||
|             SecurityObjects::UserInfo   NewUserInfo; |  | ||||||
|             StorageService()->SubDB().GetUserById(Id,NewUserInfo); |  | ||||||
|             Poco::JSON::Object  ModifiedObject; |  | ||||||
|             Sanitize(UserInfo_, NewUserInfo); |  | ||||||
|             NewUserInfo.to_json(ModifiedObject); |  | ||||||
|             return ReturnObject(ModifiedObject); |  | ||||||
|         } |  | ||||||
|         BadRequest(RESTAPI::Errors::RecordNotUpdated); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,31 +0,0 @@ | |||||||
| // |  | ||||||
| // Created by stephane bourque on 2021-11-30. |  | ||||||
| // |  | ||||||
|  |  | ||||||
| #pragma once |  | ||||||
|  |  | ||||||
| #include "framework/MicroService.h" |  | ||||||
|  |  | ||||||
| namespace OpenWifi { |  | ||||||
|     class RESTAPI_subuser_handler : public RESTAPIHandler { |  | ||||||
|     public: |  | ||||||
|         RESTAPI_subuser_handler(const RESTAPIHandler::BindingMap &bindings, Poco::Logger &L, RESTAPI_GenericServer &Server, uint64_t TransactionId, bool Internal) |  | ||||||
|         : RESTAPIHandler(bindings, L, |  | ||||||
|                          std::vector<std::string> |  | ||||||
|                          {Poco::Net::HTTPRequest::HTTP_POST, |  | ||||||
|                           Poco::Net::HTTPRequest::HTTP_GET, |  | ||||||
|                           Poco::Net::HTTPRequest::HTTP_PUT, |  | ||||||
|                           Poco::Net::HTTPRequest::HTTP_DELETE, |  | ||||||
|                           Poco::Net::HTTPRequest::HTTP_OPTIONS}, |  | ||||||
|                           Server, |  | ||||||
|                           TransactionId, |  | ||||||
|                           Internal) {} |  | ||||||
|         static auto PathName() { return std::list<std::string>{"/api/v1/subuser/{id}"}; }; |  | ||||||
|         void DoGet() final; |  | ||||||
|         void DoPost() final; |  | ||||||
|         void DoDelete() final; |  | ||||||
|         void DoPut() final; |  | ||||||
|     private: |  | ||||||
|  |  | ||||||
|     }; |  | ||||||
| } |  | ||||||
| @@ -1,80 +0,0 @@ | |||||||
| // |  | ||||||
| // Created by stephane bourque on 2021-11-30. |  | ||||||
| // |  | ||||||
|  |  | ||||||
| #include "RESTAPI_subusers_handler.h" |  | ||||||
| #include "StorageService.h" |  | ||||||
| #include "framework/MicroService.h" |  | ||||||
| #include "RESTAPI/RESTAPI_db_helpers.h" |  | ||||||
|  |  | ||||||
| namespace OpenWifi { |  | ||||||
|  |  | ||||||
|     void RESTAPI_subusers_handler::DoGet() { |  | ||||||
|         bool IdOnly = GetBoolParameter("idOnly"); |  | ||||||
|         auto operatorId = GetParameter("operatorId"); |  | ||||||
|         auto nameSearch = GetParameter("nameSearch"); |  | ||||||
|         auto emailSearch = GetParameter("emailSearch"); |  | ||||||
|  |  | ||||||
|         std::string baseQuery; |  | ||||||
|         if(!nameSearch.empty() || !emailSearch.empty()) { |  | ||||||
|             if(!nameSearch.empty()) |  | ||||||
|                 baseQuery = fmt::format(" Lower(name) like('%{}%') ", Poco::toLower(nameSearch) ); |  | ||||||
|             if(!emailSearch.empty()) |  | ||||||
|                 baseQuery += baseQuery.empty() ? fmt::format(" Lower(email) like('%{}%') ", Poco::toLower(emailSearch)) |  | ||||||
|                 : fmt::format(" and Lower(email) like('%{}%') ", Poco::toLower(emailSearch)); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         if(QB_.CountOnly) { |  | ||||||
|             std::string whereClause; |  | ||||||
|             if(!operatorId.empty()) { |  | ||||||
|                 whereClause = baseQuery.empty() ? fmt::format(" owner='{}' ", operatorId) : |  | ||||||
|                               fmt::format(" owner='{}' and {} ", operatorId, baseQuery); |  | ||||||
|                 auto count = StorageService()->SubDB().Count(whereClause); |  | ||||||
|                 return ReturnCountOnly(count); |  | ||||||
|             } |  | ||||||
|             auto count = StorageService()->UserDB().Count(); |  | ||||||
|             return ReturnCountOnly(count); |  | ||||||
|         } else if(QB_.Select.empty()) { |  | ||||||
|             std::string whereClause; |  | ||||||
|             if(!operatorId.empty()) { |  | ||||||
|                 whereClause = baseQuery.empty() ? fmt::format(" owner='{}' ", operatorId) : |  | ||||||
|                               fmt::format(" owner='{}' and {} ", operatorId, baseQuery); |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             SecurityObjects::UserInfoList   Users; |  | ||||||
|             if (StorageService()->SubDB().GetUsers(QB_.Offset, QB_.Limit, Users.users, whereClause)) { |  | ||||||
|                 for (auto &i : Users.users) { |  | ||||||
|                     Sanitize(UserInfo_, i); |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             if(IdOnly) { |  | ||||||
|                 Poco::JSON::Array   Arr; |  | ||||||
|                 Poco::JSON::Object  Answer; |  | ||||||
|  |  | ||||||
|                 for(const auto &i:Users.users) { |  | ||||||
|                     Arr.add(i.id); |  | ||||||
|                 } |  | ||||||
|                 Answer.set("users",Arr); |  | ||||||
|                 return ReturnObject(Answer); |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             Poco::JSON::Object  Answer; |  | ||||||
|             Users.to_json(Answer); |  | ||||||
|             return ReturnObject(Answer); |  | ||||||
|         } else { |  | ||||||
|             SecurityObjects::UserInfoList   Users; |  | ||||||
|             for(auto &i:SelectedRecords()) { |  | ||||||
|                 SecurityObjects::UserInfo   UInfo; |  | ||||||
|                 if(StorageService()->SubDB().GetUserById(i,UInfo)) { |  | ||||||
|                     Poco::JSON::Object Obj; |  | ||||||
|                     Sanitize(UserInfo_, UInfo); |  | ||||||
|                     Users.users.emplace_back(UInfo); |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|             Poco::JSON::Object Answer; |  | ||||||
|             Users.to_json(Answer); |  | ||||||
|             return ReturnObject(Answer); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,26 +0,0 @@ | |||||||
| // |  | ||||||
| // Created by stephane bourque on 2021-11-30. |  | ||||||
| // |  | ||||||
|  |  | ||||||
| #pragma once |  | ||||||
|  |  | ||||||
| #include "framework/MicroService.h" |  | ||||||
|  |  | ||||||
| namespace OpenWifi { |  | ||||||
|     class RESTAPI_subusers_handler : public RESTAPIHandler { |  | ||||||
|     public: |  | ||||||
|         RESTAPI_subusers_handler(const RESTAPIHandler::BindingMap &bindings, Poco::Logger &L, RESTAPI_GenericServer &Server, uint64_t TransactionId, bool Internal) |  | ||||||
|         : RESTAPIHandler(bindings, L, |  | ||||||
|                          std::vector<std::string> |  | ||||||
|                          {Poco::Net::HTTPRequest::HTTP_GET, |  | ||||||
|                           Poco::Net::HTTPRequest::HTTP_OPTIONS}, |  | ||||||
|                           Server, |  | ||||||
|                           TransactionId, |  | ||||||
|                           Internal) {} |  | ||||||
|         static auto PathName() { return std::list<std::string>{"/api/v1/subusers"}; }; |  | ||||||
|         void DoGet() final; |  | ||||||
|         void DoPost() final {}; |  | ||||||
|         void DoDelete() final {}; |  | ||||||
|         void DoPut() final {}; |  | ||||||
|     }; |  | ||||||
| }; |  | ||||||
| @@ -1,36 +0,0 @@ | |||||||
| // |  | ||||||
| // Created by stephane bourque on 2022-01-31. |  | ||||||
| // |  | ||||||
|  |  | ||||||
| #include "RESTAPI_totp_handler.h" |  | ||||||
| #include "TotpCache.h" |  | ||||||
|  |  | ||||||
| namespace OpenWifi { |  | ||||||
|  |  | ||||||
|     void RESTAPI_totp_handler::DoGet() { |  | ||||||
|  |  | ||||||
|         auto Reset = GetBoolParameter("reset",false); |  | ||||||
|         std::string QRCode; |  | ||||||
|  |  | ||||||
|         if(TotpCache()->StartValidation(UserInfo_.userinfo,false,QRCode,Reset)) { |  | ||||||
|             return SendFileContent(QRCode, "image/svg+xml","qrcode.svg"); |  | ||||||
|         } |  | ||||||
|         return BadRequest(RESTAPI::Errors::InvalidCommand); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     void RESTAPI_totp_handler::DoPut() { |  | ||||||
|         auto Value = GetParameter("value",""); |  | ||||||
|         auto nextIndex = GetParameter("index",0); |  | ||||||
|         bool moreCodes=false; |  | ||||||
|  |  | ||||||
|         RESTAPI::Errors::msg Err; |  | ||||||
|         if(TotpCache()->ContinueValidation(UserInfo_.userinfo,false,Value,nextIndex,moreCodes, Err)) { |  | ||||||
|             Poco::JSON::Object Answer; |  | ||||||
|             Answer.set("nextIndex", nextIndex); |  | ||||||
|             Answer.set("moreCodes", moreCodes); |  | ||||||
|             return ReturnObject(Answer); |  | ||||||
|         } |  | ||||||
|         return BadRequest(Err); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
| } |  | ||||||
| @@ -1,31 +0,0 @@ | |||||||
| // |  | ||||||
| // Created by stephane bourque on 2022-01-31. |  | ||||||
| // |  | ||||||
|  |  | ||||||
| #pragma once |  | ||||||
|  |  | ||||||
| #include "framework/MicroService.h" |  | ||||||
|  |  | ||||||
| namespace OpenWifi { |  | ||||||
|     class RESTAPI_totp_handler : public RESTAPIHandler { |  | ||||||
|     public: |  | ||||||
|         RESTAPI_totp_handler(const RESTAPIHandler::BindingMap &bindings, Poco::Logger &L, RESTAPI_GenericServer &Server, uint64_t TransactionId, bool Internal) |  | ||||||
|                 : RESTAPIHandler(bindings, L, |  | ||||||
|                                  std::vector<std::string> |  | ||||||
|                                          { |  | ||||||
|                                               Poco::Net::HTTPRequest::HTTP_GET, |  | ||||||
|                                               Poco::Net::HTTPRequest::HTTP_PUT, |  | ||||||
|                                               Poco::Net::HTTPRequest::HTTP_OPTIONS |  | ||||||
|                                           }, |  | ||||||
|                                  Server, |  | ||||||
|                                  TransactionId, |  | ||||||
|                                  Internal) {} |  | ||||||
|         static auto PathName() { return std::list<std::string>{"/api/v1/totp"}; }; |  | ||||||
|         void DoGet() final; |  | ||||||
|         void DoPost() final {}; |  | ||||||
|         void DoDelete() final {}; |  | ||||||
|         void DoPut() final; |  | ||||||
|     private: |  | ||||||
|  |  | ||||||
|     }; |  | ||||||
| } |  | ||||||
| @@ -1,327 +0,0 @@ | |||||||
| // |  | ||||||
| // Created by stephane bourque on 2021-06-21. |  | ||||||
| // |  | ||||||
|  |  | ||||||
| #include "RESTAPI_user_handler.h" |  | ||||||
| #include "StorageService.h" |  | ||||||
| #include "framework/ow_constants.h" |  | ||||||
| #include "SMSSender.h" |  | ||||||
| #include "SMTPMailerService.h" |  | ||||||
| #include "ACLProcessor.h" |  | ||||||
| #include "AuthService.h" |  | ||||||
| #include "RESTAPI/RESTAPI_db_helpers.h" |  | ||||||
| #include "MFAServer.h" |  | ||||||
| #include "TotpCache.h" |  | ||||||
|  |  | ||||||
| namespace OpenWifi { |  | ||||||
|  |  | ||||||
|     void RESTAPI_user_handler::DoGet() { |  | ||||||
|         std::string Id = GetBinding("id", ""); |  | ||||||
|         if(Id.empty()) { |  | ||||||
|             return BadRequest(RESTAPI::Errors::MissingUserID); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         Poco::toLowerInPlace(Id); |  | ||||||
|         std::string Arg; |  | ||||||
|         SecurityObjects::UserInfo   UInfo; |  | ||||||
|         if(HasParameter("byEmail",Arg) && Arg=="true") { |  | ||||||
|             if(!StorageService()->UserDB().GetUserByEmail(Id,UInfo)) { |  | ||||||
|                 return NotFound(); |  | ||||||
|             } |  | ||||||
|         } else if(!StorageService()->UserDB().GetUserById(Id,UInfo)) { |  | ||||||
|             return NotFound(); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         if(!ACLProcessor::Can(UserInfo_.userinfo, UInfo,ACLProcessor::READ)) { |  | ||||||
|             return UnAuthorized(RESTAPI::Errors::ACCESS_DENIED); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         Poco::JSON::Object  UserInfoObject; |  | ||||||
|         Sanitize(UserInfo_, UInfo); |  | ||||||
|         UInfo.to_json(UserInfoObject); |  | ||||||
|         ReturnObject(UserInfoObject); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     void RESTAPI_user_handler::DoDelete() { |  | ||||||
|         std::string Id = GetBinding("id", ""); |  | ||||||
|         if(Id.empty()) { |  | ||||||
|             return BadRequest(RESTAPI::Errors::MissingUserID); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         SecurityObjects::UserInfo UInfo; |  | ||||||
|         if(!StorageService()->UserDB().GetUserById(Id,UInfo)) { |  | ||||||
|             return NotFound(); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         if(!ACLProcessor::Can(UserInfo_.userinfo, UInfo,ACLProcessor::DELETE)) { |  | ||||||
|             return UnAuthorized(RESTAPI::Errors::ACCESS_DENIED); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         if(!StorageService()->UserDB().DeleteUser(UserInfo_.userinfo.email,Id)) { |  | ||||||
|             return NotFound(); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         AuthService()->DeleteUserFromCache(Id); |  | ||||||
|         StorageService()->AvatarDB().DeleteAvatar(UserInfo_.userinfo.email,Id); |  | ||||||
|         StorageService()->PreferencesDB().DeletePreferences(UserInfo_.userinfo.email,Id); |  | ||||||
|         StorageService()->UserTokenDB().RevokeAllTokens(Id); |  | ||||||
|         Logger_.information(fmt::format("User '{}' deleted by '{}'.",Id,UserInfo_.userinfo.email)); |  | ||||||
|         OK(); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     void RESTAPI_user_handler::DoPost() { |  | ||||||
|  |  | ||||||
|         std::string Id = GetBinding("id", ""); |  | ||||||
|         if(Id!="0") { |  | ||||||
|             return BadRequest(RESTAPI::Errors::IdMustBe0); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         SecurityObjects::UserInfo   NewUser; |  | ||||||
|         const auto & RawObject = ParsedBody_; |  | ||||||
|         if(!NewUser.from_json(RawObject)) { |  | ||||||
|             return BadRequest(RESTAPI::Errors::InvalidJSONDocument); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         if(NewUser.userRole == SecurityObjects::UNKNOWN) { |  | ||||||
|             return BadRequest(RESTAPI::Errors::InvalidUserRole); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         if(UserInfo_.userinfo.userRole==SecurityObjects::ROOT) { |  | ||||||
|             NewUser.owner = GetParameter("entity",""); |  | ||||||
|         } else { |  | ||||||
|             NewUser.owner = UserInfo_.userinfo.owner; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         if(!ACLProcessor::Can(UserInfo_.userinfo,NewUser,ACLProcessor::CREATE)) { |  | ||||||
|             return UnAuthorized(RESTAPI::Errors::ACCESS_DENIED); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         Poco::toLowerInPlace(NewUser.email); |  | ||||||
|         if(!Utils::ValidEMailAddress(NewUser.email)) { |  | ||||||
|             return BadRequest(RESTAPI::Errors::InvalidEmailAddress); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         SecurityObjects::UserInfo   Existing; |  | ||||||
|         if(StorageService()->SubDB().GetUserByEmail(NewUser.email,Existing)) { |  | ||||||
|             return BadRequest(RESTAPI::Errors::UserAlreadyExists); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         if(!NewUser.currentPassword.empty()) { |  | ||||||
|             if(!AuthService()->ValidatePassword(NewUser.currentPassword)) { |  | ||||||
|                 return BadRequest(RESTAPI::Errors::InvalidPassword); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         if(NewUser.name.empty()) |  | ||||||
|             NewUser.name = NewUser.email; |  | ||||||
|  |  | ||||||
|         //  You cannot enable MFA during user creation |  | ||||||
|         NewUser.userTypeProprietaryInfo.mfa.enabled = false; |  | ||||||
|         NewUser.userTypeProprietaryInfo.mfa.method = ""; |  | ||||||
|         NewUser.userTypeProprietaryInfo.mobiles.clear(); |  | ||||||
|         NewUser.userTypeProprietaryInfo.authenticatorSecret.clear(); |  | ||||||
|         NewUser.validated = true; |  | ||||||
|  |  | ||||||
|         if(!StorageService()->UserDB().CreateUser(NewUser.email,NewUser)) { |  | ||||||
|             Logger_.information(fmt::format("Could not add user '{}'.",NewUser.email)); |  | ||||||
|             return BadRequest(RESTAPI::Errors::RecordNotCreated); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         if(GetBoolParameter("email_verification")) { |  | ||||||
|             if(AuthService::VerifyEmail(NewUser)) |  | ||||||
|                 Logger_.information(fmt::format("Verification e-mail requested for {}",NewUser.email)); |  | ||||||
|             StorageService()->UserDB().UpdateUserInfo(UserInfo_.userinfo.email,NewUser.id,NewUser); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         if(!StorageService()->UserDB().GetUserByEmail(NewUser.email, NewUser)) { |  | ||||||
|             Logger_.information(fmt::format("User '{}' but not retrieved.",NewUser.email)); |  | ||||||
|             return NotFound(); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         Poco::JSON::Object  UserInfoObject; |  | ||||||
|         Sanitize(UserInfo_, NewUser); |  | ||||||
|         NewUser.to_json(UserInfoObject); |  | ||||||
|         ReturnObject(UserInfoObject); |  | ||||||
|         Logger_.information(fmt::format("User '{}' has been added by '{}')",NewUser.email, UserInfo_.userinfo.email)); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     void RESTAPI_user_handler::DoPut() { |  | ||||||
|  |  | ||||||
|         std::string Id = GetBinding("id", ""); |  | ||||||
|         if(Id.empty()) { |  | ||||||
|             return BadRequest(RESTAPI::Errors::MissingUserID); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         SecurityObjects::UserInfo   Existing; |  | ||||||
|         if(!StorageService()->UserDB().GetUserById(Id,Existing)) { |  | ||||||
|             return NotFound(); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         if(!ACLProcessor::Can(UserInfo_.userinfo,Existing,ACLProcessor::MODIFY)) { |  | ||||||
|             return UnAuthorized(RESTAPI::Errors::ACCESS_DENIED); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         if(GetBoolParameter("resetMFA")) { |  | ||||||
|             if( (UserInfo_.userinfo.userRole == SecurityObjects::ROOT) || |  | ||||||
|                 (UserInfo_.userinfo.userRole == SecurityObjects::ADMIN && Existing.userRole!=SecurityObjects::ROOT) || |  | ||||||
|                 (UserInfo_.userinfo.id == Id)) { |  | ||||||
|                 Existing.userTypeProprietaryInfo.mfa.enabled = false; |  | ||||||
|                 Existing.userTypeProprietaryInfo.mfa.method.clear(); |  | ||||||
|                 Existing.userTypeProprietaryInfo.mobiles.clear(); |  | ||||||
|                 Existing.modified = OpenWifi::Now(); |  | ||||||
|                 Existing.notes.push_back( SecurityObjects::NoteInfo{ |  | ||||||
|                             .created=OpenWifi::Now(), |  | ||||||
|                             .createdBy=UserInfo_.userinfo.email, |  | ||||||
|                             .note="MFA Reset by " + UserInfo_.userinfo.email}); |  | ||||||
|                 StorageService()->UserDB().UpdateUserInfo(UserInfo_.userinfo.email,Id,Existing); |  | ||||||
|                 SecurityObjects::UserInfo   NewUserInfo; |  | ||||||
|                 StorageService()->UserDB().GetUserByEmail(UserInfo_.userinfo.email,NewUserInfo); |  | ||||||
|                 Poco::JSON::Object  ModifiedObject; |  | ||||||
|                 Sanitize(UserInfo_, NewUserInfo); |  | ||||||
|                 NewUserInfo.to_json(ModifiedObject); |  | ||||||
|                 return ReturnObject(ModifiedObject); |  | ||||||
|             } else { |  | ||||||
|                 return UnAuthorized(RESTAPI::Errors::ACCESS_DENIED); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         if(GetBoolParameter("forgotPassword")) { |  | ||||||
|             Existing.changePassword = true; |  | ||||||
|             Logger_.information(fmt::format("FORGOTTEN-PASSWORD({}): Request for {}", Request->clientAddress().toString(), Existing.email)); |  | ||||||
|             SecurityObjects::ActionLink NewLink; |  | ||||||
|  |  | ||||||
|             NewLink.action = OpenWifi::SecurityObjects::LinkActions::FORGOT_PASSWORD; |  | ||||||
|             NewLink.id = MicroService::CreateUUID(); |  | ||||||
|             NewLink.userId = Existing.id; |  | ||||||
|             NewLink.created = OpenWifi::Now(); |  | ||||||
|             NewLink.expires = NewLink.created + (24*60*60); |  | ||||||
|             NewLink.userAction = true; |  | ||||||
|             StorageService()->ActionLinksDB().CreateAction(NewLink); |  | ||||||
|             return OK(); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         SecurityObjects::UserInfo   NewUser; |  | ||||||
|         const auto & RawObject = ParsedBody_; |  | ||||||
|         if(!NewUser.from_json(RawObject)) { |  | ||||||
|             return BadRequest(RESTAPI::Errors::InvalidJSONDocument); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         // some basic validations |  | ||||||
|         if(RawObject->has("userRole") && SecurityObjects::UserTypeFromString(RawObject->get("userRole").toString())==SecurityObjects::UNKNOWN) { |  | ||||||
|             return BadRequest(RESTAPI::Errors::InvalidUserRole); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         if(RawObject->has("owner")) { |  | ||||||
|             if (UserInfo_.userinfo.userRole == SecurityObjects::ROOT && Existing.owner.empty()) { |  | ||||||
|                 AssignIfPresent(RawObject, "owner", Existing.owner); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         // The only valid things to change are: changePassword, name, |  | ||||||
|         AssignIfPresent(RawObject,"name", Existing.name); |  | ||||||
|         AssignIfPresent(RawObject,"description", Existing.description); |  | ||||||
|         AssignIfPresent(RawObject,"location", Existing.location); |  | ||||||
|         AssignIfPresent(RawObject,"locale", Existing.locale); |  | ||||||
|         AssignIfPresent(RawObject,"changePassword", Existing.changePassword); |  | ||||||
|         AssignIfPresent(RawObject,"suspended", Existing.suspended); |  | ||||||
|         AssignIfPresent(RawObject,"blackListed", Existing.blackListed); |  | ||||||
|  |  | ||||||
|         if(RawObject->has("userRole")) { |  | ||||||
|             auto NewRole = SecurityObjects::UserTypeFromString(RawObject->get("userRole").toString()); |  | ||||||
|             if(NewRole!=Existing.userRole) { |  | ||||||
|                 if(UserInfo_.userinfo.userRole!=SecurityObjects::ROOT && NewRole==SecurityObjects::ROOT) { |  | ||||||
|                     return UnAuthorized(RESTAPI::Errors::ACCESS_DENIED); |  | ||||||
|                 } |  | ||||||
|                 if(Id==UserInfo_.userinfo.id) { |  | ||||||
|                     return UnAuthorized(RESTAPI::Errors::ACCESS_DENIED); |  | ||||||
|                 } |  | ||||||
|                 Existing.userRole = NewRole; |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         if(RawObject->has("notes")) { |  | ||||||
|             SecurityObjects::NoteInfoVec NIV; |  | ||||||
|             NIV = RESTAPI_utils::to_object_array<SecurityObjects::NoteInfo>(RawObject->get("notes").toString()); |  | ||||||
|             for(auto const &i:NIV) { |  | ||||||
|                 SecurityObjects::NoteInfo   ii{.created=(uint64_t)OpenWifi::Now(), .createdBy=UserInfo_.userinfo.email, .note=i.note}; |  | ||||||
|                 Existing.notes.push_back(ii); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         if(RawObject->has("currentPassword")) { |  | ||||||
|             if(!AuthService()->ValidatePassword(RawObject->get("currentPassword").toString())) { |  | ||||||
|                 return BadRequest(RESTAPI::Errors::InvalidPassword); |  | ||||||
|             } |  | ||||||
|             if(!AuthService()->SetPassword(RawObject->get("currentPassword").toString(),Existing)) { |  | ||||||
|                 return BadRequest(RESTAPI::Errors::PasswordRejected); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         if(GetBoolParameter("email_verification")) { |  | ||||||
|             if(AuthService::VerifyEmail(Existing)) |  | ||||||
|                 Logger_.information(fmt::format("Verification e-mail requested for {}",Existing.email)); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         if(RawObject->has("userTypeProprietaryInfo")) { |  | ||||||
|             if(NewUser.userTypeProprietaryInfo.mfa.enabled) { |  | ||||||
|                 if (!MFAMETHODS::Validate(NewUser.userTypeProprietaryInfo.mfa.method)) { |  | ||||||
|                     return BadRequest(RESTAPI::Errors::BadMFAMethod); |  | ||||||
|                 } |  | ||||||
|  |  | ||||||
|                 if( NewUser.userTypeProprietaryInfo.mfa.enabled && |  | ||||||
|                     NewUser.userTypeProprietaryInfo.mfa.method == MFAMETHODS::SMS && |  | ||||||
|                     !SMSSender()->Enabled()) { |  | ||||||
|                     return BadRequest(RESTAPI::Errors::SMSMFANotEnabled); |  | ||||||
|                 } |  | ||||||
|  |  | ||||||
|                 if( NewUser.userTypeProprietaryInfo.mfa.enabled && |  | ||||||
|                     NewUser.userTypeProprietaryInfo.mfa.method == MFAMETHODS::EMAIL && |  | ||||||
|                     !SMTPMailerService()->Enabled()) { |  | ||||||
|                     return BadRequest(RESTAPI::Errors::EMailMFANotEnabled); |  | ||||||
|                 } |  | ||||||
|  |  | ||||||
|                 Existing.userTypeProprietaryInfo.mfa.method = NewUser.userTypeProprietaryInfo.mfa.method; |  | ||||||
|                 Existing.userTypeProprietaryInfo.mfa.enabled = true; |  | ||||||
|  |  | ||||||
|                 if (NewUser.userTypeProprietaryInfo.mfa.method == MFAMETHODS::SMS) { |  | ||||||
|                     if(NewUser.userTypeProprietaryInfo.mobiles.empty()) { |  | ||||||
|                         return BadRequest(RESTAPI::Errors::NeedMobileNumber); |  | ||||||
|                     } |  | ||||||
|                     if (!SMSSender()->IsNumberValid(NewUser.userTypeProprietaryInfo.mobiles[0].number,UserInfo_.userinfo.email)) { |  | ||||||
|                         return BadRequest(RESTAPI::Errors::NeedMobileNumber); |  | ||||||
|                     } |  | ||||||
|                     Existing.userTypeProprietaryInfo.mobiles = NewUser.userTypeProprietaryInfo.mobiles; |  | ||||||
|                     Existing.userTypeProprietaryInfo.mobiles[0].verified = true; |  | ||||||
|                     Existing.userTypeProprietaryInfo.authenticatorSecret.clear(); |  | ||||||
|                 } else if (NewUser.userTypeProprietaryInfo.mfa.method == MFAMETHODS::AUTHENTICATOR) { |  | ||||||
|                     std::string Secret; |  | ||||||
|                     Existing.userTypeProprietaryInfo.mobiles.clear(); |  | ||||||
|                     if(Existing.userTypeProprietaryInfo.authenticatorSecret.empty() && TotpCache()->CompleteValidation(UserInfo_.userinfo,false,Secret)) { |  | ||||||
|                         Existing.userTypeProprietaryInfo.authenticatorSecret = Secret; |  | ||||||
|                     } else if (!Existing.userTypeProprietaryInfo.authenticatorSecret.empty()) { |  | ||||||
|                         // we allow someone to use their old secret |  | ||||||
|                     } else { |  | ||||||
|                         return BadRequest(RESTAPI::Errors::AuthenticatorVerificationIncomplete); |  | ||||||
|                     } |  | ||||||
|                 } else if (NewUser.userTypeProprietaryInfo.mfa.method == MFAMETHODS::EMAIL) { |  | ||||||
|                     Existing.userTypeProprietaryInfo.mobiles.clear(); |  | ||||||
|                     Existing.userTypeProprietaryInfo.authenticatorSecret.clear(); |  | ||||||
|                 } |  | ||||||
|             } else { |  | ||||||
|                 Existing.userTypeProprietaryInfo.authenticatorSecret.clear(); |  | ||||||
|                 Existing.userTypeProprietaryInfo.mobiles.clear(); |  | ||||||
|                 Existing.userTypeProprietaryInfo.mfa.enabled = false; |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         Existing.modified = OpenWifi::Now(); |  | ||||||
|         if(StorageService()->UserDB().UpdateUserInfo(UserInfo_.userinfo.email,Id,Existing)) { |  | ||||||
|             SecurityObjects::UserInfo   NewUserInfo; |  | ||||||
|             StorageService()->UserDB().GetUserByEmail(UserInfo_.userinfo.email,NewUserInfo); |  | ||||||
|             Poco::JSON::Object  ModifiedObject; |  | ||||||
|             Sanitize(UserInfo_, NewUserInfo); |  | ||||||
|             NewUserInfo.to_json(ModifiedObject); |  | ||||||
|             return ReturnObject(ModifiedObject); |  | ||||||
|         } |  | ||||||
|         BadRequest(RESTAPI::Errors::RecordNotUpdated); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,58 +0,0 @@ | |||||||
| // |  | ||||||
| // Created by stephane bourque on 2021-06-21. |  | ||||||
| // |  | ||||||
|  |  | ||||||
| #include "RESTAPI_users_handler.h" |  | ||||||
| #include "StorageService.h" |  | ||||||
| #include "framework/MicroService.h" |  | ||||||
| #include "RESTAPI/RESTAPI_db_helpers.h" |  | ||||||
|  |  | ||||||
| namespace OpenWifi { |  | ||||||
|     void RESTAPI_users_handler::DoGet() { |  | ||||||
|         bool IdOnly = (GetParameter("idOnly","false")=="true"); |  | ||||||
|         auto nameSearch = GetParameter("nameSearch"); |  | ||||||
|         auto emailSearch = GetParameter("emailSearch"); |  | ||||||
|  |  | ||||||
|         std::string baseQuery; |  | ||||||
|         if(!nameSearch.empty() || !emailSearch.empty()) { |  | ||||||
|             if(!nameSearch.empty()) |  | ||||||
|                 baseQuery = fmt::format(" Lower(name) like('%{}%') ", Poco::toLower(nameSearch) ); |  | ||||||
|             if(!emailSearch.empty()) |  | ||||||
|                 baseQuery += baseQuery.empty() ? fmt::format(" Lower(email) like('%{}%') ", Poco::toLower(emailSearch)) |  | ||||||
|                                                : fmt::format(" and Lower(email) like('%{}%') ", Poco::toLower(emailSearch)); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         if(QB_.Select.empty()) { |  | ||||||
|             SecurityObjects::UserInfoList   Users; |  | ||||||
|             if(StorageService()->UserDB().GetUsers(QB_.Offset, QB_.Limit, Users.users, baseQuery)) { |  | ||||||
|                 for (auto &i : Users.users) { |  | ||||||
|                     Sanitize(UserInfo_, i); |  | ||||||
|                 } |  | ||||||
|                 if(IdOnly) { |  | ||||||
|                     Poco::JSON::Array   Arr; |  | ||||||
|                     for(const auto &i:Users.users) |  | ||||||
|                         Arr.add(i.id); |  | ||||||
|                     Poco::JSON::Object  Answer; |  | ||||||
|                     Answer.set("users", Arr); |  | ||||||
|                     return ReturnObject(Answer); |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|             Poco::JSON::Object  Answer; |  | ||||||
|             Users.to_json(Answer); |  | ||||||
|             return ReturnObject(Answer); |  | ||||||
|         } else { |  | ||||||
|             SecurityObjects::UserInfoList   Users; |  | ||||||
|             for(auto &i:SelectedRecords()) { |  | ||||||
|                 SecurityObjects::UserInfo   UInfo; |  | ||||||
|                 if(StorageService()->UserDB().GetUserById(i,UInfo)) { |  | ||||||
|                     Poco::JSON::Object Obj; |  | ||||||
|                     Sanitize(UserInfo_, UInfo); |  | ||||||
|                     Users.users.emplace_back(UInfo); |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|             Poco::JSON::Object Answer; |  | ||||||
|             Users.to_json(Answer); |  | ||||||
|             return ReturnObject(Answer); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,26 +0,0 @@ | |||||||
| // |  | ||||||
| // Created by stephane bourque on 2021-11-30. |  | ||||||
| // |  | ||||||
|  |  | ||||||
| #include "RESTAPI_validate_sub_token_handler.h" |  | ||||||
| #include "AuthService.h" |  | ||||||
|  |  | ||||||
| namespace OpenWifi { |  | ||||||
|     void RESTAPI_validate_sub_token_handler::DoGet() { |  | ||||||
|         Poco::URI URI(Request->getURI()); |  | ||||||
|         auto Parameters = URI.getQueryParameters(); |  | ||||||
|         for(auto const &i:Parameters) { |  | ||||||
|             if (i.first == "token") { |  | ||||||
|                 //  can we find this token? |  | ||||||
|                 SecurityObjects::UserInfoAndPolicy SecObj; |  | ||||||
|                 bool Expired = false; |  | ||||||
|                 if (AuthService()->IsValidSubToken(i.second, SecObj.webtoken, SecObj.userinfo, Expired)) { |  | ||||||
|                     Poco::JSON::Object Obj; |  | ||||||
|                     SecObj.to_json(Obj); |  | ||||||
|                     return ReturnObject(Obj); |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         return NotFound(); |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| @@ -1,26 +0,0 @@ | |||||||
| // |  | ||||||
| // Created by stephane bourque on 2021-11-30. |  | ||||||
| // |  | ||||||
|  |  | ||||||
| #pragma once |  | ||||||
|  |  | ||||||
| #include "framework/MicroService.h" |  | ||||||
|  |  | ||||||
| namespace OpenWifi { |  | ||||||
|     class RESTAPI_validate_sub_token_handler : public RESTAPIHandler { |  | ||||||
|     public: |  | ||||||
|         RESTAPI_validate_sub_token_handler(const RESTAPIHandler::BindingMap &bindings, Poco::Logger &L, RESTAPI_GenericServer &Server, uint64_t TransactionId, bool Internal) |  | ||||||
|         : RESTAPIHandler(bindings, L, |  | ||||||
|                          std::vector<std::string> |  | ||||||
|                          {Poco::Net::HTTPRequest::HTTP_GET, |  | ||||||
|                           Poco::Net::HTTPRequest::HTTP_OPTIONS}, |  | ||||||
|                           Server, |  | ||||||
|                           TransactionId, |  | ||||||
|                           Internal) {}; |  | ||||||
|         static auto PathName() { return std::list<std::string>{"/api/v1/validateSubToken"}; }; |  | ||||||
|         void DoGet() final; |  | ||||||
|         void DoPost() final {}; |  | ||||||
|         void DoDelete() final {}; |  | ||||||
|         void DoPut() final {}; |  | ||||||
|     }; |  | ||||||
| } |  | ||||||
| @@ -2,23 +2,26 @@ | |||||||
| // Created by stephane bourque on 2021-07-10.
 | // Created by stephane bourque on 2021-07-10.
 | ||||||
| //
 | //
 | ||||||
| 
 | 
 | ||||||
| #include "RESTAPI_asset_server.h" | #include "RESTAPI_AssetServer.h" | ||||||
| #include "Poco/File.h" | #include "Poco/File.h" | ||||||
| #include "framework/ow_constants.h" |  | ||||||
| #include "Daemon.h" | #include "Daemon.h" | ||||||
|  | #include "RESTAPI_server.h" | ||||||
|  | #include "Utils.h" | ||||||
|  | #include "RESTAPI_protocol.h" | ||||||
| 
 | 
 | ||||||
| namespace OpenWifi { | namespace OpenWifi { | ||||||
|     void RESTAPI_asset_server::DoGet() { |     void RESTAPI_AssetServer::DoGet() { | ||||||
|         Poco::File  AssetFile; |         Poco::File  AssetFile; | ||||||
| 
 | 
 | ||||||
|         if(Request->getURI().find("/favicon.ico") != std::string::npos) { |         if(Request->getURI().find("/favicon.ico") != std::string::npos) { | ||||||
|             AssetFile = Daemon()->AssetDir() + "/favicon.ico"; |             AssetFile = RESTAPI_Server()->AssetDir() + "/favicon.ico"; | ||||||
|         } else { |         } else { | ||||||
|             std::string AssetName = GetBinding(RESTAPI::Protocol::ID, ""); |             std::string AssetName = GetBinding(RESTAPI::Protocol::ID, ""); | ||||||
|             AssetFile = Daemon()->AssetDir() + "/" + AssetName; |             AssetFile = RESTAPI_Server()->AssetDir() + "/" + AssetName; | ||||||
|         } |         } | ||||||
|         if(!AssetFile.isFile()) { |         if(!AssetFile.isFile()) { | ||||||
|             return NotFound(); |             NotFound(); | ||||||
|  |             return; | ||||||
|         } |         } | ||||||
|         SendFile(AssetFile); |         SendFile(AssetFile); | ||||||
|     } |     } | ||||||
| @@ -2,14 +2,15 @@ | |||||||
| // Created by stephane bourque on 2021-07-10.
 | // Created by stephane bourque on 2021-07-10.
 | ||||||
| //
 | //
 | ||||||
| 
 | 
 | ||||||
| #pragma once | #ifndef UCENTRALSEC_RESTAPI_ASSETSERVER_H | ||||||
|  | #define UCENTRALSEC_RESTAPI_ASSETSERVER_H | ||||||
| 
 | 
 | ||||||
| #include "../framework/MicroService.h" | #include "RESTAPI_handler.h" | ||||||
| 
 | 
 | ||||||
| namespace OpenWifi { | namespace OpenWifi { | ||||||
|     class RESTAPI_asset_server : public RESTAPIHandler { |     class RESTAPI_AssetServer : public RESTAPIHandler { | ||||||
|     public: |     public: | ||||||
|         RESTAPI_asset_server(const RESTAPIHandler::BindingMap &bindings, Poco::Logger &L, RESTAPI_GenericServer &Server, uint64_t TransactionId, bool Internal) |         RESTAPI_AssetServer(const RESTAPIHandler::BindingMap &bindings, Poco::Logger &L, RESTAPI_GenericServer &Server, bool Internal) | ||||||
|                 : RESTAPIHandler(bindings, L, |                 : RESTAPIHandler(bindings, L, | ||||||
|                                  std::vector<std::string> |                                  std::vector<std::string> | ||||||
|                                          {Poco::Net::HTTPRequest::HTTP_POST, |                                          {Poco::Net::HTTPRequest::HTTP_POST, | ||||||
| @@ -18,9 +19,8 @@ namespace OpenWifi { | |||||||
|                                           Poco::Net::HTTPRequest::HTTP_DELETE, |                                           Poco::Net::HTTPRequest::HTTP_DELETE, | ||||||
|                                           Poco::Net::HTTPRequest::HTTP_OPTIONS}, |                                           Poco::Net::HTTPRequest::HTTP_OPTIONS}, | ||||||
|                                           Server, |                                           Server, | ||||||
|                                           TransactionId, |                                           Internal) {} | ||||||
|                                           Internal, false) {} |         static const std::list<const char *> PathName() { return std::list<const char *>{"/wwwassets/{id}" , | ||||||
|         static auto PathName() { return std::list<std::string>{"/wwwassets/{id}" , |  | ||||||
|                                                                                          "/favicon.ico"}; }; |                                                                                          "/favicon.ico"}; }; | ||||||
|         void DoGet() final; |         void DoGet() final; | ||||||
|         void DoPost() final {}; |         void DoPost() final {}; | ||||||
| @@ -32,3 +32,5 @@ namespace OpenWifi { | |||||||
|     }; |     }; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
|  | #endif //UCENTRALSEC_RESTAPI_ASSETSERVER_H
 | ||||||
							
								
								
									
										5
									
								
								src/RESTAPI_GenericServer.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								src/RESTAPI_GenericServer.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | |||||||
|  | // | ||||||
|  | // Created by stephane bourque on 2021-09-15. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #include "RESTAPI_GenericServer.h" | ||||||
							
								
								
									
										78
									
								
								src/RESTAPI_GenericServer.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								src/RESTAPI_GenericServer.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,78 @@ | |||||||
|  | // | ||||||
|  | // Created by stephane bourque on 2021-09-15. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef OWPROV_RESTAPI_GENERICSERVER_H | ||||||
|  | #define OWPROV_RESTAPI_GENERICSERVER_H | ||||||
|  |  | ||||||
|  | #include <vector> | ||||||
|  | #include <string> | ||||||
|  |  | ||||||
|  | #include "Daemon.h" | ||||||
|  | #include "Poco/StringTokenizer.h" | ||||||
|  | #include "Poco/Net/HTTPRequest.h" | ||||||
|  |  | ||||||
|  | namespace OpenWifi { | ||||||
|  |  | ||||||
|  |     class RESTAPI_GenericServer { | ||||||
|  |     public: | ||||||
|  |  | ||||||
|  |         enum { | ||||||
|  |             LOG_GET=0, | ||||||
|  |             LOG_DELETE, | ||||||
|  |             LOG_PUT, | ||||||
|  |             LOG_POST | ||||||
|  |         }; | ||||||
|  |  | ||||||
|  |         void inline SetFlags(bool External, const std::string &Methods) { | ||||||
|  |             Poco::StringTokenizer   Tokens(Methods,","); | ||||||
|  |             auto Offset = (External ? 0 : 4); | ||||||
|  |             for(const auto &i:Tokens) { | ||||||
|  |                 if(Poco::icompare(i,Poco::Net::HTTPRequest::HTTP_DELETE)==0) | ||||||
|  |                     LogFlags_[Offset+LOG_DELETE]=true; | ||||||
|  |                 else if(Poco::icompare(i,Poco::Net::HTTPRequest::HTTP_PUT)==0) | ||||||
|  |                     LogFlags_[Offset+LOG_PUT]=true; | ||||||
|  |                 else if(Poco::icompare(i,Poco::Net::HTTPRequest::HTTP_POST)==0) | ||||||
|  |                     LogFlags_[Offset+LOG_POST]=true; | ||||||
|  |                 else if(Poco::icompare(i,Poco::Net::HTTPRequest::HTTP_GET)==0) | ||||||
|  |                     LogFlags_[Offset+LOG_GET]=true; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         inline void InitLogging() { | ||||||
|  |             std::string Public = Daemon()->ConfigGetString("apilogging.public.methods","PUT,POST,DELETE"); | ||||||
|  |             SetFlags(true, Public); | ||||||
|  |             std::string Private = Daemon()->ConfigGetString("apilogging.private.methods","PUT,POST,DELETE"); | ||||||
|  |             SetFlags(false, Private); | ||||||
|  |  | ||||||
|  |             std::string PublicBadTokens = Daemon()->ConfigGetString("apilogging.public.badtokens.methods",""); | ||||||
|  |             LogBadTokens_[0] = (Poco::icompare(PublicBadTokens,"true")==0); | ||||||
|  |             std::string PrivateBadTokens = Daemon()->ConfigGetString("apilogging.private.badtokens.methods",""); | ||||||
|  |             LogBadTokens_[1] = (Poco::icompare(PrivateBadTokens,"true")==0); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         [[nodiscard]] inline bool LogIt(const std::string &Method, bool External) const { | ||||||
|  |             auto Offset = (External ? 0 : 4); | ||||||
|  |             if(Method == Poco::Net::HTTPRequest::HTTP_GET) | ||||||
|  |                 return LogFlags_[Offset+LOG_GET]; | ||||||
|  |             if(Method == Poco::Net::HTTPRequest::HTTP_POST) | ||||||
|  |                 return LogFlags_[Offset+LOG_POST]; | ||||||
|  |             if(Method == Poco::Net::HTTPRequest::HTTP_PUT) | ||||||
|  |                 return LogFlags_[Offset+LOG_PUT]; | ||||||
|  |             if(Method == Poco::Net::HTTPRequest::HTTP_DELETE) | ||||||
|  |                 return LogFlags_[Offset+LOG_DELETE]; | ||||||
|  |             return false; | ||||||
|  |         }; | ||||||
|  |  | ||||||
|  |         [[nodiscard]] inline bool LogBadTokens(bool External) const { | ||||||
|  |             return LogBadTokens_[ (External ? 0 : 1) ]; | ||||||
|  |         }; | ||||||
|  |  | ||||||
|  |     private: | ||||||
|  |         std::array<bool,8>       LogFlags_{false}; | ||||||
|  |         std::array<bool,2>       LogBadTokens_{false}; | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | #endif //OWPROV_RESTAPI_GENERICSERVER_H | ||||||
							
								
								
									
										80
									
								
								src/RESTAPI_InternalServer.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										80
									
								
								src/RESTAPI_InternalServer.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,80 @@ | |||||||
|  | // | ||||||
|  | // Created by stephane bourque on 2021-06-29. | ||||||
|  | // | ||||||
|  |  | ||||||
|  |  | ||||||
|  | #include "Poco/URI.h" | ||||||
|  |  | ||||||
|  | #include "RESTAPI_system_command.h" | ||||||
|  | #include "RESTAPI_user_handler.h" | ||||||
|  | #include "RESTAPI_users_handler.h" | ||||||
|  | #include "RESTAPI_action_links.h" | ||||||
|  | #include "RESTAPI_validateToken_handler.h" | ||||||
|  | #include "RESTAPI_InternalServer.h" | ||||||
|  |  | ||||||
|  | #include "Utils.h" | ||||||
|  |  | ||||||
|  | namespace OpenWifi { | ||||||
|  |  | ||||||
|  |     class RESTAPI_InternalServer *RESTAPI_InternalServer::instance_ = nullptr; | ||||||
|  |  | ||||||
|  |     RESTAPI_InternalServer::RESTAPI_InternalServer() noexcept: | ||||||
|  |         SubSystemServer("RESTAPIInternalServer", "REST-ISRV", "openwifi.internal.restapi") | ||||||
|  |     { | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     int RESTAPI_InternalServer::Start() { | ||||||
|  |         Logger_.information("Starting."); | ||||||
|  |         Server_.InitLogging(); | ||||||
|  |  | ||||||
|  |         for(const auto & Svr: ConfigServersList_) { | ||||||
|  |             Logger_.information(Poco::format("Starting: %s:%s Keyfile:%s CertFile: %s", Svr.Address(), std::to_string(Svr.Port()), | ||||||
|  |                                              Svr.KeyFile(),Svr.CertFile())); | ||||||
|  |  | ||||||
|  |             auto Sock{Svr.CreateSecureSocket(Logger_)}; | ||||||
|  |  | ||||||
|  |             Svr.LogCert(Logger_); | ||||||
|  |             if(!Svr.RootCA().empty()) | ||||||
|  |                 Svr.LogCas(Logger_); | ||||||
|  |  | ||||||
|  |             auto Params = new Poco::Net::HTTPServerParams; | ||||||
|  |             Params->setMaxThreads(50); | ||||||
|  |             Params->setMaxQueued(200); | ||||||
|  |             Params->setKeepAlive(true); | ||||||
|  |  | ||||||
|  |             auto NewServer = std::make_unique<Poco::Net::HTTPServer>(new InternalRequestHandlerFactory(Server_), Pool_, Sock, Params); | ||||||
|  |             NewServer->start(); | ||||||
|  |             RESTServers_.push_back(std::move(NewServer)); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return 0; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     void RESTAPI_InternalServer::Stop() { | ||||||
|  |         Logger_.information("Stopping "); | ||||||
|  |         for( const auto & svr : RESTServers_ ) | ||||||
|  |             svr->stop(); | ||||||
|  |         RESTServers_.clear(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     void RESTAPI_InternalServer::reinitialize(Poco::Util::Application &self) { | ||||||
|  |         Daemon()->LoadConfigurationFile(); | ||||||
|  |         Logger_.information("Reinitializing."); | ||||||
|  |         Stop(); | ||||||
|  |         Start(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     Poco::Net::HTTPRequestHandler *InternalRequestHandlerFactory::createRequestHandler(const Poco::Net::HTTPServerRequest & Request) { | ||||||
|  |         Poco::URI uri(Request.getURI()); | ||||||
|  |         const auto & Path = uri.getPath(); | ||||||
|  |         RESTAPIHandler::BindingMap Bindings; | ||||||
|  |         return RESTAPI_Router_I< | ||||||
|  |                 RESTAPI_users_handler, | ||||||
|  |                 RESTAPI_user_handler, | ||||||
|  |                 RESTAPI_system_command, | ||||||
|  |                 RESTAPI_action_links, | ||||||
|  |                 RESTAPI_validateToken_handler | ||||||
|  |         >(Path,Bindings,Logger_, Server_); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | } | ||||||
							
								
								
									
										57
									
								
								src/RESTAPI_InternalServer.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								src/RESTAPI_InternalServer.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,57 @@ | |||||||
|  | // | ||||||
|  | // Created by stephane bourque on 2021-06-29. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef UCENTRALSEC_RESTAPI_INTERNALSERVER_H | ||||||
|  | #define UCENTRALSEC_RESTAPI_INTERNALSERVER_H | ||||||
|  |  | ||||||
|  | #include "SubSystemServer.h" | ||||||
|  | #include "Poco/Net/HTTPServer.h" | ||||||
|  | #include "Poco/Net/HTTPRequestHandler.h" | ||||||
|  | #include "Poco/Net/HTTPRequestHandlerFactory.h" | ||||||
|  | #include "Poco/Net/HTTPServerRequest.h" | ||||||
|  | #include "Poco/Net/NetException.h" | ||||||
|  | #include "RESTAPI_GenericServer.h" | ||||||
|  |  | ||||||
|  | namespace OpenWifi { | ||||||
|  |  | ||||||
|  |     class RESTAPI_InternalServer : public SubSystemServer { | ||||||
|  |         public: | ||||||
|  |             RESTAPI_InternalServer() noexcept; | ||||||
|  |  | ||||||
|  |             static RESTAPI_InternalServer *instance() { | ||||||
|  |                 if (instance_ == nullptr) { | ||||||
|  |                     instance_ = new RESTAPI_InternalServer; | ||||||
|  |                 } | ||||||
|  |                 return instance_; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             int Start() override; | ||||||
|  |             void Stop() override; | ||||||
|  |             void reinitialize(Poco::Util::Application &self) override; | ||||||
|  |  | ||||||
|  |         private: | ||||||
|  |             static RESTAPI_InternalServer *instance_; | ||||||
|  |             std::vector<std::unique_ptr<Poco::Net::HTTPServer>>   RESTServers_; | ||||||
|  |             Poco::ThreadPool	    Pool_; | ||||||
|  |             RESTAPI_GenericServer   Server_; | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     inline RESTAPI_InternalServer * RESTAPI_InternalServer() { return RESTAPI_InternalServer::instance(); }; | ||||||
|  |  | ||||||
|  |     class InternalRequestHandlerFactory : public Poco::Net::HTTPRequestHandlerFactory { | ||||||
|  |         public: | ||||||
|  |         explicit InternalRequestHandlerFactory(RESTAPI_GenericServer & Server) : | ||||||
|  |                     Logger_(RESTAPI_InternalServer()->Logger()), | ||||||
|  |                     Server_(Server){} | ||||||
|  |  | ||||||
|  |             Poco::Net::HTTPRequestHandler *createRequestHandler(const Poco::Net::HTTPServerRequest &request) override; | ||||||
|  |         private: | ||||||
|  |             Poco::Logger    & Logger_; | ||||||
|  |             RESTAPI_GenericServer & Server_; | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |  | ||||||
|  | } //   namespace | ||||||
|  |  | ||||||
|  | #endif //UCENTRALSEC_RESTAPI_INTERNALSERVER_H | ||||||
| @@ -9,8 +9,8 @@ | |||||||
| #include "Poco/JSON/Parser.h" | #include "Poco/JSON/Parser.h" | ||||||
| #include "Poco/JSON/Stringifier.h" | #include "Poco/JSON/Stringifier.h" | ||||||
| 
 | 
 | ||||||
| #include "framework/MicroService.h" |  | ||||||
| #include "RESTAPI_SecurityObjects.h" | #include "RESTAPI_SecurityObjects.h" | ||||||
|  | #include "RESTAPI_utils.h" | ||||||
| 
 | 
 | ||||||
| using OpenWifi::RESTAPI_utils::field_to_json; | using OpenWifi::RESTAPI_utils::field_to_json; | ||||||
| using OpenWifi::RESTAPI_utils::field_from_json; | using OpenWifi::RESTAPI_utils::field_from_json; | ||||||
| @@ -54,35 +54,25 @@ namespace OpenWifi::SecurityObjects { | |||||||
|             return ADMIN; |             return ADMIN; | ||||||
|         else if (!Poco::icompare(U,"subscriber")) |         else if (!Poco::icompare(U,"subscriber")) | ||||||
|             return SUBSCRIBER; |             return SUBSCRIBER; | ||||||
|         else if (!Poco::icompare(U,"partner")) |  | ||||||
|             return PARTNER; |  | ||||||
|         else if (!Poco::icompare(U,"csr")) |         else if (!Poco::icompare(U,"csr")) | ||||||
|             return CSR; |             return CSR; | ||||||
|         else if (!Poco::icompare(U, "system")) |         else if (!Poco::icompare(U, "system")) | ||||||
|             return SYSTEM; |             return SYSTEM; | ||||||
|         else if (!Poco::icompare(U, "installer")) |         else if (!Poco::icompare(U, "special")) | ||||||
|             return INSTALLER; |             return SPECIAL; | ||||||
|         else if (!Poco::icompare(U, "noc")) |  | ||||||
|             return NOC; |  | ||||||
|         else if (!Poco::icompare(U, "accounting")) |  | ||||||
|             return ACCOUNTING; |  | ||||||
|         return UNKNOWN; |         return UNKNOWN; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     std::string UserTypeToString(USER_ROLE U) { |     std::string UserTypeToString(USER_ROLE U) { | ||||||
|         switch(U) { |         switch(U) { | ||||||
|  |             case UNKNOWN: return "unknown"; | ||||||
|             case ROOT: return "root"; |             case ROOT: return "root"; | ||||||
|             case ADMIN: return "admin"; |  | ||||||
|             case SUBSCRIBER: return "subscriber"; |             case SUBSCRIBER: return "subscriber"; | ||||||
|             case PARTNER: return "partner"; |  | ||||||
|             case CSR: return "csr"; |             case CSR: return "csr"; | ||||||
|             case SYSTEM: return "system"; |             case SYSTEM: return "system"; | ||||||
|             case INSTALLER: return "installer"; |             case SPECIAL: return "special"; | ||||||
|             case NOC: return "noc"; |             case ADMIN: return "admin"; | ||||||
|             case ACCOUNTING: return "accounting"; |             default: return "unknown"; | ||||||
|             case UNKNOWN: |  | ||||||
|             default: |  | ||||||
|                 return "unknown"; |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @@ -95,7 +85,6 @@ namespace OpenWifi::SecurityObjects { | |||||||
| 			field_from_json(Obj, "PortalLogin", PortalLogin_); | 			field_from_json(Obj, "PortalLogin", PortalLogin_); | ||||||
| 			return true; | 			return true; | ||||||
| 		} catch(...) { | 		} catch(...) { | ||||||
|             std::cout << "Cannot parse: AclTemplate" << std::endl; |  | ||||||
| 		} | 		} | ||||||
| 		return false; | 		return false; | ||||||
| 	} | 	} | ||||||
| @@ -113,8 +102,6 @@ namespace OpenWifi::SecurityObjects { | |||||||
|         field_to_json(Obj,"userMustChangePassword",userMustChangePassword); |         field_to_json(Obj,"userMustChangePassword",userMustChangePassword); | ||||||
|         field_to_json(Obj,"errorCode", errorCode); |         field_to_json(Obj,"errorCode", errorCode); | ||||||
| 		Obj.set("aclTemplate",AclTemplateObj); | 		Obj.set("aclTemplate",AclTemplateObj); | ||||||
|         field_to_json(Obj,"errorCode", errorCode); |  | ||||||
|         field_to_json(Obj,"lastRefresh", lastRefresh_); |  | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	bool WebToken::from_json(const Poco::JSON::Object::Ptr &Obj) { | 	bool WebToken::from_json(const Poco::JSON::Object::Ptr &Obj) { | ||||||
| @@ -131,105 +118,15 @@ namespace OpenWifi::SecurityObjects { | |||||||
| 			field_from_json(Obj, "created", created_); | 			field_from_json(Obj, "created", created_); | ||||||
| 			field_from_json(Obj, "username", username_); | 			field_from_json(Obj, "username", username_); | ||||||
|             field_from_json(Obj, "userMustChangePassword",userMustChangePassword); |             field_from_json(Obj, "userMustChangePassword",userMustChangePassword); | ||||||
|             field_from_json(Obj,"lastRefresh", lastRefresh_); |  | ||||||
| 			return true; | 			return true; | ||||||
| 		} catch (...) { | 		} catch (...) { | ||||||
|             std::cout << "Cannot parse: WebToken" << std::endl; | 
 | ||||||
| 		} | 		} | ||||||
| 		return false; | 		return false; | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	void MobilePhoneNumber::to_json(Poco::JSON::Object &Obj) const { |  | ||||||
| 	    field_to_json(Obj,"number", number); |  | ||||||
| 	    field_to_json(Obj,"verified", verified); |  | ||||||
| 	    field_to_json(Obj,"primary", primary); |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	bool MobilePhoneNumber::from_json(const Poco::JSON::Object::Ptr &Obj) { |  | ||||||
| 	    try { |  | ||||||
| 	        field_from_json(Obj,"number",number); |  | ||||||
| 	        field_from_json(Obj,"verified",verified); |  | ||||||
| 	        field_from_json(Obj,"primary",primary); |  | ||||||
| 	        return true; |  | ||||||
| 	    } catch (...) { |  | ||||||
|             std::cout << "Cannot parse: MobilePhoneNumber" << std::endl; |  | ||||||
| 	    } |  | ||||||
| 	    return false; |  | ||||||
| 	}; |  | ||||||
| 
 |  | ||||||
| 	void MfaAuthInfo::to_json(Poco::JSON::Object &Obj) const { |  | ||||||
| 	    field_to_json(Obj,"enabled", enabled); |  | ||||||
| 	    field_to_json(Obj,"method", method); |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	bool MfaAuthInfo::from_json(const Poco::JSON::Object::Ptr &Obj) { |  | ||||||
| 	    try { |  | ||||||
| 	        field_from_json(Obj,"enabled",enabled); |  | ||||||
| 	        field_from_json(Obj,"method",method); |  | ||||||
| 	        return true; |  | ||||||
| 	    } catch (...) { |  | ||||||
|             std::cout << "Cannot parse: MfaAuthInfo" << std::endl; |  | ||||||
| 	    } |  | ||||||
| 	    return false; |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	void UserLoginLoginExtensions::to_json(Poco::JSON::Object &Obj) const { |  | ||||||
| 	    field_to_json(Obj, "mobiles", mobiles); |  | ||||||
| 	    field_to_json(Obj, "mfa", mfa); |  | ||||||
|         field_to_json(Obj, "authenticatorSecret", authenticatorSecret); |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	bool UserLoginLoginExtensions::from_json(const Poco::JSON::Object::Ptr &Obj) { |  | ||||||
| 	    try { |  | ||||||
| 	        field_from_json(Obj, "mobiles",mobiles); |  | ||||||
| 	        field_from_json(Obj, "mfa",mfa); |  | ||||||
|             field_from_json(Obj, "authenticatorSecret", authenticatorSecret); |  | ||||||
| 	        return true; |  | ||||||
| 	    } catch (...) { |  | ||||||
|             std::cout << "Cannot parse: UserLoginLoginExtensions" << std::endl; |  | ||||||
| 	    } |  | ||||||
| 	    return false; |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
|     void MFAChallengeRequest::to_json(Poco::JSON::Object &Obj) const { |  | ||||||
|         field_to_json(Obj, "uuid", uuid); |  | ||||||
|         field_to_json(Obj, "question", question); |  | ||||||
|         field_to_json(Obj, "created", created); |  | ||||||
|         field_to_json(Obj, "method", method); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     bool MFAChallengeRequest::from_json(const Poco::JSON::Object::Ptr &Obj) { |  | ||||||
| 	    try { |  | ||||||
| 	        field_from_json(Obj,"uuid",uuid); |  | ||||||
| 	        field_from_json(Obj,"question",question); |  | ||||||
| 	        field_from_json(Obj,"created",created); |  | ||||||
| 	        field_from_json(Obj,"method",method); |  | ||||||
| 	        return true; |  | ||||||
| 	    } catch (...) { |  | ||||||
|             std::cout << "Cannot parse: MFAChallengeRequest" << std::endl; |  | ||||||
| 	    } |  | ||||||
| 	    return false; |  | ||||||
| 	}; |  | ||||||
| 
 |  | ||||||
|     void MFAChallengeResponse::to_json(Poco::JSON::Object &Obj) const { |  | ||||||
|         field_to_json(Obj, "uuid", uuid); |  | ||||||
|         field_to_json(Obj, "answer", answer); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     bool MFAChallengeResponse::from_json(const Poco::JSON::Object::Ptr &Obj) { |  | ||||||
|         try { |  | ||||||
|             field_from_json(Obj,"uuid",uuid); |  | ||||||
|             field_from_json(Obj,"answer",answer); |  | ||||||
|             return true; |  | ||||||
|         } catch (...) { |  | ||||||
|             std::cout << "Cannot parse: MFAChallengeResponse" << std::endl; |  | ||||||
|         } |  | ||||||
|         return false; |  | ||||||
| 
 |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     void UserInfo::to_json(Poco::JSON::Object &Obj) const { |     void UserInfo::to_json(Poco::JSON::Object &Obj) const { | ||||||
| 		field_to_json(Obj,"id",id); | 		field_to_json(Obj,"Id",Id); | ||||||
| 		field_to_json(Obj,"name",name); | 		field_to_json(Obj,"name",name); | ||||||
| 		field_to_json(Obj,"description", description); | 		field_to_json(Obj,"description", description); | ||||||
| 		field_to_json(Obj,"avatar", avatar); | 		field_to_json(Obj,"avatar", avatar); | ||||||
| @@ -259,13 +156,11 @@ namespace OpenWifi::SecurityObjects { | |||||||
| 		field_to_json(Obj,"lastPasswords",lastPasswords); | 		field_to_json(Obj,"lastPasswords",lastPasswords); | ||||||
| 		field_to_json(Obj,"oauthType",oauthType); | 		field_to_json(Obj,"oauthType",oauthType); | ||||||
| 		field_to_json(Obj,"oauthUserInfo",oauthUserInfo); | 		field_to_json(Obj,"oauthUserInfo",oauthUserInfo); | ||||||
|         field_to_json(Obj,"modified",modified); |  | ||||||
|         field_to_json(Obj,"signingUp",signingUp); |  | ||||||
|     }; |     }; | ||||||
| 
 | 
 | ||||||
|     bool UserInfo::from_json(const Poco::JSON::Object::Ptr &Obj) { |     bool UserInfo::from_json(const Poco::JSON::Object::Ptr &Obj) { | ||||||
|         try { |         try { | ||||||
| 			field_from_json(Obj,"id",id); | 			field_from_json(Obj,"Id",Id); | ||||||
| 			field_from_json(Obj,"name",name); | 			field_from_json(Obj,"name",name); | ||||||
| 			field_from_json(Obj,"description",description); | 			field_from_json(Obj,"description",description); | ||||||
| 			field_from_json(Obj,"avatar",avatar); | 			field_from_json(Obj,"avatar",avatar); | ||||||
| @@ -275,8 +170,6 @@ namespace OpenWifi::SecurityObjects { | |||||||
| 			field_from_json(Obj,"currentLoginURI",currentLoginURI); | 			field_from_json(Obj,"currentLoginURI",currentLoginURI); | ||||||
| 			field_from_json(Obj,"locale",locale); | 			field_from_json(Obj,"locale",locale); | ||||||
| 			field_from_json(Obj,"notes",notes); | 			field_from_json(Obj,"notes",notes); | ||||||
|             field_from_json(Obj,"location", location); |  | ||||||
|             field_from_json(Obj,"owner", owner); |  | ||||||
| 			field_from_json<USER_ROLE>(Obj,"userRole",userRole, UserTypeFromString); | 			field_from_json<USER_ROLE>(Obj,"userRole",userRole, UserTypeFromString); | ||||||
| 			field_from_json(Obj,"securityPolicy",securityPolicy); | 			field_from_json(Obj,"securityPolicy",securityPolicy); | ||||||
| 			field_from_json(Obj,"userTypeProprietaryInfo",userTypeProprietaryInfo); | 			field_from_json(Obj,"userTypeProprietaryInfo",userTypeProprietaryInfo); | ||||||
| @@ -295,29 +188,13 @@ namespace OpenWifi::SecurityObjects { | |||||||
| 			field_from_json(Obj,"lastPasswords",lastPasswords); | 			field_from_json(Obj,"lastPasswords",lastPasswords); | ||||||
| 			field_from_json(Obj,"oauthType",oauthType); | 			field_from_json(Obj,"oauthType",oauthType); | ||||||
| 			field_from_json(Obj,"oauthUserInfo",oauthUserInfo); | 			field_from_json(Obj,"oauthUserInfo",oauthUserInfo); | ||||||
|             field_from_json(Obj,"modified",modified); |  | ||||||
|             field_from_json(Obj,"signingUp",signingUp); |  | ||||||
|             return true; |             return true; | ||||||
|         } catch (const Poco::Exception &E) { |         } catch (const Poco::Exception &E) { | ||||||
|             std::cout << "Cannot parse: UserInfo" << std::endl; | 
 | ||||||
|         } |         } | ||||||
|         return false; |         return false; | ||||||
|     }; |     }; | ||||||
| 
 | 
 | ||||||
|     void UserInfoList::to_json(Poco::JSON::Object &Obj) const { |  | ||||||
|         field_to_json(Obj,"users",users); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     bool UserInfoList::from_json(const Poco::JSON::Object::Ptr &Obj) { |  | ||||||
|         try { |  | ||||||
|             field_from_json(Obj,"users",users); |  | ||||||
|             return true; |  | ||||||
|         } catch (...) { |  | ||||||
|             std::cout << "Cannot parse: InternalServiceInfo" << std::endl; |  | ||||||
|         } |  | ||||||
|         return false; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
| 	void InternalServiceInfo::to_json(Poco::JSON::Object &Obj) const { | 	void InternalServiceInfo::to_json(Poco::JSON::Object &Obj) const { | ||||||
| 		field_to_json(Obj,"privateURI",privateURI); | 		field_to_json(Obj,"privateURI",privateURI); | ||||||
| 		field_to_json(Obj,"publicURI",publicURI); | 		field_to_json(Obj,"publicURI",publicURI); | ||||||
| @@ -331,7 +208,7 @@ namespace OpenWifi::SecurityObjects { | |||||||
| 			field_from_json(Obj,"token",token); | 			field_from_json(Obj,"token",token); | ||||||
| 			return true; | 			return true; | ||||||
| 		} catch (...) { | 		} catch (...) { | ||||||
|             std::cout << "Cannot parse: InternalServiceInfo" << std::endl; | 
 | ||||||
| 		} | 		} | ||||||
| 		return false; | 		return false; | ||||||
| 	}; | 	}; | ||||||
| @@ -349,7 +226,7 @@ namespace OpenWifi::SecurityObjects { | |||||||
| 			field_from_json(Obj, "services", services); | 			field_from_json(Obj, "services", services); | ||||||
| 			return true; | 			return true; | ||||||
| 		} catch(...) { | 		} catch(...) { | ||||||
|             std::cout << "Cannot parse: InternalSystemServices" << std::endl; | 
 | ||||||
| 		} | 		} | ||||||
| 		return false; | 		return false; | ||||||
| 	}; | 	}; | ||||||
| @@ -371,7 +248,7 @@ namespace OpenWifi::SecurityObjects { | |||||||
| 			field_from_json(Obj, "authenticationType", authenticationType); | 			field_from_json(Obj, "authenticationType", authenticationType); | ||||||
| 			return true; | 			return true; | ||||||
| 		} catch (...) { | 		} catch (...) { | ||||||
|             std::cout << "Cannot parse: SystemEndpoint" << std::endl; | 
 | ||||||
| 		} | 		} | ||||||
| 		return false; | 		return false; | ||||||
| 	}; | 	}; | ||||||
| @@ -385,7 +262,7 @@ namespace OpenWifi::SecurityObjects { | |||||||
| 			field_from_json(Obj, "endpoints", endpoints); | 			field_from_json(Obj, "endpoints", endpoints); | ||||||
| 			return true; | 			return true; | ||||||
| 		} catch (...) { | 		} catch (...) { | ||||||
|             std::cout << "Cannot parse: SystemEndpointList" << std::endl; | 
 | ||||||
| 		} | 		} | ||||||
| 		return false; | 		return false; | ||||||
| 	} | 	} | ||||||
| @@ -404,7 +281,7 @@ namespace OpenWifi::SecurityObjects { | |||||||
| 			field_from_json(Obj, "userInfo", userinfo); | 			field_from_json(Obj, "userInfo", userinfo); | ||||||
| 			return true; | 			return true; | ||||||
| 		} catch(...) { | 		} catch(...) { | ||||||
|             std::cout << "Cannot parse: UserInfoAndPolicy" << std::endl; | 
 | ||||||
| 		} | 		} | ||||||
| 		return false; | 		return false; | ||||||
| 	} | 	} | ||||||
| @@ -415,55 +292,42 @@ namespace OpenWifi::SecurityObjects { | |||||||
| 		field_to_json(Obj,"note", note); | 		field_to_json(Obj,"note", note); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	bool NoteInfo::from_json(const Poco::JSON::Object::Ptr &Obj) { | 	bool NoteInfo::from_json(Poco::JSON::Object::Ptr Obj) { | ||||||
| 		try { | 		try { | ||||||
| 			field_from_json(Obj,"created",created); | 			field_from_json(Obj,"created",created); | ||||||
| 			field_from_json(Obj,"createdBy",createdBy); | 			field_from_json(Obj,"createdBy",createdBy); | ||||||
| 			field_from_json(Obj,"note", note); | 			field_from_json(Obj,"note",note); | ||||||
| 			return true; |  | ||||||
| 		} catch(...) { | 		} catch(...) { | ||||||
|             std::cout << "Cannot parse: NoteInfo" << std::endl; | 
 | ||||||
| 		} | 		} | ||||||
| 		return false; | 		return false; | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|     bool MergeNotes(Poco::JSON::Object::Ptr Obj, const UserInfo &UInfo, NoteInfoVec & Notes) { |     bool append_from_json(Poco::JSON::Object::Ptr Obj, const UserInfo &UInfo, NoteInfoVec & Notes) { | ||||||
| 	    try { | 	    try { | ||||||
| 	        if(Obj->has("notes") && Obj->isArray("notes")) { |  | ||||||
| 	        SecurityObjects::NoteInfoVec NIV; | 	        SecurityObjects::NoteInfoVec NIV; | ||||||
| 	        NIV = RESTAPI_utils::to_object_array<SecurityObjects::NoteInfo>(Obj->get("notes").toString()); | 	        NIV = RESTAPI_utils::to_object_array<SecurityObjects::NoteInfo>(Obj->get("notes").toString()); | ||||||
| 	        for(auto const &i:NIV) { | 	        for(auto const &i:NIV) { | ||||||
| 	                SecurityObjects::NoteInfo   ii{.created=(uint64_t)OpenWifi::Now(), .createdBy=UInfo.email, .note=i.note}; | 	            SecurityObjects::NoteInfo   ii{.created=(uint64_t)std::time(nullptr), .createdBy=UInfo.email, .note=i.note}; | ||||||
| 	            Notes.push_back(ii); | 	            Notes.push_back(ii); | ||||||
| 	        } | 	        } | ||||||
| 	        } |  | ||||||
| 	        return true; |  | ||||||
| 	    } catch(...) { | 	    } catch(...) { | ||||||
|             std::cout << "Cannot parse: MergeNotes" << std::endl; | 
 | ||||||
| 	    } | 	    } | ||||||
| 	    return false; | 	    return false; | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	bool MergeNotes(const NoteInfoVec & NewNotes, const UserInfo &UInfo, NoteInfoVec & ExistingNotes) { |  | ||||||
| 	    for(auto const &i:NewNotes) { |  | ||||||
| 	        SecurityObjects::NoteInfo   ii{.created=(uint64_t)OpenWifi::Now(), .createdBy=UInfo.email, .note=i.note}; |  | ||||||
| 	        ExistingNotes.push_back(ii); |  | ||||||
| 	    } |  | ||||||
|         return true; |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
| 	void ProfileAction::to_json(Poco::JSON::Object &Obj) const { | 	void ProfileAction::to_json(Poco::JSON::Object &Obj) const { | ||||||
| 		field_to_json(Obj,"resource", resource); | 		field_to_json(Obj,"resource", resource); | ||||||
| 		field_to_json<ResourceAccessType>(Obj,"access", access, ResourceAccessTypeToString); | 		field_to_json<ResourceAccessType>(Obj,"access", access, ResourceAccessTypeToString); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	bool ProfileAction::from_json(const Poco::JSON::Object::Ptr &Obj) { | 	bool ProfileAction::from_json(Poco::JSON::Object::Ptr Obj) { | ||||||
| 		try { | 		try { | ||||||
| 			field_from_json(Obj,"resource",resource); | 			field_from_json(Obj,"resource",resource); | ||||||
| 			field_from_json<ResourceAccessType>(Obj,"access",access,ResourceAccessTypeFromString ); | 			field_from_json<ResourceAccessType>(Obj,"access",access,ResourceAccessTypeFromString ); | ||||||
| 			return true; |  | ||||||
| 		} catch(...) { | 		} catch(...) { | ||||||
|             std::cout << "Cannot parse: ProfileAction" << std::endl; | 
 | ||||||
| 		} | 		} | ||||||
| 		return false; | 		return false; | ||||||
| 	} | 	} | ||||||
| @@ -477,7 +341,7 @@ namespace OpenWifi::SecurityObjects { | |||||||
| 		field_to_json(Obj,"notes", notes); | 		field_to_json(Obj,"notes", notes); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	bool SecurityProfile::from_json(const Poco::JSON::Object::Ptr &Obj) { | 	bool SecurityProfile::from_json(Poco::JSON::Object::Ptr Obj) { | ||||||
| 		try { | 		try { | ||||||
| 			field_from_json(Obj,"id",id); | 			field_from_json(Obj,"id",id); | ||||||
| 			field_from_json(Obj,"name",name); | 			field_from_json(Obj,"name",name); | ||||||
| @@ -485,9 +349,8 @@ namespace OpenWifi::SecurityObjects { | |||||||
| 			field_from_json(Obj,"policy",policy); | 			field_from_json(Obj,"policy",policy); | ||||||
| 			field_from_json(Obj,"role",role); | 			field_from_json(Obj,"role",role); | ||||||
| 			field_from_json(Obj,"notes",notes); | 			field_from_json(Obj,"notes",notes); | ||||||
| 			return true; |  | ||||||
| 		} catch(...) { | 		} catch(...) { | ||||||
|             std::cout << "Cannot parse: SecurityProfile" << std::endl; | 
 | ||||||
| 		} | 		} | ||||||
| 		return false; | 		return false; | ||||||
| 	} | 	} | ||||||
| @@ -496,128 +359,13 @@ namespace OpenWifi::SecurityObjects { | |||||||
| 		field_to_json(Obj, "profiles", profiles); | 		field_to_json(Obj, "profiles", profiles); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	bool SecurityProfileList::from_json(const Poco::JSON::Object::Ptr &Obj) { | 	bool SecurityProfileList::from_json(Poco::JSON::Object::Ptr Obj) { | ||||||
| 		try { | 		try { | ||||||
| 			field_from_json(Obj,"profiles",profiles); | 			field_from_json(Obj,"profiles",profiles); | ||||||
| 			return true; |  | ||||||
| 		} catch(...) { | 		} catch(...) { | ||||||
|             std::cout << "Cannot parse: SecurityProfileList" << std::endl; | 
 | ||||||
| 		} | 		} | ||||||
| 		return false; | 		return false; | ||||||
| 	} | 	} | ||||||
| 
 |  | ||||||
|     void ActionLink::to_json(Poco::JSON::Object &Obj) const { |  | ||||||
| 	    field_to_json(Obj,"id",id); |  | ||||||
| 	    field_to_json(Obj,"action",action); |  | ||||||
| 	    field_to_json(Obj,"userId",userId); |  | ||||||
| 	    field_to_json(Obj,"actionTemplate",actionTemplate); |  | ||||||
| 	    field_to_json(Obj,"variables",variables); |  | ||||||
| 	    field_to_json(Obj,"locale",locale); |  | ||||||
| 	    field_to_json(Obj,"message",message); |  | ||||||
| 	    field_to_json(Obj,"sent",sent); |  | ||||||
| 	    field_to_json(Obj,"created",created); |  | ||||||
| 	    field_to_json(Obj,"expires",expires); |  | ||||||
| 	    field_to_json(Obj,"completed",completed); |  | ||||||
| 	    field_to_json(Obj,"canceled",canceled); |  | ||||||
|         field_to_json(Obj,"userAction",userAction); |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
|     bool ActionLink::from_json(const Poco::JSON::Object::Ptr &Obj) { |  | ||||||
| 	    try { |  | ||||||
| 	        field_from_json(Obj,"id",id); |  | ||||||
| 	        field_from_json(Obj,"action",action); |  | ||||||
| 	        field_from_json(Obj,"userId",userId); |  | ||||||
| 	        field_from_json(Obj,"actionTemplate",actionTemplate); |  | ||||||
| 	        field_from_json(Obj,"variables",variables); |  | ||||||
| 	        field_from_json(Obj,"locale",locale); |  | ||||||
| 	        field_from_json(Obj,"message",message); |  | ||||||
| 	        field_from_json(Obj,"sent",sent); |  | ||||||
| 	        field_from_json(Obj,"created",created); |  | ||||||
| 	        field_from_json(Obj,"expires",expires); |  | ||||||
| 	        field_from_json(Obj,"completed",completed); |  | ||||||
| 	        field_from_json(Obj,"canceled",canceled); |  | ||||||
|             field_from_json(Obj,"userAction",userAction); |  | ||||||
| 	        return true; |  | ||||||
| 	    } catch(...) { |  | ||||||
|             std::cout << "Cannot parse: ActionLink" << std::endl; |  | ||||||
| 	    } |  | ||||||
| 	    return false; |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
|     void Preferences::to_json(Poco::JSON::Object &Obj) const { |  | ||||||
| 	    field_to_json(Obj,"id",id); |  | ||||||
| 	    field_to_json(Obj,"modified",modified); |  | ||||||
| 	    field_to_json(Obj,"data",data); |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
|     bool Preferences::from_json(const Poco::JSON::Object::Ptr &Obj) { |  | ||||||
| 	    try { |  | ||||||
| 	        field_from_json(Obj,"id",id); |  | ||||||
| 	        field_from_json(Obj,"modified",modified); |  | ||||||
| 	        field_from_json(Obj,"data",data); |  | ||||||
| 	        return true; |  | ||||||
| 	    } catch(...) { |  | ||||||
|             std::cout << "Cannot parse: Preferences" << std::endl; |  | ||||||
| 	    } |  | ||||||
| 	    return false; |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
|     void SubMfaConfig::to_json(Poco::JSON::Object &Obj) const { |  | ||||||
| 	    field_to_json(Obj,"id",id); |  | ||||||
| 	    field_to_json(Obj,"type",type); |  | ||||||
| 	    field_to_json(Obj,"sms",sms); |  | ||||||
| 	    field_to_json(Obj,"email",email); |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
|     bool SubMfaConfig::from_json(const Poco::JSON::Object::Ptr &Obj) { |  | ||||||
| 	    try { |  | ||||||
| 	        field_from_json(Obj,"id",id); |  | ||||||
| 	        field_from_json(Obj,"type",type); |  | ||||||
| 	        field_from_json(Obj,"sms",sms); |  | ||||||
| 	        field_from_json(Obj,"email",email); |  | ||||||
| 	        return true; |  | ||||||
| 	    } catch(...) { |  | ||||||
|             std::cout << "Cannot parse: SubMfaConfig" << std::endl; |  | ||||||
| 	    } |  | ||||||
| 	    return false; |  | ||||||
| 	} |  | ||||||
| 
 |  | ||||||
|     void Token::to_json(Poco::JSON::Object &Obj) const { |  | ||||||
|         field_to_json(Obj,"token",token); |  | ||||||
|         field_to_json(Obj,"refreshToken",refreshToken); |  | ||||||
|         field_to_json(Obj,"tokenType",tokenType); |  | ||||||
|         field_to_json(Obj,"userName",userName); |  | ||||||
|         field_to_json(Obj,"created",created); |  | ||||||
|         field_to_json(Obj,"expires",expires); |  | ||||||
|         field_to_json(Obj,"idleTimeout",idleTimeout); |  | ||||||
|         field_to_json(Obj,"revocationDate",revocationDate); |  | ||||||
|         field_to_json(Obj,"lastRefresh", lastRefresh); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     bool Token::from_json(const Poco::JSON::Object::Ptr &Obj) { |  | ||||||
|         try { |  | ||||||
|             field_from_json(Obj,"token",token); |  | ||||||
|             field_from_json(Obj,"refreshToken",refreshToken); |  | ||||||
|             field_from_json(Obj,"tokenType",tokenType); |  | ||||||
|             field_from_json(Obj,"userName",userName); |  | ||||||
|             field_from_json(Obj,"created",created); |  | ||||||
|             field_from_json(Obj,"expires",expires); |  | ||||||
|             field_from_json(Obj,"idleTimeout",idleTimeout); |  | ||||||
|             field_from_json(Obj,"revocationDate",revocationDate); |  | ||||||
|             field_from_json(Obj,"lastRefresh", lastRefresh); |  | ||||||
|             return true; |  | ||||||
|         } catch(...) { |  | ||||||
|             std::cout << "Cannot parse: Token" << std::endl; |  | ||||||
|         } |  | ||||||
|         return false; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     void LoginRecordInfo::to_json(Poco::JSON::Object &Obj) const { |  | ||||||
|         field_to_json(Obj,"sessionId",sessionId); |  | ||||||
|         field_to_json(Obj,"userId",userId); |  | ||||||
|         field_to_json(Obj,"email",email); |  | ||||||
|         field_to_json(Obj,"login",login); |  | ||||||
|         field_to_json(Obj,"logout",logout); |  | ||||||
|     } |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
							
								
								
									
										181
									
								
								src/RESTAPI_SecurityObjects.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										181
									
								
								src/RESTAPI_SecurityObjects.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,181 @@ | |||||||
|  | // | ||||||
|  | //	License type: BSD 3-Clause License | ||||||
|  | //	License copy: https://github.com/Telecominfraproject/wlan-cloud-ucentralgw/blob/master/LICENSE | ||||||
|  | // | ||||||
|  | //	Created by Stephane Bourque on 2021-03-04. | ||||||
|  | //	Arilia Wireless Inc. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef UCENTRAL_RESTAPI_SECURITYOBJECTS_H | ||||||
|  | #define UCENTRAL_RESTAPI_SECURITYOBJECTS_H | ||||||
|  |  | ||||||
|  | #include "Poco/JSON/Object.h" | ||||||
|  | #include "OpenWifiTypes.h" | ||||||
|  |  | ||||||
|  | namespace OpenWifi::SecurityObjects { | ||||||
|  |  | ||||||
|  | 	struct AclTemplate { | ||||||
|  | 		bool Read_ = true; | ||||||
|  | 		bool ReadWrite_ = true; | ||||||
|  | 		bool ReadWriteCreate_ = true; | ||||||
|  | 		bool Delete_ = true; | ||||||
|  | 		bool PortalLogin_ = true; | ||||||
|  |  | ||||||
|  | 		void to_json(Poco::JSON::Object &Obj) const; | ||||||
|  | 		bool from_json(const Poco::JSON::Object::Ptr &Obj);	}; | ||||||
|  |  | ||||||
|  | 	struct WebToken { | ||||||
|  | 		std::string access_token_; | ||||||
|  | 		std::string refresh_token_; | ||||||
|  | 		std::string id_token_; | ||||||
|  | 		std::string token_type_; | ||||||
|  | 		std::string username_; | ||||||
|  | 		bool userMustChangePassword=false; | ||||||
|  |         uint64_t errorCode=0; | ||||||
|  | 		uint64_t expires_in_=0; | ||||||
|  | 		uint64_t idle_timeout_=0; | ||||||
|  | 		AclTemplate acl_template_; | ||||||
|  | 		uint64_t created_=0; | ||||||
|  |  | ||||||
|  | 		void to_json(Poco::JSON::Object &Obj) const; | ||||||
|  | 		bool from_json(const Poco::JSON::Object::Ptr &Obj); | ||||||
|  | 	}; | ||||||
|  |  | ||||||
|  |     enum USER_ROLE { | ||||||
|  |         UNKNOWN, ROOT, ADMIN, SUBSCRIBER, CSR, SYSTEM, SPECIAL | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     USER_ROLE UserTypeFromString(const std::string &U); | ||||||
|  |     std::string UserTypeToString(USER_ROLE U); | ||||||
|  |  | ||||||
|  |     struct NoteInfo { | ||||||
|  | 		uint64_t created = std::time(nullptr); | ||||||
|  | 		std::string createdBy; | ||||||
|  | 		std::string note; | ||||||
|  | 		void to_json(Poco::JSON::Object &Obj) const; | ||||||
|  | 		bool from_json(Poco::JSON::Object::Ptr Obj); | ||||||
|  | 	}; | ||||||
|  | 	typedef std::vector<NoteInfo>	NoteInfoVec; | ||||||
|  |  | ||||||
|  | 	struct UserInfo { | ||||||
|  |         std::string Id; | ||||||
|  | 		std::string name; | ||||||
|  | 		std::string description; | ||||||
|  | 		std::string avatar; | ||||||
|  | 		std::string email; | ||||||
|  | 		bool validated = false; | ||||||
|  | 		std::string validationEmail; | ||||||
|  | 		uint64_t validationDate = 0; | ||||||
|  | 		uint64_t creationDate = 0; | ||||||
|  | 		std::string validationURI; | ||||||
|  | 		bool changePassword = false; | ||||||
|  | 		uint64_t lastLogin = 0; | ||||||
|  | 		std::string currentLoginURI; | ||||||
|  | 		uint64_t lastPasswordChange = 0; | ||||||
|  | 		uint64_t lastEmailCheck = 0; | ||||||
|  | 		bool waitingForEmailCheck = false; | ||||||
|  | 		std::string locale; | ||||||
|  | 		NoteInfoVec notes; | ||||||
|  | 		std::string location; | ||||||
|  | 		std::string owner; | ||||||
|  | 		bool suspended = false; | ||||||
|  | 		bool blackListed = false; | ||||||
|  |         USER_ROLE userRole; | ||||||
|  | 		std::string userTypeProprietaryInfo; | ||||||
|  | 		std::string securityPolicy; | ||||||
|  | 		uint64_t securityPolicyChange = 0 ; | ||||||
|  | 		std::string currentPassword; | ||||||
|  | 		Types::StringVec lastPasswords; | ||||||
|  | 		std::string oauthType; | ||||||
|  | 		std::string oauthUserInfo; | ||||||
|  |  | ||||||
|  | 		void to_json(Poco::JSON::Object &Obj) const; | ||||||
|  | 		bool from_json(const Poco::JSON::Object::Ptr &Obj); | ||||||
|  | 	}; | ||||||
|  | 	typedef std::vector<UserInfo>   UserInfoVec; | ||||||
|  |  | ||||||
|  | 	bool append_from_json(Poco::JSON::Object::Ptr Obj, const UserInfo &UInfo, NoteInfoVec & Notes); | ||||||
|  |  | ||||||
|  | 	struct InternalServiceInfo { | ||||||
|  | 		std::string privateURI; | ||||||
|  | 		std::string publicURI; | ||||||
|  | 		std::string token; | ||||||
|  | 		void to_json(Poco::JSON::Object &Obj) const; | ||||||
|  | 		bool from_json(const Poco::JSON::Object::Ptr &Obj); | ||||||
|  | 	}; | ||||||
|  | 	typedef std::vector<InternalServiceInfo>	InternalServiceInfoVec; | ||||||
|  |  | ||||||
|  | 	struct InternalSystemServices { | ||||||
|  | 		std::string key; | ||||||
|  | 		std::string version; | ||||||
|  | 		InternalServiceInfoVec services; | ||||||
|  | 		void to_json(Poco::JSON::Object &Obj) const; | ||||||
|  | 		bool from_json(const Poco::JSON::Object::Ptr &Obj); | ||||||
|  | 	}; | ||||||
|  |  | ||||||
|  | 	struct SystemEndpoint { | ||||||
|  | 		std::string type; | ||||||
|  | 		uint64_t 	id = 0; | ||||||
|  | 		std::string vendor{"OpenWiFi"}; | ||||||
|  | 		std::string uri; | ||||||
|  | 		std::string authenticationType{"internal_v1"}; | ||||||
|  | 		void to_json(Poco::JSON::Object &Obj) const; | ||||||
|  | 		bool from_json(const Poco::JSON::Object::Ptr &Obj); | ||||||
|  | 	}; | ||||||
|  | 	typedef std::vector<SystemEndpoint> SystemEndpointVec; | ||||||
|  |  | ||||||
|  | 	struct SystemEndpointList { | ||||||
|  | 		SystemEndpointVec	endpoints; | ||||||
|  | 		void to_json(Poco::JSON::Object &Obj) const; | ||||||
|  | 		bool from_json(const Poco::JSON::Object::Ptr &Obj); | ||||||
|  | 	}; | ||||||
|  |  | ||||||
|  | 	struct UserInfoAndPolicy { | ||||||
|  | 		WebToken	webtoken; | ||||||
|  | 		UserInfo	userinfo; | ||||||
|  | 		void to_json(Poco::JSON::Object &Obj) const; | ||||||
|  | 		bool from_json(const Poco::JSON::Object::Ptr &Obj); | ||||||
|  | 	}; | ||||||
|  | 	typedef std::map<std::string,SecurityObjects::UserInfoAndPolicy>	UserInfoCache; | ||||||
|  |  | ||||||
|  | 	enum ResourceAccessType { | ||||||
|  | 		NONE, | ||||||
|  | 		READ, | ||||||
|  | 		MODIFY, | ||||||
|  | 		DELETE, | ||||||
|  | 		CREATE, | ||||||
|  | 		TEST, | ||||||
|  | 		MOVE | ||||||
|  | 	}; | ||||||
|  |  | ||||||
|  | 	ResourceAccessType ResourceAccessTypeFromString(const std::string &s); | ||||||
|  | 	std::string ResourceAccessTypeToString(const ResourceAccessType & T); | ||||||
|  |  | ||||||
|  | 	struct ProfileAction { | ||||||
|  | 		std::string resource; | ||||||
|  | 		ResourceAccessType access; | ||||||
|  | 		void to_json(Poco::JSON::Object &Obj) const; | ||||||
|  | 		bool from_json(Poco::JSON::Object::Ptr Obj); | ||||||
|  | 	}; | ||||||
|  | 	typedef std::vector<ProfileAction>	ProfileActionVec; | ||||||
|  |  | ||||||
|  | 	struct SecurityProfile { | ||||||
|  | 		uint64_t id=0; | ||||||
|  | 		std::string name; | ||||||
|  | 		std::string description; | ||||||
|  | 		ProfileActionVec policy; | ||||||
|  | 		std::string role; | ||||||
|  | 		NoteInfoVec notes; | ||||||
|  | 		void to_json(Poco::JSON::Object &Obj) const; | ||||||
|  | 		bool from_json(Poco::JSON::Object::Ptr Obj); | ||||||
|  | 	}; | ||||||
|  | 	typedef std::vector<SecurityProfile> SecurityProfileVec; | ||||||
|  |  | ||||||
|  | 	struct SecurityProfileList { | ||||||
|  | 		SecurityProfileVec profiles; | ||||||
|  | 		void to_json(Poco::JSON::Object &Obj) const; | ||||||
|  | 		bool from_json(Poco::JSON::Object::Ptr Obj); | ||||||
|  | 	}; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #endif //UCENTRAL_RESTAPI_SECURITYOBJECTS_H | ||||||
							
								
								
									
										132
									
								
								src/RESTAPI_action_links.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										132
									
								
								src/RESTAPI_action_links.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,132 @@ | |||||||
|  | // | ||||||
|  | // Created by stephane bourque on 2021-06-22. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #include "RESTAPI_action_links.h" | ||||||
|  | #include "StorageService.h" | ||||||
|  | #include "Utils.h" | ||||||
|  | #include "RESTAPI_utils.h" | ||||||
|  |  | ||||||
|  | #include "Poco/JSON/Parser.h" | ||||||
|  | #include "Poco/Net/HTMLForm.h" | ||||||
|  | #include "RESTAPI_server.h" | ||||||
|  | #include "Daemon.h" | ||||||
|  |  | ||||||
|  | namespace OpenWifi { | ||||||
|  |     void RESTAPI_action_links::DoGet() { | ||||||
|  |  | ||||||
|  |         auto Action = GetParameter("action",""); | ||||||
|  |         auto Id = GetParameter("id",""); | ||||||
|  |  | ||||||
|  |         if(Action=="password_reset") | ||||||
|  |             RequestResetPassword(Id); | ||||||
|  |         else if(Action=="email_verification") | ||||||
|  |             DoEmailVerification(Id); | ||||||
|  |         else | ||||||
|  |             DoReturnA404(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     void RESTAPI_action_links::DoPost() { | ||||||
|  |         auto Action = GetParameter("action",""); | ||||||
|  |         auto Id = GetParameter("id",""); | ||||||
|  |  | ||||||
|  |         Logger_.information(Poco::format("COMPLETE-PASSWORD-RESET(%s): For ID=%s", Request->clientAddress().toString(), Id)); | ||||||
|  |         if(Action=="password_reset") | ||||||
|  |             CompleteResetPassword(Id); | ||||||
|  |         else | ||||||
|  |             DoReturnA404(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     void RESTAPI_action_links::RequestResetPassword(std::string &Id) { | ||||||
|  |         Logger_.information(Poco::format("REQUEST-PASSWORD-RESET(%s): For ID=%s", Request->clientAddress().toString(), Id)); | ||||||
|  |         Poco::File  FormFile{ RESTAPI_Server()->AssetDir() + "/password_reset.html"}; | ||||||
|  |         Types::StringPairVec    FormVars{ {"UUID", Id}, | ||||||
|  |                                           {"PASSWORD_VALIDATION", AuthService()->PasswordValidationExpression()}}; | ||||||
|  |         SendHTMLFileBack(FormFile,FormVars); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     void RESTAPI_action_links::CompleteResetPassword(std::string &Id) { | ||||||
|  |         //  form has been posted... | ||||||
|  |         RESTAPI_PartHandler PartHandler; | ||||||
|  |         Poco::Net::HTMLForm Form(*Request, Request->stream(), PartHandler); | ||||||
|  |         if (!Form.empty()) { | ||||||
|  |             auto Password1 = Form.get("password1","bla"); | ||||||
|  |             auto Password2 = Form.get("password1","blu"); | ||||||
|  |             Id = Form.get("id",""); | ||||||
|  |             if(Password1!=Password2 || !AuthService()->ValidatePassword(Password2) || !AuthService()->ValidatePassword(Password1)) { | ||||||
|  |                 Poco::File  FormFile{ RESTAPI_Server()->AssetDir() + "/password_reset_error.html"}; | ||||||
|  |                 Types::StringPairVec    FormVars{ {"UUID", Id}, | ||||||
|  |                                                   {"ERROR_TEXT", "For some reason, the passwords entered do not match or they do not comply with" | ||||||
|  |                                                                  " accepted password creation restrictions. Please consult our on-line help" | ||||||
|  |                                                                  " to look at the our password policy. If you would like to contact us, please mention" | ||||||
|  |                                                                  " id(" + Id + ")"}}; | ||||||
|  |                 SendHTMLFileBack(FormFile,FormVars); | ||||||
|  |                 return; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             SecurityObjects::UserInfo   UInfo; | ||||||
|  |             if(!Storage()->GetUserById(Id,UInfo)) { | ||||||
|  |                 Poco::File  FormFile{ RESTAPI_Server()->AssetDir() + "/password_reset_error.html"}; | ||||||
|  |                 Types::StringPairVec    FormVars{ {"UUID", Id}, | ||||||
|  |                                                   {"ERROR_TEXT", "This request does not contain a valid user ID. Please contact your system administrator."}}; | ||||||
|  |                 SendHTMLFileBack(FormFile,FormVars); | ||||||
|  |                 return; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             if(UInfo.blackListed || UInfo.suspended) { | ||||||
|  |                 Poco::File  FormFile{ RESTAPI_Server()->AssetDir() + "/password_reset_error.html"}; | ||||||
|  |                 Types::StringPairVec    FormVars{ {"UUID", Id}, | ||||||
|  |                                                   {"ERROR_TEXT", "Please contact our system administrators. We have identified an error in your account that must be resolved first."}}; | ||||||
|  |                 SendHTMLFileBack(FormFile,FormVars); | ||||||
|  |                 return; | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             if(!AuthService()->SetPassword(Password1,UInfo)) { | ||||||
|  |                 Poco::File  FormFile{ RESTAPI_Server()->AssetDir() + "/password_reset_error.html"}; | ||||||
|  |                 Types::StringPairVec    FormVars{ {"UUID", Id}, | ||||||
|  |                                                   {"ERROR_TEXT", "You cannot reuse one of your recent passwords."}}; | ||||||
|  |                 SendHTMLFileBack(FormFile,FormVars); | ||||||
|  |                 return; | ||||||
|  |             } | ||||||
|  |             Storage()->UpdateUserInfo(UInfo.email,Id,UInfo); | ||||||
|  |             Poco::File  FormFile{ RESTAPI_Server()->AssetDir() + "/password_reset_success.html"}; | ||||||
|  |             Types::StringPairVec    FormVars{ {"UUID", Id}, | ||||||
|  |                                               {"USERNAME", UInfo.email}, | ||||||
|  |                                               {"ACTION_LINK",Daemon()->GetUIURI()}}; | ||||||
|  |             SendHTMLFileBack(FormFile,FormVars); | ||||||
|  |         } else { | ||||||
|  |             DoReturnA404(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     void RESTAPI_action_links::DoEmailVerification(std::string &Id) { | ||||||
|  |         SecurityObjects::UserInfo UInfo; | ||||||
|  |  | ||||||
|  |         Logger_.information(Poco::format("EMAIL-VERIFICATION(%s): For ID=%s", Request->clientAddress().toString(), Id)); | ||||||
|  |         if (!Storage()->GetUserById(Id, UInfo)) { | ||||||
|  |             Types::StringPairVec FormVars{{"UUID",       Id}, | ||||||
|  |                                           {"ERROR_TEXT", "This does not appear to be a valid email verification link.."}}; | ||||||
|  |             Poco::File FormFile{RESTAPI_Server()->AssetDir() + "/email_verification_error.html"}; | ||||||
|  |             SendHTMLFileBack(FormFile, FormVars); | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         UInfo.waitingForEmailCheck = false; | ||||||
|  |         UInfo.validated = true; | ||||||
|  |         UInfo.lastEmailCheck = std::time(nullptr); | ||||||
|  |         UInfo.validationDate = std::time(nullptr); | ||||||
|  |         Storage()->UpdateUserInfo(UInfo.email, Id, UInfo); | ||||||
|  |         Types::StringPairVec FormVars{{"UUID",     Id}, | ||||||
|  |                                       {"USERNAME", UInfo.email}, | ||||||
|  |                                       {"ACTION_LINK",Daemon()->GetUIURI()}}; | ||||||
|  |         Poco::File FormFile{RESTAPI_Server()->AssetDir() + "/email_verification_success.html"}; | ||||||
|  |         SendHTMLFileBack(FormFile, FormVars); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     void RESTAPI_action_links::DoReturnA404() { | ||||||
|  |         Types::StringPairVec FormVars; | ||||||
|  |         Poco::File FormFile{RESTAPI_Server()->AssetDir() + "/404_error.html"}; | ||||||
|  |         SendHTMLFileBack(FormFile, FormVars); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | } | ||||||
							
								
								
									
										43
									
								
								src/RESTAPI_action_links.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								src/RESTAPI_action_links.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,43 @@ | |||||||
|  | // | ||||||
|  | // Created by stephane bourque on 2021-06-22. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef UCENTRALSEC_RESTAPI_ACTION_LINKS_H | ||||||
|  | #define UCENTRALSEC_RESTAPI_ACTION_LINKS_H | ||||||
|  |  | ||||||
|  |  | ||||||
|  | #include "RESTAPI_handler.h" | ||||||
|  | #include "Poco/Net/PartHandler.h" | ||||||
|  | #include "Poco/Message.h" | ||||||
|  | #include "Poco/Net/MessageHeader.h" | ||||||
|  | #include "Poco/Net/NameValueCollection.h" | ||||||
|  | #include "Poco/NullStream.h" | ||||||
|  | #include "Poco/StreamCopier.h" | ||||||
|  | #include "Poco/CountingStream.h" | ||||||
|  |  | ||||||
|  | namespace OpenWifi { | ||||||
|  |     class RESTAPI_action_links : public RESTAPIHandler { | ||||||
|  |     public: | ||||||
|  |         RESTAPI_action_links(const RESTAPIHandler::BindingMap &bindings, Poco::Logger &L, RESTAPI_GenericServer &Server, bool Internal) | ||||||
|  |                 : RESTAPIHandler(bindings, L, | ||||||
|  |              std::vector<std::string>{ | ||||||
|  |                                         Poco::Net::HTTPRequest::HTTP_GET, | ||||||
|  |                                         Poco::Net::HTTPRequest::HTTP_POST, | ||||||
|  |                                         Poco::Net::HTTPRequest::HTTP_OPTIONS}, | ||||||
|  |                                         Server, | ||||||
|  |                                         Internal, | ||||||
|  |                                         false) {} | ||||||
|  |         static const std::list<const char *> PathName() { return std::list<const char *>{"/api/v1/actionLink"}; }; | ||||||
|  |         void RequestResetPassword(std::string &Id); | ||||||
|  |         void CompleteResetPassword(std::string &Id); | ||||||
|  |         void DoEmailVerification(std::string &Id); | ||||||
|  |         void DoReturnA404(); | ||||||
|  |  | ||||||
|  |         void DoGet() final; | ||||||
|  |         void DoPost() final; | ||||||
|  |         void DoDelete() final {}; | ||||||
|  |         void DoPut() final {}; | ||||||
|  |     }; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #endif //UCENTRALSEC_RESTAPI_ACTION_LINKS_H | ||||||
							
								
								
									
										89
									
								
								src/RESTAPI_avatarHandler.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										89
									
								
								src/RESTAPI_avatarHandler.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,89 @@ | |||||||
|  | // | ||||||
|  | // Created by stephane bourque on 2021-07-15. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #include <fstream> | ||||||
|  | #include <iostream> | ||||||
|  |  | ||||||
|  | #include "RESTAPI_avatarHandler.h" | ||||||
|  | #include "StorageService.h" | ||||||
|  | #include "Daemon.h" | ||||||
|  | #include "Poco/Net/HTMLForm.h" | ||||||
|  | #include "Utils.h" | ||||||
|  | #include "RESTAPI_protocol.h" | ||||||
|  |  | ||||||
|  | namespace OpenWifi { | ||||||
|  |  | ||||||
|  |     void AvatarPartHandler::handlePart(const Poco::Net::MessageHeader &Header, std::istream &Stream) { | ||||||
|  |         FileType_ = Header.get(RESTAPI::Protocol::CONTENTTYPE, RESTAPI::Protocol::UNSPECIFIED); | ||||||
|  |         if (Header.has(RESTAPI::Protocol::CONTENTDISPOSITION)) { | ||||||
|  |             std::string Disposition; | ||||||
|  |             Poco::Net::NameValueCollection Parameters; | ||||||
|  |             Poco::Net::MessageHeader::splitParameters(Header[RESTAPI::Protocol::CONTENTDISPOSITION], Disposition, Parameters); | ||||||
|  |             Name_ = Parameters.get(RESTAPI::Protocol::NAME, RESTAPI::Protocol::UNNAMED); | ||||||
|  |         } | ||||||
|  |         Poco::CountingInputStream InputStream(Stream); | ||||||
|  |         std::ofstream OutputStream(TempFile_.path(), std::ofstream::out); | ||||||
|  |         Poco::StreamCopier::copyStream(InputStream, OutputStream); | ||||||
|  |         Length_ = InputStream.chars(); | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     void RESTAPI_avatarHandler::DoPost() { | ||||||
|  |         std::string Id = GetBinding(RESTAPI::Protocol::ID, ""); | ||||||
|  |         SecurityObjects::UserInfo UInfo; | ||||||
|  |  | ||||||
|  |         if (Id.empty() || !Storage()->GetUserById(Id, UInfo)) { | ||||||
|  |             NotFound(); | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         //  if there is an avatar, just remove it... | ||||||
|  |         Storage()->DeleteAvatar(UserInfo_.userinfo.email,Id); | ||||||
|  |  | ||||||
|  |         Poco::TemporaryFile TmpFile; | ||||||
|  |         AvatarPartHandler partHandler(Id, Logger_, TmpFile); | ||||||
|  |  | ||||||
|  |         Poco::Net::HTMLForm form(*Request, Request->stream(), partHandler); | ||||||
|  |         Poco::JSON::Object Answer; | ||||||
|  |         if (!partHandler.Name().empty() && partHandler.Length()<Daemon()->ConfigGetInt("openwifi.avatar.maxsize",2000000)) { | ||||||
|  |             Answer.set(RESTAPI::Protocol::AVATARID, Id); | ||||||
|  |             Answer.set(RESTAPI::Protocol::ERRORCODE, 0); | ||||||
|  |             Logger_.information(Poco::format("Uploaded avatar: %s Type: %s", partHandler.Name(), partHandler.ContentType())); | ||||||
|  |             Storage()->SetAvatar(UserInfo_.userinfo.email, | ||||||
|  |                                  Id, TmpFile, partHandler.ContentType(), partHandler.Name()); | ||||||
|  |         } else { | ||||||
|  |             Answer.set(RESTAPI::Protocol::AVATARID, Id); | ||||||
|  |             Answer.set(RESTAPI::Protocol::ERRORCODE, 13); | ||||||
|  |             Answer.set(RESTAPI::Protocol::ERRORTEXT, "Avatar upload could not complete."); | ||||||
|  |         } | ||||||
|  |         ReturnObject(Answer); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     void RESTAPI_avatarHandler::DoGet() { | ||||||
|  |         std::string Id = GetBinding(RESTAPI::Protocol::ID, ""); | ||||||
|  |         if (Id.empty()) { | ||||||
|  |             NotFound(); | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |         Poco::TemporaryFile TempAvatar; | ||||||
|  |         std::string Type, Name; | ||||||
|  |         if (!Storage()->GetAvatar(UserInfo_.userinfo.email, Id, TempAvatar, Type, Name)) { | ||||||
|  |             NotFound(); | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |         SendFile(TempAvatar, Type, Name); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     void RESTAPI_avatarHandler::DoDelete() { | ||||||
|  |         std::string Id = GetBinding(RESTAPI::Protocol::ID, ""); | ||||||
|  |         if (Id.empty()) { | ||||||
|  |             NotFound(); | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |         if (!Storage()->DeleteAvatar(UserInfo_.userinfo.email, Id)) { | ||||||
|  |             NotFound(); | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |         OK(); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -1,38 +1,39 @@ | |||||||
| //
 | //
 | ||||||
| // Created by stephane bourque on 2021-07-15.
 | // Created by stephane bourque on 2021-07-15.
 | ||||||
| //
 | //
 | ||||||
| #pragma once |  | ||||||
| 
 | 
 | ||||||
| #include "framework/MicroService.h" | #ifndef UCENTRALSEC_RESTAPI_AVATARHANDLER_H | ||||||
|  | #define UCENTRALSEC_RESTAPI_AVATARHANDLER_H | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | #include "RESTAPI_handler.h" | ||||||
| 
 | 
 | ||||||
| namespace OpenWifi { | namespace OpenWifi { | ||||||
| 
 | 
 | ||||||
|     class AvatarPartHandler : public Poco::Net::PartHandler { |     class AvatarPartHandler : public Poco::Net::PartHandler { | ||||||
|     public: |     public: | ||||||
|         AvatarPartHandler(std::string Id, Poco::Logger &Logger, std::stringstream & ofs) : |         AvatarPartHandler(std::string Id, Poco::Logger &Logger, Poco::TemporaryFile &TmpFile) : | ||||||
|                 Id_(std::move(Id)), |                 Id_(std::move(Id)), | ||||||
|                 Logger_(Logger), |                 Logger_(Logger), | ||||||
|                 OutputStream_(ofs){ |                 TempFile_(TmpFile){ | ||||||
|         } |         } | ||||||
|         void handlePart(const Poco::Net::MessageHeader &Header, std::istream &Stream); |         void handlePart(const Poco::Net::MessageHeader &Header, std::istream &Stream); | ||||||
|         [[nodiscard]] uint64_t Length() const { return Length_; } |         [[nodiscard]] uint64_t Length() const { return Length_; } | ||||||
|         [[nodiscard]] std::string &Name() { return Name_; } |         [[nodiscard]] std::string &Name() { return Name_; } | ||||||
|         [[nodiscard]] std::string &ContentType() { return FileType_; } |         [[nodiscard]] std::string &ContentType() { return FileType_; } | ||||||
| 
 |         [[nodiscard]] std::string FileName() const { return TempFile_.path(); } | ||||||
|     private: |     private: | ||||||
|         uint64_t        Length_ = 0; |         uint64_t        Length_ = 0; | ||||||
|         std::string     FileType_; |         std::string     FileType_; | ||||||
|         std::string     Name_; |         std::string     Name_; | ||||||
|         std::string     Id_; |         std::string     Id_; | ||||||
|         Poco::Logger    &Logger_; |         Poco::Logger    &Logger_; | ||||||
|         std::stringstream &OutputStream_; |         Poco::TemporaryFile &TempFile_; | ||||||
| 
 |  | ||||||
|         inline Poco::Logger & Logger() { return Logger_; }; |  | ||||||
|     }; |     }; | ||||||
| 
 | 
 | ||||||
|     class RESTAPI_avatar_handler : public RESTAPIHandler { |     class RESTAPI_avatarHandler : public RESTAPIHandler { | ||||||
|     public: |     public: | ||||||
|         RESTAPI_avatar_handler(const RESTAPIHandler::BindingMap &bindings, Poco::Logger &L, RESTAPI_GenericServer &Server, uint64_t TransactionId, bool Internal) |         RESTAPI_avatarHandler(const RESTAPIHandler::BindingMap &bindings, Poco::Logger &L, RESTAPI_GenericServer &Server, bool Internal) | ||||||
|                 : RESTAPIHandler(bindings, L, |                 : RESTAPIHandler(bindings, L, | ||||||
|                                  std::vector<std::string>{ |                                  std::vector<std::string>{ | ||||||
|                                          Poco::Net::HTTPRequest::HTTP_GET, |                                          Poco::Net::HTTPRequest::HTTP_GET, | ||||||
| @@ -40,9 +41,8 @@ namespace OpenWifi { | |||||||
|                                          Poco::Net::HTTPRequest::HTTP_DELETE, |                                          Poco::Net::HTTPRequest::HTTP_DELETE, | ||||||
|                                          Poco::Net::HTTPRequest::HTTP_OPTIONS}, |                                          Poco::Net::HTTPRequest::HTTP_OPTIONS}, | ||||||
|                                          Server, |                                          Server, | ||||||
|                                          TransactionId, |  | ||||||
|                                          Internal) {} |                                          Internal) {} | ||||||
|         static auto PathName() { return std::list<std::string>{"/api/v1/avatar/{id}"}; }; |         static const std::list<const char *> PathName() { return std::list<const char *>{"/api/v1/avatar/{id}"}; }; | ||||||
| 
 | 
 | ||||||
|         void DoGet() final; |         void DoGet() final; | ||||||
|         void DoPost() final; |         void DoPost() final; | ||||||
| @@ -51,3 +51,4 @@ namespace OpenWifi { | |||||||
| 
 | 
 | ||||||
|     }; |     }; | ||||||
| } | } | ||||||
|  | #endif //UCENTRALSEC_RESTAPI_AVATARHANDLER_H
 | ||||||
							
								
								
									
										36
									
								
								src/RESTAPI_email_handler.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								src/RESTAPI_email_handler.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,36 @@ | |||||||
|  | // | ||||||
|  | // Created by stephane bourque on 2021-09-02. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #include "RESTAPI_email_handler.h" | ||||||
|  |  | ||||||
|  |  | ||||||
|  | #include "Poco/Exception.h" | ||||||
|  | #include "Poco/JSON/Parser.h" | ||||||
|  |  | ||||||
|  | #include "Daemon.h" | ||||||
|  | #include "SMTPMailerService.h" | ||||||
|  | #include "RESTAPI_errors.h" | ||||||
|  |  | ||||||
|  | namespace OpenWifi { | ||||||
|  |     void RESTAPI_email_handler::DoPost() { | ||||||
|  |         auto Obj = ParseStream(); | ||||||
|  |         if (Obj->has("subject") && | ||||||
|  |             Obj->has("from") && | ||||||
|  |             Obj->has("text") && | ||||||
|  |             Obj->has("recipients")) { | ||||||
|  |             auto   Recipients = Obj->getArray("recipients"); | ||||||
|  |             MessageAttributes Attrs; | ||||||
|  |             Attrs[RECIPIENT_EMAIL] = Recipients->get(0).toString(); | ||||||
|  |             Attrs[SUBJECT] = Obj->get("subject").toString(); | ||||||
|  |             Attrs[TEXT] = Obj->get("text").toString(); | ||||||
|  |             if(SMTPMailerService()->SendMessage(Recipients->get(0).toString(), "password_reset.txt", Attrs)) { | ||||||
|  |                 OK(); | ||||||
|  |                 return; | ||||||
|  |             } | ||||||
|  |             ReturnStatus(Poco::Net::HTTPResponse::HTTP_SERVICE_UNAVAILABLE); | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |         BadRequest(RESTAPI::Errors::MissingOrInvalidParameters); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -2,24 +2,27 @@ | |||||||
| // Created by stephane bourque on 2021-09-02.
 | // Created by stephane bourque on 2021-09-02.
 | ||||||
| //
 | //
 | ||||||
| 
 | 
 | ||||||
| #pragma once | #ifndef OWSEC_RESTAPI_EMAIL_HANDLER_H | ||||||
|  | #define OWSEC_RESTAPI_EMAIL_HANDLER_H | ||||||
| 
 | 
 | ||||||
| #include "framework/MicroService.h" | 
 | ||||||
|  | #include "RESTAPI_handler.h" | ||||||
| 
 | 
 | ||||||
| namespace OpenWifi { | namespace OpenWifi { | ||||||
|     class RESTAPI_email_handler : public RESTAPIHandler { |     class RESTAPI_email_handler : public RESTAPIHandler { | ||||||
|     public: |     public: | ||||||
|         RESTAPI_email_handler(const RESTAPIHandler::BindingMap &bindings, Poco::Logger &L, RESTAPI_GenericServer &Server, uint64_t TransactionId, bool Internal) |         RESTAPI_email_handler(const RESTAPIHandler::BindingMap &bindings, Poco::Logger &L, RESTAPI_GenericServer &Server, bool Internal) | ||||||
|         : RESTAPIHandler(bindings, L, |         : RESTAPIHandler(bindings, L, | ||||||
|                          std::vector<std::string>{Poco::Net::HTTPRequest::HTTP_POST, |                          std::vector<std::string>{Poco::Net::HTTPRequest::HTTP_POST, | ||||||
|                                                   Poco::Net::HTTPRequest::HTTP_OPTIONS}, |                                                   Poco::Net::HTTPRequest::HTTP_OPTIONS}, | ||||||
|                                                   Server, |                                                   Server, | ||||||
|                                                   TransactionId, |  | ||||||
|                                                   Internal) {} |                                                   Internal) {} | ||||||
|         static auto PathName() { return std::list<std::string>{"/api/v1/email"};} |         static const std::list<const char *> PathName() { return std::list<const char *>{"/api/v1/email"};} | ||||||
|         void DoGet() final {}; |         void DoGet() final {}; | ||||||
|         void DoPost() final; |         void DoPost() final; | ||||||
|         void DoDelete() final {}; |         void DoDelete() final {}; | ||||||
|         void DoPut() final {}; |         void DoPut() final {}; | ||||||
|     }; |     }; | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | #endif //OWSEC_RESTAPI_EMAIL_HANDLER_H
 | ||||||
							
								
								
									
										55
									
								
								src/RESTAPI_errors.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								src/RESTAPI_errors.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,55 @@ | |||||||
|  | // | ||||||
|  | // Created by stephane bourque on 2021-09-12. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef OWPROV_RESTAPI_ERRORS_H | ||||||
|  | #define OWPROV_RESTAPI_ERRORS_H | ||||||
|  |  | ||||||
|  | namespace OpenWifi::RESTAPI::Errors { | ||||||
|  |     static const std::string MissingUUID{"Missing UUID."}; | ||||||
|  |     static const std::string MissingSerialNumber{"Missing Serial Number."}; | ||||||
|  |     static const std::string InternalError{"Internal error. Please try later."}; | ||||||
|  |     static const std::string InvalidJSONDocument{"Invalid JSON document."}; | ||||||
|  |     static const std::string UnsupportedHTTPMethod{"Unsupported HTTP Method"}; | ||||||
|  |     static const std::string StillInUse{"Element still in use."}; | ||||||
|  |     static const std::string CouldNotBeDeleted{"Element could not be deleted."}; | ||||||
|  |     static const std::string NameMustBeSet{"The name property must be set."}; | ||||||
|  |     static const std::string ConfigBlockInvalid{"Configuration block type invalid."}; | ||||||
|  |     static const std::string UnknownId{"Unknown management policy."}; | ||||||
|  |     static const std::string InvalidDeviceTypes{"Unknown or invalid device type(s)."}; | ||||||
|  |     static const std::string RecordNotCreated{"Record could not be created."}; | ||||||
|  |     static const std::string RecordNotUpdated{"Record could not be updated."}; | ||||||
|  |     static const std::string UnknownManagementPolicyUUID{"Unknown management policy UUID."}; | ||||||
|  |     static const std::string CannotDeleteRoot{"Root Entity cannot be removed, only modified."}; | ||||||
|  |     static const std::string MustCreateRootFirst{"Root entity must be created first."}; | ||||||
|  |     static const std::string ParentUUIDMustExist{"Parent UUID must exist."}; | ||||||
|  |     static const std::string ConfigurationMustExist{"Configuration must exist."}; | ||||||
|  |     static const std::string MissingOrInvalidParameters{"Invalid or missing parameters."}; | ||||||
|  |     static const std::string UnknownSerialNumber{"Unknown Serial Number."}; | ||||||
|  |     static const std::string InvalidSerialNumber{"Invalid Serial Number."}; | ||||||
|  |     static const std::string SerialNumberExists{"Serial Number already exists."}; | ||||||
|  |     static const std::string ValidNonRootUUID{"Must be a non-root, and valid UUID."}; | ||||||
|  |     static const std::string VenueMustExist{"Venue does not exist."}; | ||||||
|  |     static const std::string NotBoth{"You cannot specify both Entity and Venue"}; | ||||||
|  |     static const std::string EntityMustExist{"Entity must exist."}; | ||||||
|  |     static const std::string ParentOrEntityMustBeSet{"Parent or Entity must be set."}; | ||||||
|  |     static const std::string ContactMustExist{"Contact must exist."}; | ||||||
|  |     static const std::string LocationMustExist{"Location must exist."}; | ||||||
|  |     static const std::string OnlyWSSupported{"This endpoint only supports WebSocket."}; | ||||||
|  |     static const std::string SerialNumberMismatch{"Serial Number mismatch."}; | ||||||
|  |     static const std::string InvalidCommand{"Invalid command."}; | ||||||
|  |     static const std::string NoRecordsDeleted{"No records deleted."}; | ||||||
|  |     static const std::string DeviceNotConnected{"Device is not currently connected."}; | ||||||
|  |     static const std::string CannotCreateWS{"Telemetry system could not create WS endpoint. Please try again."}; | ||||||
|  |     static const std::string BothDeviceTypeRevision{"Both deviceType and revision must be set."}; | ||||||
|  |     static const std::string IdOrSerialEmpty{"SerialNumber and Id must not be empty."}; | ||||||
|  |     static const std::string MissingUserID{"Missing user ID."}; | ||||||
|  |     static const std::string IdMustBe0{"To create a user, you must set the ID to 0"}; | ||||||
|  |     static const std::string InvalidUserRole{"Invalid userRole."}; | ||||||
|  |     static const std::string InvalidEmailAddress{"Invalid email address."}; | ||||||
|  |     static const std::string InvalidPassword{"Invalid password."}; | ||||||
|  |     static const std::string PasswordRejected{"Password was rejected. This maybe an old password."}; | ||||||
|  |     static const std::string InvalidIPRanges{"Invalid IP range specifications."}; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #endif //OWPROV_RESTAPI_ERRORS_H | ||||||
							
								
								
									
										479
									
								
								src/RESTAPI_handler.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										479
									
								
								src/RESTAPI_handler.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,479 @@ | |||||||
|  | // | ||||||
|  | //	License type: BSD 3-Clause License | ||||||
|  | //	License copy: https://github.com/Telecominfraproject/wlan-cloud-ucentralgw/blob/master/LICENSE | ||||||
|  | // | ||||||
|  | //	Created by Stephane Bourque on 2021-03-04. | ||||||
|  | //	Arilia Wireless Inc. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #include <cctype> | ||||||
|  | #include <algorithm> | ||||||
|  | #include <functional> | ||||||
|  | #include <iostream> | ||||||
|  | #include <iterator> | ||||||
|  | #include <future> | ||||||
|  | #include <chrono> | ||||||
|  |  | ||||||
|  | #include "Poco/URI.h" | ||||||
|  | #include "Poco/Net/OAuth20Credentials.h" | ||||||
|  |  | ||||||
|  | #include "RESTAPI_errors.h" | ||||||
|  |  | ||||||
|  | #ifdef	TIP_SECURITY_SERVICE | ||||||
|  | #include "AuthService.h" | ||||||
|  | #else | ||||||
|  | #include "AuthClient.h" | ||||||
|  | #endif | ||||||
|  |  | ||||||
|  | #include "RESTAPI_handler.h" | ||||||
|  | #include "RESTAPI_protocol.h" | ||||||
|  | #include "Utils.h" | ||||||
|  | #include "Daemon.h" | ||||||
|  |  | ||||||
|  | namespace OpenWifi { | ||||||
|  |  | ||||||
|  |     void RESTAPIHandler::handleRequest(Poco::Net::HTTPServerRequest &RequestIn, | ||||||
|  |                        Poco::Net::HTTPServerResponse &ResponseIn) { | ||||||
|  | 		try { | ||||||
|  | 			Request = &RequestIn; | ||||||
|  | 			Response = &ResponseIn; | ||||||
|  |  | ||||||
|  | 			if (!ContinueProcessing()) | ||||||
|  | 				return; | ||||||
|  |  | ||||||
|  | 			if (AlwaysAuthorize_ && !IsAuthorized()) | ||||||
|  | 				return; | ||||||
|  |  | ||||||
|  | 			ParseParameters(); | ||||||
|  | 			if (Request->getMethod() == Poco::Net::HTTPRequest::HTTP_GET) | ||||||
|  | 				DoGet(); | ||||||
|  | 			else if (Request->getMethod() == Poco::Net::HTTPRequest::HTTP_POST) | ||||||
|  | 				DoPost(); | ||||||
|  | 			else if (Request->getMethod() == Poco::Net::HTTPRequest::HTTP_DELETE) | ||||||
|  | 				DoDelete(); | ||||||
|  | 			else if (Request->getMethod() == Poco::Net::HTTPRequest::HTTP_PUT) | ||||||
|  | 				DoPut(); | ||||||
|  | 			else | ||||||
|  | 				BadRequest(RESTAPI::Errors::UnsupportedHTTPMethod); | ||||||
|  | 			return; | ||||||
|  | 		} catch (const Poco::Exception &E) { | ||||||
|  | 			Logger_.log(E); | ||||||
|  | 			BadRequest(RESTAPI::Errors::InternalError); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  |     const Poco::JSON::Object::Ptr &RESTAPIHandler::ParseStream() { | ||||||
|  |         return IncomingParser_.parse(Request->stream()).extract<Poco::JSON::Object::Ptr>(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | 	bool RESTAPIHandler::ParseBindings(const std::string & Request, const std::list<const char *> & EndPoints, BindingMap &bindings) { | ||||||
|  | 		bindings.clear(); | ||||||
|  | 		std::vector<std::string> PathItems = Utils::Split(Request, '/'); | ||||||
|  |  | ||||||
|  | 		for(const auto &EndPoint:EndPoints) { | ||||||
|  | 			std::vector<std::string> ParamItems = Utils::Split(EndPoint, '/'); | ||||||
|  | 			if (PathItems.size() != ParamItems.size()) | ||||||
|  | 				continue; | ||||||
|  |  | ||||||
|  | 			bool Matched = true; | ||||||
|  | 			for (auto i = 0; i != PathItems.size() && Matched; i++) { | ||||||
|  | 				if (PathItems[i] != ParamItems[i]) { | ||||||
|  | 					if (ParamItems[i][0] == '{') { | ||||||
|  | 						auto ParamName = ParamItems[i].substr(1, ParamItems[i].size() - 2); | ||||||
|  | 						bindings[Poco::toLower(ParamName)] = PathItems[i]; | ||||||
|  | 					} else { | ||||||
|  | 						Matched = false; | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  | 			} | ||||||
|  | 			if(Matched) | ||||||
|  | 				return true; | ||||||
|  | 		} | ||||||
|  | 		return false; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	void RESTAPIHandler::PrintBindings() { | ||||||
|  | 		for (const auto &[key, value] : Bindings_) | ||||||
|  | 			std::cout << "Key = " << key << "  Value= " << value << std::endl; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	void RESTAPIHandler::ParseParameters() { | ||||||
|  | 		Poco::URI uri(Request->getURI()); | ||||||
|  | 		Parameters_ = uri.getQueryParameters(); | ||||||
|  | 		InitQueryBlock(); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	static bool is_number(const std::string &s) { | ||||||
|  | 		return !s.empty() && std::all_of(s.begin(), s.end(), ::isdigit); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	static bool is_bool(const std::string &s) { | ||||||
|  | 		if (s == "true" || s == "false") | ||||||
|  | 			return true; | ||||||
|  | 		return false; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	uint64_t RESTAPIHandler::GetParameter(const std::string &Name, const uint64_t Default) { | ||||||
|  |         auto Hint = std::find_if(Parameters_.begin(),Parameters_.end(),[Name](const std::pair<std::string,std::string> &S){ return S.first==Name; }); | ||||||
|  |         if(Hint==Parameters_.end() || !is_number(Hint->second)) | ||||||
|  |             return Default; | ||||||
|  |         return std::stoull(Hint->second); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	bool RESTAPIHandler::GetBoolParameter(const std::string &Name, bool Default) { | ||||||
|  |         auto Hint = std::find_if(begin(Parameters_),end(Parameters_),[Name](const std::pair<std::string,std::string> &S){ return S.first==Name; }); | ||||||
|  |         if(Hint==end(Parameters_) || !is_bool(Hint->second)) | ||||||
|  |             return Default; | ||||||
|  | 		return Hint->second=="true"; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	std::string RESTAPIHandler::GetParameter(const std::string &Name, const std::string &Default) { | ||||||
|  |         auto Hint = std::find_if(begin(Parameters_),end(Parameters_),[Name](const std::pair<std::string,std::string> &S){ return S.first==Name; }); | ||||||
|  |         if(Hint==end(Parameters_)) | ||||||
|  |             return Default; | ||||||
|  |         return Hint->second; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	bool RESTAPIHandler::HasParameter(const std::string &Name, std::string &Value) { | ||||||
|  |         auto Hint = std::find_if(begin(Parameters_),end(Parameters_),[Name](const std::pair<std::string,std::string> &S){ return S.first==Name; }); | ||||||
|  |         if(Hint==end(Parameters_)) | ||||||
|  |             return false; | ||||||
|  |         Value = Hint->second; | ||||||
|  |         return true; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	bool RESTAPIHandler::HasParameter(const std::string &Name, uint64_t & Value) { | ||||||
|  |         auto Hint = std::find_if(begin(Parameters_),end(Parameters_),[Name](const std::pair<std::string,std::string> &S){ return S.first==Name; }); | ||||||
|  |         if(Hint==end(Parameters_)) | ||||||
|  |             return false; | ||||||
|  |         Value = std::stoull(Hint->second); | ||||||
|  |         return true; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	const std::string &RESTAPIHandler::GetBinding(const std::string &Name, const std::string &Default) { | ||||||
|  | 		auto E = Bindings_.find(Poco::toLower(Name)); | ||||||
|  | 		if (E == Bindings_.end()) | ||||||
|  | 			return Default; | ||||||
|  |  | ||||||
|  | 		return E->second; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	static std::string MakeList(const std::vector<std::string> &L) { | ||||||
|  | 		std::string Return; | ||||||
|  | 		for (const auto &i : L) | ||||||
|  | 			if (Return.empty()) | ||||||
|  | 				Return = i; | ||||||
|  | 			else | ||||||
|  | 				Return += ", " + i; | ||||||
|  |  | ||||||
|  | 		return Return; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	bool RESTAPIHandler::AssignIfPresent(const Poco::JSON::Object::Ptr &O, const std::string &Field, std::string &Value) { | ||||||
|  | 	    if(O->has(Field)) { | ||||||
|  | 	        Value = O->get(Field).toString(); | ||||||
|  | 	        return true; | ||||||
|  | 	    } | ||||||
|  | 	    return false; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	bool RESTAPIHandler::AssignIfPresent(const Poco::JSON::Object::Ptr &O, const std::string &Field, uint64_t &Value) { | ||||||
|  | 	    if(O->has(Field)) { | ||||||
|  | 	        Value = O->get(Field); | ||||||
|  | 	        return true; | ||||||
|  | 	    } | ||||||
|  | 	    return false; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	void RESTAPIHandler::AddCORS() { | ||||||
|  | 		auto Origin = Request->find("Origin"); | ||||||
|  | 		if (Origin != Request->end()) { | ||||||
|  | 			Response->set("Access-Control-Allow-Origin", Origin->second); | ||||||
|  | 			Response->set("Vary", "Origin"); | ||||||
|  | 		} else { | ||||||
|  | 			Response->set("Access-Control-Allow-Origin", "*"); | ||||||
|  | 		} | ||||||
|  | 		Response->set("Access-Control-Allow-Headers", "*"); | ||||||
|  | 		Response->set("Access-Control-Allow-Methods", MakeList(Methods_)); | ||||||
|  | 		Response->set("Access-Control-Max-Age", "86400"); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	void RESTAPIHandler::SetCommonHeaders(bool CloseConnection) { | ||||||
|  | 		Response->setVersion(Poco::Net::HTTPMessage::HTTP_1_1); | ||||||
|  | 		Response->setChunkedTransferEncoding(true); | ||||||
|  | 		Response->setContentType("application/json"); | ||||||
|  | 		if(CloseConnection) { | ||||||
|  | 			Response->set("Connection", "close"); | ||||||
|  | 			Response->setKeepAlive(false); | ||||||
|  | 		} else { | ||||||
|  | 			Response->setKeepAlive(true); | ||||||
|  | 			Response->set("Connection", "Keep-Alive"); | ||||||
|  | 			Response->set("Keep-Alive", "timeout=5, max=1000"); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	void RESTAPIHandler::ProcessOptions() { | ||||||
|  | 		AddCORS(); | ||||||
|  | 		SetCommonHeaders(); | ||||||
|  | 		Response->setContentLength(0); | ||||||
|  | 		Response->set("Access-Control-Allow-Credentials", "true"); | ||||||
|  | 		Response->setStatus(Poco::Net::HTTPResponse::HTTP_OK); | ||||||
|  | 		Response->set("Vary", "Origin, Access-Control-Request-Headers, Access-Control-Request-Method"); | ||||||
|  | 		Response->send(); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	void RESTAPIHandler::PrepareResponse( Poco::Net::HTTPResponse::HTTPStatus Status, | ||||||
|  | 										 bool CloseConnection) { | ||||||
|  | 		Response->setStatus(Status); | ||||||
|  | 		AddCORS(); | ||||||
|  | 		SetCommonHeaders(CloseConnection); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	void RESTAPIHandler::BadRequest(const std::string & Reason) { | ||||||
|  | 		PrepareResponse(Poco::Net::HTTPResponse::HTTP_BAD_REQUEST); | ||||||
|  | 		Poco::JSON::Object	ErrorObject; | ||||||
|  | 		ErrorObject.set("ErrorCode",400); | ||||||
|  | 		ErrorObject.set("ErrorDetails",Request->getMethod()); | ||||||
|  | 		ErrorObject.set("ErrorDescription",Reason.empty() ? "Command is missing parameters or wrong values." : Reason) ; | ||||||
|  | 		std::ostream &Answer = Response->send(); | ||||||
|  | 		Poco::JSON::Stringifier::stringify(ErrorObject, Answer); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	void RESTAPIHandler::InternalError(const std::string & Reason) { | ||||||
|  |         PrepareResponse(Poco::Net::HTTPResponse::HTTP_INTERNAL_SERVER_ERROR); | ||||||
|  |         Poco::JSON::Object	ErrorObject; | ||||||
|  |         ErrorObject.set("ErrorCode",500); | ||||||
|  |         ErrorObject.set("ErrorDetails",Request->getMethod()); | ||||||
|  |         ErrorObject.set("ErrorDescription",Reason.empty() ? "Please try later or review the data submitted." : Reason) ; | ||||||
|  |         std::ostream &Answer = Response->send(); | ||||||
|  |         Poco::JSON::Stringifier::stringify(ErrorObject, Answer); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | 	void RESTAPIHandler::UnAuthorized(const std::string & Reason) { | ||||||
|  | 		PrepareResponse(Poco::Net::HTTPResponse::HTTP_FORBIDDEN); | ||||||
|  | 		Poco::JSON::Object	ErrorObject; | ||||||
|  | 		ErrorObject.set("ErrorCode",403); | ||||||
|  | 		ErrorObject.set("ErrorDetails",Request->getMethod()); | ||||||
|  | 		ErrorObject.set("ErrorDescription",Reason.empty() ? "No access allowed." : Reason) ; | ||||||
|  | 		std::ostream &Answer = Response->send(); | ||||||
|  | 		Poco::JSON::Stringifier::stringify(ErrorObject, Answer); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	void RESTAPIHandler::NotFound() { | ||||||
|  | 		PrepareResponse(Poco::Net::HTTPResponse::HTTP_NOT_FOUND); | ||||||
|  | 		Poco::JSON::Object	ErrorObject; | ||||||
|  | 		ErrorObject.set("ErrorCode",404); | ||||||
|  | 		ErrorObject.set("ErrorDetails",Request->getMethod()); | ||||||
|  | 		ErrorObject.set("ErrorDescription","This resource does not exist."); | ||||||
|  | 		std::ostream &Answer = Response->send(); | ||||||
|  | 		Poco::JSON::Stringifier::stringify(ErrorObject, Answer); | ||||||
|  | 		Logger_.debug(Poco::format("RES-NOTFOUND: User='%s' Method='%s' Path='%s", | ||||||
|  |                                    Utils::FormatIPv6(Request->clientAddress().toString()), | ||||||
|  |                                    Request->getMethod(), | ||||||
|  |                                    Request->getURI())); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	void RESTAPIHandler::OK() { | ||||||
|  | 		PrepareResponse(); | ||||||
|  | 		if(	Request->getMethod()==Poco::Net::HTTPRequest::HTTP_DELETE || | ||||||
|  | 			Request->getMethod()==Poco::Net::HTTPRequest::HTTP_OPTIONS) { | ||||||
|  | 			Response->send(); | ||||||
|  | 		} else { | ||||||
|  | 			Poco::JSON::Object ErrorObject; | ||||||
|  | 			ErrorObject.set("Code", 0); | ||||||
|  | 			ErrorObject.set("Operation", Request->getMethod()); | ||||||
|  | 			ErrorObject.set("Details", "Command completed."); | ||||||
|  | 			std::ostream &Answer = Response->send(); | ||||||
|  | 			Poco::JSON::Stringifier::stringify(ErrorObject, Answer); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	void RESTAPIHandler::SendFile(Poco::File & File, const std::string & UUID) { | ||||||
|  | 		Response->set("Content-Type","application/octet-stream"); | ||||||
|  | 		Response->set("Content-Disposition", "attachment; filename=" + UUID ); | ||||||
|  | 		Response->set("Content-Transfer-Encoding","binary"); | ||||||
|  | 		Response->set("Accept-Ranges", "bytes"); | ||||||
|  | 		Response->set("Cache-Control", "private"); | ||||||
|  | 		Response->set("Pragma", "private"); | ||||||
|  | 		Response->set("Expires", "Mon, 26 Jul 2027 05:00:00 GMT"); | ||||||
|  | 		Response->set("Content-Length", std::to_string(File.getSize())); | ||||||
|  | 		AddCORS(); | ||||||
|  | 		Response->sendFile(File.path(),"application/octet-stream"); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  |     void RESTAPIHandler::SendFile(Poco::File & File) { | ||||||
|  |         Poco::Path  P(File.path()); | ||||||
|  |         auto MT = Utils::FindMediaType(File); | ||||||
|  |         if(MT.Encoding==Utils::BINARY) { | ||||||
|  |             Response->set("Content-Transfer-Encoding","binary"); | ||||||
|  |             Response->set("Accept-Ranges", "bytes"); | ||||||
|  |         } | ||||||
|  |         Response->set("Cache-Control", "private"); | ||||||
|  |         Response->set("Pragma", "private"); | ||||||
|  |         Response->set("Expires", "Mon, 26 Jul 2027 05:00:00 GMT"); | ||||||
|  |         AddCORS(); | ||||||
|  |         Response->sendFile(File.path(),MT.ContentType); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     void RESTAPIHandler::SendFile(Poco::TemporaryFile &TempAvatar, const std::string &Type, const std::string & Name) { | ||||||
|  |         auto MT = Utils::FindMediaType(Name); | ||||||
|  |         if(MT.Encoding==Utils::BINARY) { | ||||||
|  |             Response->set("Content-Transfer-Encoding","binary"); | ||||||
|  |             Response->set("Accept-Ranges", "bytes"); | ||||||
|  |         } | ||||||
|  |         Response->set("Content-Disposition", "attachment; filename=" + Name ); | ||||||
|  |         Response->set("Accept-Ranges", "bytes"); | ||||||
|  |         Response->set("Cache-Control", "private"); | ||||||
|  |         Response->set("Pragma", "private"); | ||||||
|  |         Response->set("Expires", "Mon, 26 Jul 2027 05:00:00 GMT"); | ||||||
|  |         AddCORS(); | ||||||
|  |         Response->sendFile(TempAvatar.path(),MT.ContentType); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  |     void RESTAPIHandler::SendHTMLFileBack(Poco::File & File, | ||||||
|  |                           const Types::StringPairVec & FormVars) { | ||||||
|  |         Response->set("Pragma", "private"); | ||||||
|  |         Response->set("Expires", "Mon, 26 Jul 2027 05:00:00 GMT"); | ||||||
|  |         Response->set("Content-Length", std::to_string(File.getSize())); | ||||||
|  |         AddCORS(); | ||||||
|  |         auto FormContent = Utils::LoadFile(File.path()); | ||||||
|  |         Utils::ReplaceVariables(FormContent, FormVars); | ||||||
|  |         Response->setChunkedTransferEncoding(true); | ||||||
|  |         Response->setContentType("text/html"); | ||||||
|  |         std::ostream& ostr = Response->send(); | ||||||
|  |         ostr << FormContent; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  |     void RESTAPIHandler::ReturnStatus(Poco::Net::HTTPResponse::HTTPStatus Status, bool CloseConnection) { | ||||||
|  | 		PrepareResponse(Status, CloseConnection); | ||||||
|  | 		if(Status == Poco::Net::HTTPResponse::HTTP_NO_CONTENT) { | ||||||
|  | 			Response->setContentLength(0); | ||||||
|  | 			Response->erase("Content-Type"); | ||||||
|  | 			Response->setChunkedTransferEncoding(false); | ||||||
|  | 		} | ||||||
|  | 		Response->send(); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	bool RESTAPIHandler::ContinueProcessing() { | ||||||
|  | 		if (Request->getMethod() == Poco::Net::HTTPRequest::HTTP_OPTIONS) { | ||||||
|  | 			ProcessOptions(); | ||||||
|  | 			return false; | ||||||
|  | 		} else if (std::find(Methods_.begin(), Methods_.end(), Request->getMethod()) == Methods_.end()) { | ||||||
|  | 			BadRequest(RESTAPI::Errors::UnsupportedHTTPMethod); | ||||||
|  | 			return false; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		return true; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	bool RESTAPIHandler::IsAuthorized() { | ||||||
|  | 	    if(Internal_) { | ||||||
|  | 	        auto Allowed = Daemon()->IsValidAPIKEY(*Request); | ||||||
|  | 	        if(!Allowed) { | ||||||
|  | 	            if(Server_.LogBadTokens(false)) { | ||||||
|  | 	                Logger_.debug(Poco::format("I-REQ-DENIED(%s): Method='%s' Path='%s", | ||||||
|  |                                                Utils::FormatIPv6(Request->clientAddress().toString()), | ||||||
|  |                                                Request->getMethod(), Request->getURI())); | ||||||
|  | 	            } | ||||||
|  | 	        } else { | ||||||
|  | 	            auto Id = Request->get("X-INTERNAL-NAME", "unknown"); | ||||||
|  | 	            if(Server_.LogIt(Request->getMethod(),true)) { | ||||||
|  | 	                Logger_.debug(Poco::format("I-REQ-ALLOWED(%s): User='%s' Method='%s' Path='%s", | ||||||
|  |                                                Utils::FormatIPv6(Request->clientAddress().toString()), Id, | ||||||
|  |                                                Request->getMethod(), Request->getURI())); | ||||||
|  | 	            } | ||||||
|  | 	        } | ||||||
|  |             return Allowed; | ||||||
|  | 	    } else { | ||||||
|  |             if (SessionToken_.empty()) { | ||||||
|  |                 try { | ||||||
|  |                     Poco::Net::OAuth20Credentials Auth(*Request); | ||||||
|  |                     if (Auth.getScheme() == "Bearer") { | ||||||
|  |                         SessionToken_ = Auth.getBearerToken(); | ||||||
|  |                     } | ||||||
|  |                 } catch (const Poco::Exception &E) { | ||||||
|  |                     Logger_.log(E); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  | #ifdef    TIP_SECURITY_SERVICE | ||||||
|  |             if (AuthService()->IsAuthorized(*Request, SessionToken_, UserInfo_)) { | ||||||
|  | #else | ||||||
|  |             if (AuthClient()->IsAuthorized(*Request, SessionToken_, UserInfo_)) { | ||||||
|  | #endif | ||||||
|  |                 if(Server_.LogIt(Request->getMethod(),true)) { | ||||||
|  |                     Logger_.debug(Poco::format("X-REQ-ALLOWED(%s): User='%s@%s' Method='%s' Path='%s", | ||||||
|  |                          Utils::FormatIPv6(Request->clientAddress().toString()), | ||||||
|  |                          UserInfo_.userinfo.email, | ||||||
|  |                          Request->clientAddress().toString(), | ||||||
|  |                          Request->getMethod(), | ||||||
|  |                          Request->getURI())); | ||||||
|  |                 } | ||||||
|  |                 return true; | ||||||
|  |             } else { | ||||||
|  |                 if(Server_.LogBadTokens(true)) { | ||||||
|  |                     Logger_.debug(Poco::format("X-REQ-DENIED(%s): Method='%s' Path='%s", | ||||||
|  |                          Utils::FormatIPv6(Request->clientAddress().toString()), | ||||||
|  |                          Request->getMethod(), Request->getURI())); | ||||||
|  |                 } | ||||||
|  |                 UnAuthorized(); | ||||||
|  |             } | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	void RESTAPIHandler::ReturnObject(Poco::JSON::Object &Object) { | ||||||
|  | 		PrepareResponse(); | ||||||
|  | 		std::ostream &Answer = Response->send(); | ||||||
|  | 		Poco::JSON::Stringifier::stringify(Object, Answer); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	void RESTAPIHandler::ReturnCountOnly(uint64_t Count) { | ||||||
|  | 	    Poco::JSON::Object  Answer; | ||||||
|  | 	    Answer.set("count", Count); | ||||||
|  |         ReturnObject(Answer); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	bool RESTAPIHandler::InitQueryBlock() { | ||||||
|  | 	    if(QueryBlockInitialized_) | ||||||
|  | 	        return true; | ||||||
|  | 	    QueryBlockInitialized_=true; | ||||||
|  | 		QB_.SerialNumber = GetParameter(RESTAPI::Protocol::SERIALNUMBER, ""); | ||||||
|  | 		QB_.StartDate = GetParameter(RESTAPI::Protocol::STARTDATE, 0); | ||||||
|  | 		QB_.EndDate = GetParameter(RESTAPI::Protocol::ENDDATE, 0); | ||||||
|  | 		QB_.Offset = GetParameter(RESTAPI::Protocol::OFFSET, 1); | ||||||
|  | 		QB_.Limit = GetParameter(RESTAPI::Protocol::LIMIT, 100); | ||||||
|  | 		QB_.Filter = GetParameter(RESTAPI::Protocol::FILTER, ""); | ||||||
|  | 		QB_.Select = GetParameter(RESTAPI::Protocol::SELECT, ""); | ||||||
|  | 		QB_.Lifetime = GetBoolParameter(RESTAPI::Protocol::LIFETIME,false); | ||||||
|  | 		QB_.LogType = GetParameter(RESTAPI::Protocol::LOGTYPE,0); | ||||||
|  | 		QB_.LastOnly = GetBoolParameter(RESTAPI::Protocol::LASTONLY,false); | ||||||
|  | 		QB_.Newest = GetBoolParameter(RESTAPI::Protocol::NEWEST,false); | ||||||
|  | 		QB_.CountOnly = GetBoolParameter(RESTAPI::Protocol::COUNTONLY,false); | ||||||
|  |  | ||||||
|  | 		if(QB_.Offset<1) | ||||||
|  | 		    QB_.Offset=1; | ||||||
|  | 		return true; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	[[nodiscard]] uint64_t RESTAPIHandler::Get(const char *Parameter,const Poco::JSON::Object::Ptr &Obj, uint64_t Default){ | ||||||
|  | 		if(Obj->has(Parameter)) | ||||||
|  | 			return Obj->get(Parameter); | ||||||
|  | 		return Default; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	[[nodiscard]] std::string RESTAPIHandler::GetS(const char *Parameter,const Poco::JSON::Object::Ptr &Obj, const std::string & Default){ | ||||||
|  | 		if(Obj->has(Parameter)) | ||||||
|  | 			return Obj->get(Parameter).toString(); | ||||||
|  | 		return Default; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	[[nodiscard]] bool RESTAPIHandler::GetB(const char *Parameter,const Poco::JSON::Object::Ptr &Obj, bool Default){ | ||||||
|  | 		if(Obj->has(Parameter)) | ||||||
|  | 			return Obj->get(Parameter).toString()=="true"; | ||||||
|  | 		return Default; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	[[nodiscard]] uint64_t RESTAPIHandler::GetWhen(const Poco::JSON::Object::Ptr &Obj) { | ||||||
|  | 		return RESTAPIHandler::Get(RESTAPI::Protocol::WHEN, Obj); | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										233
									
								
								src/RESTAPI_handler.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										233
									
								
								src/RESTAPI_handler.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,233 @@ | |||||||
|  | // | ||||||
|  | //	License type: BSD 3-Clause License | ||||||
|  | //	License copy: https://github.com/Telecominfraproject/wlan-cloud-ucentralgw/blob/master/LICENSE | ||||||
|  | // | ||||||
|  | //	Created by Stephane Bourque on 2021-03-04. | ||||||
|  | //	Arilia Wireless Inc. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef UCENTRAL_RESTAPI_HANDLER_H | ||||||
|  | #define UCENTRAL_RESTAPI_HANDLER_H | ||||||
|  |  | ||||||
|  | #include "Poco/URI.h" | ||||||
|  | #include "Poco/Net/HTTPRequestHandler.h" | ||||||
|  | #include "Poco/Net/HTTPRequestHandlerFactory.h" | ||||||
|  | #include "Poco/Net/HTTPServerRequest.h" | ||||||
|  | #include "Poco/Net/HTTPServerResponse.h" | ||||||
|  | #include "Poco/Net/NetException.h" | ||||||
|  | #include "Poco/Net/PartHandler.h" | ||||||
|  |  | ||||||
|  | #include "Poco/Logger.h" | ||||||
|  | #include "Poco/File.h" | ||||||
|  | #include "Poco/TemporaryFile.h" | ||||||
|  | #include "Poco/JSON/Object.h" | ||||||
|  | #include "Poco/CountingStream.h" | ||||||
|  | #include "Poco/NullStream.h" | ||||||
|  |  | ||||||
|  | #include "RESTAPI_SecurityObjects.h" | ||||||
|  | #include "RESTAPI_utils.h" | ||||||
|  | #include "RESTAPI_GenericServer.h" | ||||||
|  |  | ||||||
|  | namespace OpenWifi { | ||||||
|  |  | ||||||
|  |     class RESTAPI_PartHandler: public Poco::Net::PartHandler | ||||||
|  |     { | ||||||
|  |     public: | ||||||
|  |         RESTAPI_PartHandler(): | ||||||
|  |                 _length(0) | ||||||
|  |         { | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         void handlePart(const Poco::Net::MessageHeader& header, std::istream& stream) override | ||||||
|  |         { | ||||||
|  |             _type = header.get("Content-Type", "(unspecified)"); | ||||||
|  |             if (header.has("Content-Disposition")) | ||||||
|  |             { | ||||||
|  |                 std::string disp; | ||||||
|  |                 Poco::Net::NameValueCollection params; | ||||||
|  |                 Poco::Net::MessageHeader::splitParameters(header["Content-Disposition"], disp, params); | ||||||
|  |                 _name = params.get("name", "(unnamed)"); | ||||||
|  |                 _fileName = params.get("filename", "(unnamed)"); | ||||||
|  |             } | ||||||
|  |  | ||||||
|  |             Poco::CountingInputStream istr(stream); | ||||||
|  |             Poco::NullOutputStream ostr; | ||||||
|  |             Poco::StreamCopier::copyStream(istr, ostr); | ||||||
|  |             _length = (int)istr.chars(); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         [[nodiscard]] int length() const | ||||||
|  |         { | ||||||
|  |             return _length; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         [[nodiscard]] const std::string& name() const | ||||||
|  |         { | ||||||
|  |             return _name; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         [[nodiscard]] const std::string& fileName() const | ||||||
|  |         { | ||||||
|  |             return _fileName; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         [[nodiscard]] const std::string& contentType() const | ||||||
|  |         { | ||||||
|  |             return _type; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |     private: | ||||||
|  |         int _length; | ||||||
|  |         std::string _type; | ||||||
|  |         std::string _name; | ||||||
|  |         std::string _fileName; | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     class RESTAPIHandler : public Poco::Net::HTTPRequestHandler { | ||||||
|  | 	  public: | ||||||
|  | 		struct QueryBlock { | ||||||
|  | 			uint64_t StartDate = 0 , EndDate = 0 , Offset = 0 , Limit = 0, LogType = 0 ; | ||||||
|  | 			std::string SerialNumber, Filter, Select; | ||||||
|  | 			bool Lifetime=false, LastOnly=false, Newest=false, CountOnly=false; | ||||||
|  | 		}; | ||||||
|  |  | ||||||
|  | 		typedef std::map<std::string, std::string> BindingMap; | ||||||
|  |  | ||||||
|  | 		RESTAPIHandler(BindingMap map, Poco::Logger &l, std::vector<std::string> Methods, RESTAPI_GenericServer & Server, bool Internal=false, bool AlwaysAuthorize=true) | ||||||
|  | 		: Bindings_(std::move(map)), Logger_(l), Methods_(std::move(Methods)), Server_(Server), Internal_(Internal), AlwaysAuthorize_(AlwaysAuthorize) {} | ||||||
|  |  | ||||||
|  | 		static bool ParseBindings(const std::string & Request, const std::list<const char *> & EndPoints, BindingMap &Keys); | ||||||
|  | 		void PrintBindings(); | ||||||
|  | 		void ParseParameters(); | ||||||
|  |  | ||||||
|  | 		void AddCORS(); | ||||||
|  | 	 	void SetCommonHeaders(bool CloseConnection=false); | ||||||
|  | 		void ProcessOptions(); | ||||||
|  | 		void | ||||||
|  | 		PrepareResponse(Poco::Net::HTTPResponse::HTTPStatus Status = Poco::Net::HTTPResponse::HTTP_OK, | ||||||
|  | 						bool CloseConnection = false); | ||||||
|  | 		bool ContinueProcessing(); | ||||||
|  | 		bool IsAuthorized(); | ||||||
|  |  | ||||||
|  | 		uint64_t GetParameter(const std::string &Name, uint64_t Default); | ||||||
|  | 		std::string GetParameter(const std::string &Name, const std::string &Default); | ||||||
|  | 		bool GetBoolParameter(const std::string &Name, bool Default); | ||||||
|  |  | ||||||
|  | 		void BadRequest(const std::string &Reason ); | ||||||
|  | 		void InternalError(const std::string &Reason = ""); | ||||||
|  | 		void UnAuthorized(const std::string &Reason = ""); | ||||||
|  | 		void ReturnObject(Poco::JSON::Object &Object); | ||||||
|  | 		void NotFound(); | ||||||
|  | 		void OK(); | ||||||
|  | 		void ReturnStatus(Poco::Net::HTTPResponse::HTTPStatus Status, | ||||||
|  | 						  bool CloseConnection=false); | ||||||
|  | 		void SendFile(Poco::File & File, const std::string & UUID); | ||||||
|  | 		void SendHTMLFileBack(Poco::File & File, | ||||||
|  |                               const Types::StringPairVec & FormVars); | ||||||
|  |         void SendFile(Poco::TemporaryFile &TempAvatar, const std::string &Type, const std::string & Name); | ||||||
|  |  | ||||||
|  |         void SendFile(Poco::File & File); | ||||||
|  |  | ||||||
|  |         const std::string &GetBinding(const std::string &Name, const std::string &Default); | ||||||
|  | 		bool InitQueryBlock(); | ||||||
|  |  | ||||||
|  | 		void ReturnCountOnly(uint64_t Count); | ||||||
|  |  | ||||||
|  | 		[[nodiscard]] static uint64_t Get(const char *Parameter,const Poco::JSON::Object::Ptr &Obj, uint64_t Default=0); | ||||||
|  | 		[[nodiscard]] static std::string GetS(const char *Parameter,const Poco::JSON::Object::Ptr &Obj, const std::string & Default=""); | ||||||
|  | 		[[nodiscard]] static bool GetB(const char *Parameter,const Poco::JSON::Object::Ptr &Obj, bool Default=false); | ||||||
|  | 		[[nodiscard]] static uint64_t GetWhen(const Poco::JSON::Object::Ptr &Obj); | ||||||
|  | 		bool HasParameter(const std::string &QueryParameter, std::string &Value); | ||||||
|  | 		bool HasParameter(const std::string &QueryParameter, uint64_t & Value); | ||||||
|  |  | ||||||
|  | 		static bool AssignIfPresent(const Poco::JSON::Object::Ptr &O, const std::string &Field, std::string &Value); | ||||||
|  | 		static bool AssignIfPresent(const Poco::JSON::Object::Ptr &O, const std::string &Field, uint64_t &Value); | ||||||
|  |  | ||||||
|  | 		template<typename T> void ReturnObject(const char *Name, const std::vector<T> & Objects) { | ||||||
|  | 		    Poco::JSON::Object  Answer; | ||||||
|  | 		    RESTAPI_utils::field_to_json(Answer,Name,Objects); | ||||||
|  |             ReturnObject(Answer); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		Poco::Logger & Logger() { return Logger_; } | ||||||
|  |  | ||||||
|  | 		void handleRequest(Poco::Net::HTTPServerRequest &request, | ||||||
|  |                            Poco::Net::HTTPServerResponse &response) final; | ||||||
|  |  | ||||||
|  | 		virtual void DoGet() = 0 ; | ||||||
|  | 		virtual void DoDelete() = 0 ; | ||||||
|  | 		virtual void DoPost() = 0 ; | ||||||
|  | 		virtual void DoPut() = 0 ; | ||||||
|  |  | ||||||
|  | 		const Poco::JSON::Object::Ptr & ParseStream(); | ||||||
|  |  | ||||||
|  | 	  protected: | ||||||
|  | 		BindingMap 					Bindings_; | ||||||
|  | 		Poco::URI::QueryParameters 	Parameters_; | ||||||
|  | 		Poco::Logger 				&Logger_; | ||||||
|  | 		std::string 				SessionToken_; | ||||||
|  | 		SecurityObjects::UserInfoAndPolicy 	UserInfo_; | ||||||
|  | 		std::vector<std::string> 	Methods_; | ||||||
|  | 		QueryBlock					QB_; | ||||||
|  | 		bool                        Internal_=false; | ||||||
|  | 		bool                        QueryBlockInitialized_=false; | ||||||
|  | 		Poco::Net::HTTPServerRequest    *Request= nullptr; | ||||||
|  | 		Poco::Net::HTTPServerResponse   *Response= nullptr; | ||||||
|  | 		bool                        AlwaysAuthorize_=true; | ||||||
|  | 		Poco::JSON::Parser          IncomingParser_; | ||||||
|  | 		RESTAPI_GenericServer       & Server_; | ||||||
|  | 	}; | ||||||
|  |  | ||||||
|  | 	class RESTAPI_UnknownRequestHandler : public RESTAPIHandler { | ||||||
|  | 	  public: | ||||||
|  | 		RESTAPI_UnknownRequestHandler(const RESTAPIHandler::BindingMap &bindings, Poco::Logger &L, RESTAPI_GenericServer & Server) | ||||||
|  | 			: RESTAPIHandler(bindings, L, std::vector<std::string>{}, Server) {} | ||||||
|  |         inline void DoGet() override {}; | ||||||
|  | 		inline void DoPost() override {}; | ||||||
|  | 		inline void DoPut() override {}; | ||||||
|  | 		inline void DoDelete() override {}; | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  | 	template<class T> | ||||||
|  | 	constexpr auto test_has_PathName_method(T*) | ||||||
|  | 	-> decltype(  T::PathName() , std::true_type{} ) | ||||||
|  | 	{ | ||||||
|  | 		return std::true_type{}; | ||||||
|  | 	} | ||||||
|  | 	constexpr auto test_has_PathName_method(...) -> std::false_type | ||||||
|  | 	{ | ||||||
|  | 		return std::false_type{}; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	template<typename T, typename... Args> | ||||||
|  | 	RESTAPIHandler * RESTAPI_Router(const std::string & RequestedPath, RESTAPIHandler::BindingMap &Bindings, Poco::Logger & Logger, RESTAPI_GenericServer & Server) { | ||||||
|  | 		static_assert(test_has_PathName_method((T*)nullptr), "Class must have a static PathName() method."); | ||||||
|  | 		if(RESTAPIHandler::ParseBindings(RequestedPath,T::PathName(),Bindings)) { | ||||||
|  | 			return new T(Bindings, Logger, Server, false); | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		if constexpr (sizeof...(Args) == 0) { | ||||||
|  | 			return new RESTAPI_UnknownRequestHandler(Bindings,Logger, Server); | ||||||
|  | 		} else { | ||||||
|  | 			return RESTAPI_Router<Args...>(RequestedPath, Bindings, Logger, Server); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  |     template<typename T, typename... Args> | ||||||
|  |     RESTAPIHandler * RESTAPI_Router_I(const std::string & RequestedPath, RESTAPIHandler::BindingMap &Bindings, Poco::Logger & Logger, RESTAPI_GenericServer & Server) { | ||||||
|  |         static_assert(test_has_PathName_method((T*)nullptr), "Class must have a static PathName() method."); | ||||||
|  |         if(RESTAPIHandler::ParseBindings(RequestedPath,T::PathName(),Bindings)) { | ||||||
|  |             return new T(Bindings, Logger, Server, true); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if constexpr (sizeof...(Args) == 0) { | ||||||
|  |             return new RESTAPI_UnknownRequestHandler(Bindings,Logger, Server); | ||||||
|  |         } else { | ||||||
|  |             return RESTAPI_Router_I<Args...>(RequestedPath, Bindings, Logger, Server); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #endif //UCENTRAL_RESTAPI_HANDLER_H | ||||||
							
								
								
									
										100
									
								
								src/RESTAPI_oauth2Handler.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										100
									
								
								src/RESTAPI_oauth2Handler.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,100 @@ | |||||||
|  | // | ||||||
|  | //	License type: BSD 3-Clause License | ||||||
|  | //	License copy: https://github.com/Telecominfraproject/wlan-cloud-ucentralgw/blob/master/LICENSE | ||||||
|  | // | ||||||
|  | //	Created by Stephane Bourque on 2021-03-04. | ||||||
|  | //	Arilia Wireless Inc. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #include "Poco/JSON/Parser.h" | ||||||
|  |  | ||||||
|  | #include "AuthService.h" | ||||||
|  | #include "RESTAPI_oauth2Handler.h" | ||||||
|  | #include "RESTAPI_protocol.h" | ||||||
|  | #include "RESTAPI_server.h" | ||||||
|  |  | ||||||
|  | #include "Utils.h" | ||||||
|  |  | ||||||
|  | namespace OpenWifi { | ||||||
|  | 	void RESTAPI_oauth2Handler::DoGet() { | ||||||
|  |         if (!IsAuthorized()) { | ||||||
|  |             UnAuthorized("Not authorized."); | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |         bool GetMe = GetBoolParameter(RESTAPI::Protocol::ME, false); | ||||||
|  |         if(GetMe) { | ||||||
|  |             Logger_.information(Poco::format("REQUEST-ME(%s): Request for %s", Request->clientAddress().toString(), UserInfo_.userinfo.email)); | ||||||
|  |             Poco::JSON::Object Me; | ||||||
|  |             UserInfo_.userinfo.to_json(Me); | ||||||
|  |             ReturnObject(Me); | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |         BadRequest("Ill-formed request. Please consult documentation."); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  |     void RESTAPI_oauth2Handler::DoDelete() { | ||||||
|  |         if (!IsAuthorized()) { | ||||||
|  |             UnAuthorized("Not authorized."); | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |         auto Token = GetBinding(RESTAPI::Protocol::TOKEN, "..."); | ||||||
|  |         if (Token == SessionToken_) { | ||||||
|  |             AuthService()->Logout(Token); | ||||||
|  |             ReturnStatus(Poco::Net::HTTPResponse::HTTP_NO_CONTENT, true); | ||||||
|  |         } else { | ||||||
|  |             Logger_.information(Poco::format("BAD-LOGOUT(%s): Request for %s", Request->clientAddress().toString(), UserInfo_.userinfo.email)); | ||||||
|  |             NotFound(); | ||||||
|  |         } | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	void RESTAPI_oauth2Handler::DoPost() { | ||||||
|  |         auto Obj = ParseStream(); | ||||||
|  |         auto userId = GetS(RESTAPI::Protocol::USERID, Obj); | ||||||
|  |         auto password = GetS(RESTAPI::Protocol::PASSWORD, Obj); | ||||||
|  |         auto newPassword = GetS(RESTAPI::Protocol::NEWPASSWORD, Obj); | ||||||
|  |         Poco::toLowerInPlace(userId); | ||||||
|  |  | ||||||
|  |         if(GetBoolParameter(RESTAPI::Protocol::REQUIREMENTS, false)) { | ||||||
|  |             Logger_.information(Poco::format("POLICY-REQUEST(%s): Request.", Request->clientAddress().toString())); | ||||||
|  |             Poco::JSON::Object  Answer; | ||||||
|  |             Answer.set(RESTAPI::Protocol::PASSWORDPATTERN, AuthService()->PasswordValidationExpression()); | ||||||
|  |             Answer.set(RESTAPI::Protocol::ACCESSPOLICY, RESTAPI_Server()->GetAccessPolicy()); | ||||||
|  |             Answer.set(RESTAPI::Protocol::PASSWORDPOLICY, RESTAPI_Server()->GetPasswordPolicy()); | ||||||
|  |             ReturnObject(Answer); | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if(GetBoolParameter(RESTAPI::Protocol::FORGOTPASSWORD,false)) { | ||||||
|  |             //  Send an email to the userId | ||||||
|  |             Logger_.information(Poco::format("FORGOTTEN-PASSWORD(%s): Request for %s", Request->clientAddress().toString(), userId)); | ||||||
|  |             SecurityObjects::UserInfoAndPolicy UInfo; | ||||||
|  |             if(AuthService::SendEmailToUser(userId,AuthService::FORGOT_PASSWORD)) | ||||||
|  |                 Logger_.information(Poco::format("Send password reset link to %s",userId)); | ||||||
|  |             UInfo.webtoken.userMustChangePassword=true; | ||||||
|  |             Poco::JSON::Object ReturnObj; | ||||||
|  |             UInfo.webtoken.to_json(ReturnObj); | ||||||
|  |             ReturnObject(ReturnObj); | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         SecurityObjects::UserInfoAndPolicy UInfo; | ||||||
|  |  | ||||||
|  |         auto Code=AuthService()->Authorize(userId, password, newPassword, UInfo); | ||||||
|  |         if (Code==AuthService::SUCCESS) { | ||||||
|  |             Poco::JSON::Object ReturnObj; | ||||||
|  |             UInfo.webtoken.to_json(ReturnObj); | ||||||
|  |             ReturnObject(ReturnObj); | ||||||
|  |             return; | ||||||
|  |         } else { | ||||||
|  |             switch(Code) { | ||||||
|  |                 case AuthService::INVALID_CREDENTIALS: UnAuthorized("Unrecognized credentials (username/password)."); break; | ||||||
|  |                 case AuthService::PASSWORD_INVALID: UnAuthorized("Invalid password."); break; | ||||||
|  |                 case AuthService::PASSWORD_ALREADY_USED: UnAuthorized("Password already used previously."); break; | ||||||
|  |                 case AuthService::USERNAME_PENDING_VERIFICATION: UnAuthorized("User access pending email verification."); break; | ||||||
|  |                 case AuthService::PASSWORD_CHANGE_REQUIRED: UnAuthorized("Password change expected."); break; | ||||||
|  |                 default: UnAuthorized("Unrecognized credentials (username/password)."); break; | ||||||
|  |             } | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  | 	} | ||||||
|  | } | ||||||
| @@ -6,27 +6,27 @@ | |||||||
| //	Arilia Wireless Inc.
 | //	Arilia Wireless Inc.
 | ||||||
| //
 | //
 | ||||||
| 
 | 
 | ||||||
| #pragma once | #ifndef UCENTRAL_RESTAPI_OAUTH2HANDLER_H | ||||||
| #include "framework/MicroService.h" | #define UCENTRAL_RESTAPI_OAUTH2HANDLER_H | ||||||
|  | 
 | ||||||
|  | #include "RESTAPI_handler.h" | ||||||
| 
 | 
 | ||||||
| namespace OpenWifi { | namespace OpenWifi { | ||||||
| 	class RESTAPI_oauth2_handler : public RESTAPIHandler { | 	class RESTAPI_oauth2Handler : public RESTAPIHandler { | ||||||
| 	  public: | 	  public: | ||||||
| 	    RESTAPI_oauth2_handler(const RESTAPIHandler::BindingMap &bindings, Poco::Logger &L, RESTAPI_GenericServer &Server, uint64_t TransactionId, bool Internal) | 	    RESTAPI_oauth2Handler(const RESTAPIHandler::BindingMap &bindings, Poco::Logger &L, RESTAPI_GenericServer &Server, bool Internal) | ||||||
| 			: RESTAPIHandler(bindings, L, | 			: RESTAPIHandler(bindings, L, | ||||||
| 							 std::vector<std::string>{Poco::Net::HTTPRequest::HTTP_POST, | 							 std::vector<std::string>{Poco::Net::HTTPRequest::HTTP_POST, | ||||||
| 													  Poco::Net::HTTPRequest::HTTP_DELETE, | 													  Poco::Net::HTTPRequest::HTTP_DELETE, | ||||||
|                                                       Poco::Net::HTTPRequest::HTTP_GET, |                                                       Poco::Net::HTTPRequest::HTTP_GET, | ||||||
| 													  Poco::Net::HTTPRequest::HTTP_OPTIONS}, | 													  Poco::Net::HTTPRequest::HTTP_OPTIONS}, | ||||||
| 													  Server, | 													  Server, | ||||||
|                                                       TransactionId, | 													  Internal, false) {} | ||||||
| 													  Internal, false, true , RateLimit{.Interval=1000,.MaxCalls=10}) {} | 		static const std::list<const char *> PathName() { return std::list<const char *>{"/api/v1/oauth2/{token}","/api/v1/oauth2"}; }; | ||||||
|         static auto PathName() { return std::list<std::string>{"/api/v1/oauth2/{token}","/api/v1/oauth2"}; }; |  | ||||||
| 		void DoGet() final; | 		void DoGet() final; | ||||||
| 		void DoPost() final; | 		void DoPost() final; | ||||||
| 		void DoDelete() final; | 		void DoDelete() final; | ||||||
| 		void DoPut() final {}; | 		void DoPut() final {}; | ||||||
| 	}; | 	}; | ||||||
| } | } | ||||||
| 
 | #endif //UCENTRAL_RESTAPI_OAUTH2HANDLER_H
 | ||||||
| 
 |  | ||||||
							
								
								
									
										136
									
								
								src/RESTAPI_protocol.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										136
									
								
								src/RESTAPI_protocol.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,136 @@ | |||||||
|  | // | ||||||
|  | //	License type: BSD 3-Clause License | ||||||
|  | //	License copy: https://github.com/Telecominfraproject/wlan-cloud-ucentralgw/blob/master/LICENSE | ||||||
|  | // | ||||||
|  | //	Created by Stephane Bourque on 2021-03-04. | ||||||
|  | //	Arilia Wireless Inc. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef UCENTRALGW_RESTAPI_PROTOCOL_H | ||||||
|  | #define UCENTRALGW_RESTAPI_PROTOCOL_H | ||||||
|  |  | ||||||
|  | namespace OpenWifi::RESTAPI::Protocol { | ||||||
|  | 	static const char * CAPABILITIES = "capabilities"; | ||||||
|  | 	static const char * LOGS = "logs"; | ||||||
|  | 	static const char * HEALTHCHECKS = "healthchecks"; | ||||||
|  | 	static const char * STATISTICS = "statistics"; | ||||||
|  | 	static const char * STATUS = "status"; | ||||||
|  | 	static const char * SERIALNUMBER = "serialNumber"; | ||||||
|  | 	static const char * PERFORM = "perform"; | ||||||
|  | 	static const char * CONFIGURE = "configure"; | ||||||
|  | 	static const char * UPGRADE = "upgrade"; | ||||||
|  | 	static const char * REBOOT = "reboot"; | ||||||
|  | 	static const char * FACTORY = "factory"; | ||||||
|  | 	static const char * LEDS = "leds"; | ||||||
|  | 	static const char * TRACE = "trace"; | ||||||
|  | 	static const char * REQUEST = "request"; | ||||||
|  | 	static const char * WIFISCAN = "wifiscan"; | ||||||
|  | 	static const char * EVENTQUEUE = "eventqueue"; | ||||||
|  | 	static const char * RTTY = "rtty"; | ||||||
|  | 	static const char * COMMAND = "command"; | ||||||
|  | 	static const char * STARTDATE = "startDate"; | ||||||
|  | 	static const char * ENDDATE = "endDate"; | ||||||
|  | 	static const char * OFFSET = "offset"; | ||||||
|  | 	static const char * LIMIT = "limit"; | ||||||
|  | 	static const char * LIFETIME = "lifetime"; | ||||||
|  | 	static const char * UUID = "UUID"; | ||||||
|  | 	static const char * DATA = "data"; | ||||||
|  | 	static const char * CONFIGURATION = "configuration"; | ||||||
|  | 	static const char * WHEN = "when"; | ||||||
|  | 	static const char * URI = "uri"; | ||||||
|  | 	static const char * LOGTYPE = "logType"; | ||||||
|  | 	static const char * VALUES = "values"; | ||||||
|  | 	static const char * TYPES = "types"; | ||||||
|  | 	static const char * PAYLOAD = "payload"; | ||||||
|  | 	static const char * KEEPREDIRECTOR = "keepRedirector"; | ||||||
|  | 	static const char * NETWORK = "network"; | ||||||
|  | 	static const char * INTERFACE = "interface"; | ||||||
|  | 	static const char * BANDS = "bands"; | ||||||
|  | 	static const char * CHANNELS = "channels"; | ||||||
|  | 	static const char * VERBOSE = "verbose"; | ||||||
|  | 	static const char * MESSAGE = "message"; | ||||||
|  | 	static const char * STATE = "state"; | ||||||
|  | 	static const char * HEALTHCHECK = "healthcheck"; | ||||||
|  | 	static const char * PCAP_FILE_TYPE = "pcap"; | ||||||
|  | 	static const char * DURATION = "duration"; | ||||||
|  | 	static const char * NUMBEROFPACKETS = "numberOfPackets"; | ||||||
|  | 	static const char * FILTER = "filter"; | ||||||
|  | 	static const char * SELECT = "select"; | ||||||
|  | 	static const char * SERIALONLY = "serialOnly"; | ||||||
|  | 	static const char * COUNTONLY = "countOnly"; | ||||||
|  | 	static const char * DEVICEWITHSTATUS = "deviceWithStatus"; | ||||||
|  | 	static const char * DEVICESWITHSTATUS = "devicesWithStatus"; | ||||||
|  | 	static const char * DEVICES = "devices"; | ||||||
|  | 	static const char * COUNT = "count"; | ||||||
|  | 	static const char * SERIALNUMBERS = "serialNumbers"; | ||||||
|  | 	static const char * CONFIGURATIONS = "configurations"; | ||||||
|  | 	static const char * NAME = "name"; | ||||||
|  | 	static const char * COMMANDS = "commands"; | ||||||
|  | 	static const char * COMMANDUUID = "commandUUID"; | ||||||
|  | 	static const char * FIRMWARES = "firmwares"; | ||||||
|  | 	static const char * TOPIC = "topic"; | ||||||
|  | 	static const char * HOST = "host"; | ||||||
|  | 	static const char * OS = "os"; | ||||||
|  | 	static const char * HOSTNAME = "hostname"; | ||||||
|  | 	static const char * PROCESSORS = "processors"; | ||||||
|  | 	static const char * REASON = "reason"; | ||||||
|  | 	static const char * RELOAD = "reload"; | ||||||
|  | 	static const char * SUBSYSTEMS = "subsystems"; | ||||||
|  | 	static const char * FILEUUID = "uuid"; | ||||||
|  | 	static const char * USERID = "userId"; | ||||||
|  | 	static const char * PASSWORD = "password"; | ||||||
|  | 	static const char * TOKEN = "token"; | ||||||
|  | 	static const char * SETLOGLEVEL = "setloglevel"; | ||||||
|  | 	static const char * GETLOGLEVELS = "getloglevels"; | ||||||
|  | 	static const char * GETSUBSYSTEMNAMES = "getsubsystemnames"; | ||||||
|  | 	static const char * GETLOGLEVELNAMES = "getloglevelnames"; | ||||||
|  | 	static const char * STATS = "stats"; | ||||||
|  | 	static const char * PARAMETERS = "parameters"; | ||||||
|  | 	static const char * VALUE = "value"; | ||||||
|  | 	static const char * LASTONLY = "lastOnly"; | ||||||
|  | 	static const char * NEWEST = "newest"; | ||||||
|  | 	static const char * ACTIVESCAN = "activeScan"; | ||||||
|  | 	static const char * LIST = "list"; | ||||||
|  | 	static const char * TAG = "tag"; | ||||||
|  | 	static const char * TAGLIST = "tagList"; | ||||||
|  |     static const char * DESCRIPTION = "description"; | ||||||
|  |     static const char * NOTES = "notes"; | ||||||
|  |     static const char * DEVICETYPE = "deviceType"; | ||||||
|  |     static const char * REVISION = "revision"; | ||||||
|  |     static const char * AGES = "ages"; | ||||||
|  |     static const char * REVISIONS = "revisions"; | ||||||
|  |     static const char * DEVICETYPES = "deviceTypes"; | ||||||
|  |     static const char * LATESTONLY = "latestOnly"; | ||||||
|  |     static const char * IDONLY = "idOnly"; | ||||||
|  |     static const char * REVISIONSET = "revisionSet"; | ||||||
|  |     static const char * DEVICESET = "deviceSet"; | ||||||
|  |     static const char * HISTORY = "history"; | ||||||
|  |     static const char * ID = "id"; | ||||||
|  |     static const char * VERSION = "version"; | ||||||
|  |     static const char * TIMES = "times"; | ||||||
|  |     static const char * UPTIME = "uptime"; | ||||||
|  |     static const char * START = "start"; | ||||||
|  |  | ||||||
|  |     static const char * NEWPASSWORD = "newPassword"; | ||||||
|  |     static const char * USERS = "users"; | ||||||
|  |  | ||||||
|  |     static const char * ERRORTEXT = "errorText"; | ||||||
|  |     static const char * ERRORCODE = "errorCode"; | ||||||
|  |     static const char * AVATARID = "avatarId"; | ||||||
|  |     static const char * UNNAMED = "(unnamed)"; | ||||||
|  |     static const char * UNSPECIFIED = "(unspecified)"; | ||||||
|  |     static const char * CONTENTDISPOSITION = "Content-Disposition"; | ||||||
|  |     static const char * CONTENTTYPE = "Content-Type"; | ||||||
|  |  | ||||||
|  |     static const char * REQUIREMENTS = "requirements"; | ||||||
|  |     static const char * PASSWORDPATTERN = "passwordPattern"; | ||||||
|  |     static const char * ACCESSPOLICY = "accessPolicy"; | ||||||
|  |     static const char * PASSWORDPOLICY = "passwordPolicy"; | ||||||
|  |     static const char * FORGOTPASSWORD = "forgotPassword"; | ||||||
|  |     static const char * ME = "me"; | ||||||
|  |     static const char * TELEMETRY = "telemetry"; | ||||||
|  |     static const char * INTERVAL = "interval"; | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | #endif // UCENTRALGW_RESTAPI_PROTOCOL_H | ||||||
							
								
								
									
										92
									
								
								src/RESTAPI_server.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										92
									
								
								src/RESTAPI_server.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,92 @@ | |||||||
|  | // | ||||||
|  | //	License type: BSD 3-Clause License | ||||||
|  | //	License copy: https://github.com/Telecominfraproject/wlan-cloud-ucentralgw/blob/master/LICENSE | ||||||
|  | // | ||||||
|  | //	Created by Stephane Bourque on 2021-03-04. | ||||||
|  | //	Arilia Wireless Inc. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #include <memory> | ||||||
|  |  | ||||||
|  | #include "Poco/URI.h" | ||||||
|  |  | ||||||
|  | #include "RESTAPI_server.h" | ||||||
|  | #include "RESTAPI_oauth2Handler.h" | ||||||
|  | #include "RESTAPI_system_command.h" | ||||||
|  | #include "RESTAPI_user_handler.h" | ||||||
|  | #include "RESTAPI_users_handler.h" | ||||||
|  | #include "RESTAPI_action_links.h" | ||||||
|  | #include "RESTAPI_systemEndpoints_handler.h" | ||||||
|  | #include "RESTAPI_AssetServer.h" | ||||||
|  | #include "RESTAPI_avatarHandler.h" | ||||||
|  | #include "RESTAPI_email_handler.h" | ||||||
|  |  | ||||||
|  | #include "Daemon.h" | ||||||
|  | #include "Utils.h" | ||||||
|  |  | ||||||
|  | namespace OpenWifi { | ||||||
|  |  | ||||||
|  |     class RESTAPI_Server *RESTAPI_Server::instance_ = nullptr; | ||||||
|  |  | ||||||
|  |     int RESTAPI_Server::Start() { | ||||||
|  |         Logger_.information("Starting."); | ||||||
|  |         Server_.InitLogging(); | ||||||
|  |  | ||||||
|  |         AsserDir_ = Daemon()->ConfigPath("openwifi.restapi.wwwassets"); | ||||||
|  |         AccessPolicy_ = Daemon()->ConfigGetString("openwifi.document.policy.access", "/wwwassets/access_policy.html"); | ||||||
|  |         PasswordPolicy_ = Daemon()->ConfigGetString("openwifi.document.policy.password", "/wwwassets/possword_policy.html"); | ||||||
|  |  | ||||||
|  |         for(const auto & Svr: ConfigServersList_) { | ||||||
|  | 			Logger_.information(Poco::format("Starting: %s:%s Keyfile:%s CertFile: %s", Svr.Address(), std::to_string(Svr.Port()), | ||||||
|  | 											 Svr.KeyFile(),Svr.CertFile())); | ||||||
|  |  | ||||||
|  |             auto Sock{Svr.CreateSecureSocket(Logger_)}; | ||||||
|  |  | ||||||
|  | 			Svr.LogCert(Logger_); | ||||||
|  | 			if(!Svr.RootCA().empty()) | ||||||
|  | 				Svr.LogCas(Logger_); | ||||||
|  |  | ||||||
|  |             auto Params = new Poco::Net::HTTPServerParams; | ||||||
|  |             Params->setMaxThreads(50); | ||||||
|  |             Params->setMaxQueued(200); | ||||||
|  | 			Params->setKeepAlive(true); | ||||||
|  |  | ||||||
|  |             auto NewServer = std::make_unique<Poco::Net::HTTPServer>(new RequestHandlerFactory(Server_), Pool_, Sock, Params); | ||||||
|  |             NewServer->start(); | ||||||
|  |             RESTServers_.push_back(std::move(NewServer)); | ||||||
|  |         } | ||||||
|  |         return 0; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     Poco::Net::HTTPRequestHandler *RequestHandlerFactory::createRequestHandler(const Poco::Net::HTTPServerRequest & Request) { | ||||||
|  |         Poco::URI uri(Request.getURI()); | ||||||
|  |         const auto & Path = uri.getPath(); | ||||||
|  |         RESTAPIHandler::BindingMap Bindings; | ||||||
|  |         return RESTAPI_Router< | ||||||
|  |                 RESTAPI_oauth2Handler, | ||||||
|  |                 RESTAPI_users_handler, | ||||||
|  |                 RESTAPI_user_handler, | ||||||
|  |                 RESTAPI_system_command, | ||||||
|  |                 RESTAPI_AssetServer, | ||||||
|  |                 RESTAPI_systemEndpoints_handler, | ||||||
|  |                 RESTAPI_action_links, | ||||||
|  |                 RESTAPI_avatarHandler, | ||||||
|  |                 RESTAPI_email_handler | ||||||
|  |                 >(Path,Bindings,Logger_,Server_); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     void RESTAPI_Server::Stop() { | ||||||
|  |         Logger_.information("Stopping "); | ||||||
|  |         for( const auto & svr : RESTServers_ ) | ||||||
|  |             svr->stop(); | ||||||
|  |         RESTServers_.clear(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     void RESTAPI_Server::reinitialize(Poco::Util::Application &self) { | ||||||
|  |         Daemon()->LoadConfigurationFile(); | ||||||
|  |         Logger_.information("Reinitializing."); | ||||||
|  |         Stop(); | ||||||
|  |         Start(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | }  // namespace | ||||||
							
								
								
									
										71
									
								
								src/RESTAPI_server.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								src/RESTAPI_server.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,71 @@ | |||||||
|  | // | ||||||
|  | //	License type: BSD 3-Clause License | ||||||
|  | //	License copy: https://github.com/Telecominfraproject/wlan-cloud-ucentralgw/blob/master/LICENSE | ||||||
|  | // | ||||||
|  | //	Created by Stephane Bourque on 2021-03-04. | ||||||
|  | //	Arilia Wireless Inc. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef UCENTRAL_UCENTRALRESTAPISERVER_H | ||||||
|  | #define UCENTRAL_UCENTRALRESTAPISERVER_H | ||||||
|  |  | ||||||
|  | #include "SubSystemServer.h" | ||||||
|  | #include "Poco/Net/HTTPServer.h" | ||||||
|  | #include "Poco/Net/HTTPRequestHandler.h" | ||||||
|  | #include "Poco/Net/HTTPRequestHandlerFactory.h" | ||||||
|  | #include "Poco/Net/HTTPServerRequest.h" | ||||||
|  | #include "Poco/Net/NetException.h" | ||||||
|  | #include "RESTAPI_GenericServer.h" | ||||||
|  |  | ||||||
|  | namespace OpenWifi { | ||||||
|  |  | ||||||
|  |     class RESTAPI_Server : public SubSystemServer { | ||||||
|  |  | ||||||
|  |     public: | ||||||
|  |         static RESTAPI_Server *instance() { | ||||||
|  |             if (instance_ == nullptr) { | ||||||
|  |                 instance_ = new RESTAPI_Server; | ||||||
|  |             } | ||||||
|  |             return instance_; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         int Start() override; | ||||||
|  |         void Stop() override; | ||||||
|  |         void reinitialize(Poco::Util::Application &self) override; | ||||||
|  |  | ||||||
|  |         inline const std::string & AssetDir() { return AsserDir_; } | ||||||
|  |         inline const std::string & GetPasswordPolicy() const { return PasswordPolicy_; } | ||||||
|  |         inline const std::string & GetAccessPolicy() const { return AccessPolicy_; } | ||||||
|  |     private: | ||||||
|  | 		static RESTAPI_Server *instance_; | ||||||
|  |         std::vector<std::unique_ptr<Poco::Net::HTTPServer>>   RESTServers_; | ||||||
|  | 		Poco::ThreadPool	Pool_; | ||||||
|  | 		std::string         AsserDir_; | ||||||
|  | 		std::string         PasswordPolicy_; | ||||||
|  | 		std::string         AccessPolicy_; | ||||||
|  | 		RESTAPI_GenericServer   Server_; | ||||||
|  |  | ||||||
|  |         RESTAPI_Server() noexcept: | ||||||
|  |             SubSystemServer("RESTAPIServer", "REST-SRV", "openwifi.restapi") | ||||||
|  |         { | ||||||
|  |         } | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |     inline RESTAPI_Server * RESTAPI_Server() { return RESTAPI_Server::instance(); }; | ||||||
|  |  | ||||||
|  |     class RequestHandlerFactory : public Poco::Net::HTTPRequestHandlerFactory { | ||||||
|  |         public: | ||||||
|  |         RequestHandlerFactory(RESTAPI_GenericServer &Server) : | ||||||
|  |                 Logger_(RESTAPI_Server()->Logger()), | ||||||
|  |                 Server_(Server){} | ||||||
|  |  | ||||||
|  |             Poco::Net::HTTPRequestHandler *createRequestHandler(const Poco::Net::HTTPServerRequest &request) override; | ||||||
|  |         private: | ||||||
|  |             Poco::Logger    & Logger_; | ||||||
|  |             RESTAPI_GenericServer   &Server_; | ||||||
|  |     }; | ||||||
|  |  | ||||||
|  |  | ||||||
|  | } //   namespace | ||||||
|  |  | ||||||
|  | #endif //UCENTRAL_UCENTRALRESTAPISERVER_H | ||||||
| @@ -2,13 +2,14 @@ | |||||||
| // Created by stephane bourque on 2021-07-01.
 | // Created by stephane bourque on 2021-07-01.
 | ||||||
| //
 | //
 | ||||||
| 
 | 
 | ||||||
| #include "RESTAPI_system_endpoints_handler.h" | #include "RESTAPI_systemEndpoints_handler.h" | ||||||
| #include "RESTObjects/RESTAPI_SecurityObjects.h" | #include "Daemon.h" | ||||||
|  | #include "RESTAPI_SecurityObjects.h" | ||||||
| 
 | 
 | ||||||
| namespace OpenWifi { | namespace OpenWifi { | ||||||
| 
 | 
 | ||||||
|     void RESTAPI_system_endpoints_handler::DoGet() { |     void RESTAPI_systemEndpoints_handler::DoGet() { | ||||||
|         auto Services = MicroService::instance().GetServices(); |         auto Services = Daemon()->GetServices(); | ||||||
|         SecurityObjects::SystemEndpointList L; |         SecurityObjects::SystemEndpointList L; | ||||||
|         for(const auto &i:Services) { |         for(const auto &i:Services) { | ||||||
|             SecurityObjects::SystemEndpoint S{ |             SecurityObjects::SystemEndpoint S{ | ||||||
| @@ -2,24 +2,25 @@ | |||||||
| // Created by stephane bourque on 2021-07-01.
 | // Created by stephane bourque on 2021-07-01.
 | ||||||
| //
 | //
 | ||||||
| 
 | 
 | ||||||
| #pragma once | #ifndef UCENTRALSEC_RESTAPI_SYSTEMENDPOINTS_HANDLER_H | ||||||
| 
 | #define UCENTRALSEC_RESTAPI_SYSTEMENDPOINTS_HANDLER_H | ||||||
| #include "../framework/MicroService.h" |  | ||||||
| 
 | 
 | ||||||
|  | #include "RESTAPI_handler.h" | ||||||
| namespace OpenWifi { | namespace OpenWifi { | ||||||
|     class RESTAPI_system_endpoints_handler : public RESTAPIHandler { |     class RESTAPI_systemEndpoints_handler : public RESTAPIHandler { | ||||||
|     public: |     public: | ||||||
|         RESTAPI_system_endpoints_handler(const RESTAPIHandler::BindingMap &bindings, Poco::Logger &L, RESTAPI_GenericServer &Server, uint64_t TransactionId, bool Internal) |         RESTAPI_systemEndpoints_handler(const RESTAPIHandler::BindingMap &bindings, Poco::Logger &L, RESTAPI_GenericServer &Server, bool Internal) | ||||||
|                 : RESTAPIHandler(bindings, L, |                 : RESTAPIHandler(bindings, L, | ||||||
|                                  std::vector<std::string>{Poco::Net::HTTPRequest::HTTP_GET, |                                  std::vector<std::string>{Poco::Net::HTTPRequest::HTTP_GET, | ||||||
|                                                           Poco::Net::HTTPRequest::HTTP_OPTIONS}, |                                                           Poco::Net::HTTPRequest::HTTP_OPTIONS}, | ||||||
|                                                           Server, |                                                           Server, | ||||||
|                                                           TransactionId, |  | ||||||
|                                                           Internal) {} |                                                           Internal) {} | ||||||
|         static auto PathName() { return std::list<std::string>{"/api/v1/systemEndpoints"}; }; |         static const std::list<const char *> PathName() { return std::list<const char *>{"/api/v1/systemEndpoints"}; }; | ||||||
|         void DoGet() final; |         void DoGet() final; | ||||||
|         void DoPost() final {}; |         void DoPost() final {}; | ||||||
|         void DoDelete() final {}; |         void DoDelete() final {}; | ||||||
|         void DoPut() final {}; |         void DoPut() final {}; | ||||||
|     }; |     }; | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | #endif //UCENTRALSEC_RESTAPI_SYSTEMENDPOINTS_HANDLER_H
 | ||||||
							
								
								
									
										146
									
								
								src/RESTAPI_system_command.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										146
									
								
								src/RESTAPI_system_command.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,146 @@ | |||||||
|  | // | ||||||
|  | //	License type: BSD 3-Clause License | ||||||
|  | //	License copy: https://github.com/Telecominfraproject/wlan-cloud-ucentralgw/blob/master/LICENSE | ||||||
|  | // | ||||||
|  | //	Created by Stephane Bourque on 2021-03-04. | ||||||
|  | //	Arilia Wireless Inc. | ||||||
|  | // | ||||||
|  | #include "RESTAPI_system_command.h" | ||||||
|  |  | ||||||
|  | #include "Poco/Exception.h" | ||||||
|  | #include "Poco/JSON/Parser.h" | ||||||
|  | #include "Poco/DateTime.h" | ||||||
|  | #include "Poco/DateTimeFormat.h" | ||||||
|  |  | ||||||
|  | #include "Daemon.h" | ||||||
|  | #include "RESTAPI_protocol.h" | ||||||
|  | #include "RESTAPI_errors.h" | ||||||
|  | #include <thread> | ||||||
|  | #include <chrono> | ||||||
|  |  | ||||||
|  | using namespace std::chrono_literals; | ||||||
|  |  | ||||||
|  | namespace OpenWifi { | ||||||
|  | 	void RESTAPI_system_command::DoPost() { | ||||||
|  | 		auto Obj = ParseStream(); | ||||||
|  | 		if (Obj->has(RESTAPI::Protocol::COMMAND)) { | ||||||
|  | 			auto Command = Poco::toLower(Obj->get(RESTAPI::Protocol::COMMAND).toString()); | ||||||
|  | 			if (Command == RESTAPI::Protocol::SETLOGLEVEL) { | ||||||
|  | 				if (Obj->has(RESTAPI::Protocol::SUBSYSTEMS) && | ||||||
|  | 					Obj->isArray(RESTAPI::Protocol::SUBSYSTEMS)) { | ||||||
|  | 					auto ParametersBlock = Obj->getArray(RESTAPI::Protocol::SUBSYSTEMS); | ||||||
|  | 					for (const auto &i : *ParametersBlock) { | ||||||
|  | 						Poco::JSON::Parser pp; | ||||||
|  | 						auto InnerObj = pp.parse(i).extract<Poco::JSON::Object::Ptr>(); | ||||||
|  | 						if (InnerObj->has(RESTAPI::Protocol::TAG) && | ||||||
|  | 							InnerObj->has(RESTAPI::Protocol::VALUE)) { | ||||||
|  | 							auto Name = GetS(RESTAPI::Protocol::TAG, InnerObj); | ||||||
|  | 							auto Value = GetS(RESTAPI::Protocol::VALUE, InnerObj); | ||||||
|  | 							Daemon()->SetSubsystemLogLevel(Name, Value); | ||||||
|  | 							Logger_.information( | ||||||
|  | 								Poco::format("Setting log level for %s at %s", Name, Value)); | ||||||
|  | 						} | ||||||
|  | 					} | ||||||
|  | 					OK(); | ||||||
|  | 					return; | ||||||
|  | 				} | ||||||
|  | 			} else if (Command == RESTAPI::Protocol::GETLOGLEVELS) { | ||||||
|  | 				auto CurrentLogLevels = Daemon()->GetLogLevels(); | ||||||
|  | 				Poco::JSON::Object Result; | ||||||
|  | 				Poco::JSON::Array Array; | ||||||
|  | 				for (auto &[Name, Level] : CurrentLogLevels) { | ||||||
|  | 					Poco::JSON::Object Pair; | ||||||
|  | 					Pair.set(RESTAPI::Protocol::TAG, Name); | ||||||
|  | 					Pair.set(RESTAPI::Protocol::VALUE, Level); | ||||||
|  | 					Array.add(Pair); | ||||||
|  | 				} | ||||||
|  | 				Result.set(RESTAPI::Protocol::TAGLIST, Array); | ||||||
|  | 				ReturnObject(Result); | ||||||
|  | 				return; | ||||||
|  | 			} else if (Command == RESTAPI::Protocol::GETLOGLEVELNAMES) { | ||||||
|  | 				Poco::JSON::Object Result; | ||||||
|  | 				Poco::JSON::Array LevelNamesArray; | ||||||
|  | 				const Types::StringVec &LevelNames = Daemon()->GetLogLevelNames(); | ||||||
|  | 				for (const auto &i : LevelNames) | ||||||
|  | 					LevelNamesArray.add(i); | ||||||
|  | 				Result.set(RESTAPI::Protocol::LIST, LevelNamesArray); | ||||||
|  | 				ReturnObject(Result); | ||||||
|  | 				return; | ||||||
|  | 			} else if (Command == RESTAPI::Protocol::GETSUBSYSTEMNAMES) { | ||||||
|  | 				Poco::JSON::Object Result; | ||||||
|  | 				Poco::JSON::Array LevelNamesArray; | ||||||
|  | 				const Types::StringVec &SubSystemNames = Daemon()->GetSubSystems(); | ||||||
|  | 				for (const auto &i : SubSystemNames) | ||||||
|  | 					LevelNamesArray.add(i); | ||||||
|  | 				Result.set(RESTAPI::Protocol::LIST, LevelNamesArray); | ||||||
|  | 				ReturnObject(Result); | ||||||
|  | 				return; | ||||||
|  | 			} else if (Command == RESTAPI::Protocol::STATS) { | ||||||
|  |  | ||||||
|  | 			} else if (Command == RESTAPI::Protocol::RELOAD) { | ||||||
|  | 				if (Obj->has(RESTAPI::Protocol::SUBSYSTEMS) && | ||||||
|  | 					Obj->isArray(RESTAPI::Protocol::SUBSYSTEMS)) { | ||||||
|  | 					auto SubSystems = Obj->getArray(RESTAPI::Protocol::SUBSYSTEMS); | ||||||
|  | 					std::vector<std::string> Names; | ||||||
|  | 					for (const auto &i : *SubSystems) | ||||||
|  | 						Names.push_back(i.toString()); | ||||||
|  | 						std::thread	ReloadThread([Names](){ | ||||||
|  | 						std::this_thread::sleep_for(10000ms); | ||||||
|  | 						for(const auto &i:Names) { | ||||||
|  | 						    if(i=="daemon") | ||||||
|  | 						        Daemon()->Reload(); | ||||||
|  | 						    else | ||||||
|  |     							Daemon()->Reload(i); | ||||||
|  | 						} | ||||||
|  | 					 }); | ||||||
|  | 					ReloadThread.detach(); | ||||||
|  | 				} | ||||||
|  | 				OK(); | ||||||
|  | 				return; | ||||||
|  | 			} | ||||||
|  | 		} else { | ||||||
|  | 			BadRequest(RESTAPI::Errors::InvalidCommand); | ||||||
|  | 			return; | ||||||
|  | 		} | ||||||
|  | 		BadRequest(RESTAPI::Errors::MissingOrInvalidParameters); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	void RESTAPI_system_command::DoGet() { | ||||||
|  | 		std::string Arg; | ||||||
|  | 		if(HasParameter("command",Arg) && Arg=="info") { | ||||||
|  | 			Poco::JSON::Object Answer; | ||||||
|  | 			Answer.set(RESTAPI::Protocol::VERSION, Daemon()->Version()); | ||||||
|  | 			Answer.set(RESTAPI::Protocol::UPTIME, Daemon()->uptime().totalSeconds()); | ||||||
|  | 			Answer.set(RESTAPI::Protocol::START, Daemon()->startTime().epochTime()); | ||||||
|  | 			Answer.set(RESTAPI::Protocol::OS, Poco::Environment::osName()); | ||||||
|  | 			Answer.set(RESTAPI::Protocol::PROCESSORS, Poco::Environment::processorCount()); | ||||||
|  | 			Answer.set(RESTAPI::Protocol::HOSTNAME, Poco::Environment::nodeName()); | ||||||
|  |  | ||||||
|  | 			Poco::JSON::Array   Certificates; | ||||||
|  | 			auto SubSystems = Daemon()->GetFullSubSystems(); | ||||||
|  | 			std::set<std::string>   CertNames; | ||||||
|  |  | ||||||
|  | 			for(const auto &i:SubSystems) { | ||||||
|  | 			    auto Hosts=i->HostSize(); | ||||||
|  | 			    for(uint64_t j=0;j<Hosts;++j) { | ||||||
|  | 			        auto CertFileName = i->Host(j).CertFile(); | ||||||
|  | 			        if(!CertFileName.empty()) { | ||||||
|  | 			            auto InsertResult = CertNames.insert(CertFileName); | ||||||
|  | 			            if(InsertResult.second) { | ||||||
|  | 			                Poco::JSON::Object  Inner; | ||||||
|  | 			                Inner.set("filename", CertFileName); | ||||||
|  | 			                Poco::Crypto::X509Certificate   C(CertFileName); | ||||||
|  | 			                auto ExpiresOn = C.expiresOn(); | ||||||
|  | 			                Inner.set("expiresOn",ExpiresOn.timestamp().epochTime()); | ||||||
|  | 			                Certificates.add(Inner); | ||||||
|  | 			            } | ||||||
|  | 			        } | ||||||
|  | 			    } | ||||||
|  | 			} | ||||||
|  | 			Answer.set("certificates", Certificates); | ||||||
|  | 			ReturnObject(Answer); | ||||||
|  | 			return; | ||||||
|  | 		} | ||||||
|  | 		BadRequest(RESTAPI::Errors::InvalidCommand); | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										32
									
								
								src/RESTAPI_system_command.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								src/RESTAPI_system_command.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,32 @@ | |||||||
|  | // | ||||||
|  | //	License type: BSD 3-Clause License | ||||||
|  | //	License copy: https://github.com/Telecominfraproject/wlan-cloud-ucentralgw/blob/master/LICENSE | ||||||
|  | // | ||||||
|  | //	Created by Stephane Bourque on 2021-03-04. | ||||||
|  | //	Arilia Wireless Inc. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #ifndef UCENTRALGW_RESTAPI_SYSTEM_COMMAND_H | ||||||
|  | #define UCENTRALGW_RESTAPI_SYSTEM_COMMAND_H | ||||||
|  |  | ||||||
|  | #include "RESTAPI_handler.h" | ||||||
|  |  | ||||||
|  | namespace OpenWifi { | ||||||
|  | class RESTAPI_system_command : public RESTAPIHandler { | ||||||
|  |   public: | ||||||
|  |     RESTAPI_system_command(const RESTAPIHandler::BindingMap &bindings, Poco::Logger &L, RESTAPI_GenericServer & Server, bool Internal) | ||||||
|  | 		: RESTAPIHandler(bindings, L, | ||||||
|  | 						 std::vector<std::string>{Poco::Net::HTTPRequest::HTTP_POST, | ||||||
|  | 														  Poco::Net::HTTPRequest::HTTP_GET, | ||||||
|  | 														  Poco::Net::HTTPRequest::HTTP_OPTIONS}, | ||||||
|  | 														  Server, | ||||||
|  | 						 Internal) {} | ||||||
|  | 	static const std::list<const char *> PathName() { return std::list<const char *>{"/api/v1/system"};} | ||||||
|  |  | ||||||
|  | 	void DoGet() final; | ||||||
|  | 	void DoPost() final; | ||||||
|  | 	void DoPut() final {}; | ||||||
|  | 	void DoDelete() final {}; | ||||||
|  | 	}; | ||||||
|  | } | ||||||
|  | #endif // UCENTRALGW_RESTAPI_SYSTEM_COMMAND_H | ||||||
							
								
								
									
										186
									
								
								src/RESTAPI_user_handler.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										186
									
								
								src/RESTAPI_user_handler.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,186 @@ | |||||||
|  | // | ||||||
|  | // Created by stephane bourque on 2021-06-21. | ||||||
|  | // | ||||||
|  |  | ||||||
|  | #include "RESTAPI_user_handler.h" | ||||||
|  | #include "StorageService.h" | ||||||
|  | #include "Poco/JSON/Parser.h" | ||||||
|  | #include "Utils.h" | ||||||
|  | #include "RESTAPI_utils.h" | ||||||
|  | #include "RESTAPI_errors.h" | ||||||
|  |  | ||||||
|  | namespace OpenWifi { | ||||||
|  |     void RESTAPI_user_handler::DoGet() { | ||||||
|  |         std::string Id = GetBinding("id", ""); | ||||||
|  |         if(Id.empty()) { | ||||||
|  |             BadRequest(RESTAPI::Errors::MissingUserID); | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         SecurityObjects::UserInfo   UInfo; | ||||||
|  |         if(!Storage()->GetUserById(Id,UInfo)) { | ||||||
|  |             NotFound(); | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |         Poco::JSON::Object  UserInfoObject; | ||||||
|  |         UInfo.to_json(UserInfoObject); | ||||||
|  |         ReturnObject(UserInfoObject); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     void RESTAPI_user_handler::DoDelete() { | ||||||
|  |         std::string Id = GetBinding("id", ""); | ||||||
|  |         if(Id.empty()) { | ||||||
|  |             BadRequest(RESTAPI::Errors::MissingUserID); | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         SecurityObjects::UserInfo UInfo; | ||||||
|  |         if(!Storage()->GetUserById(Id,UInfo)) { | ||||||
|  |             NotFound(); | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if(!Storage()->DeleteUser(UserInfo_.userinfo.email,Id)) { | ||||||
|  |             NotFound(); | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if(AuthService()->DeleteUserFromCache(UInfo.email)) | ||||||
|  |             ; | ||||||
|  |         Logger_.information(Poco::format("Remove all tokens for '%s'", UserInfo_.userinfo.email)); | ||||||
|  |         Storage()->RevokeAllTokens(UInfo.email); | ||||||
|  |         Logger_.information(Poco::format("User '%s' deleted by '%s'.",Id,UserInfo_.userinfo.email)); | ||||||
|  |         OK(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     void RESTAPI_user_handler::DoPost() { | ||||||
|  |         std::string Id = GetBinding("id", ""); | ||||||
|  |         if(Id!="0") { | ||||||
|  |             BadRequest(RESTAPI::Errors::IdMustBe0); | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         SecurityObjects::UserInfo   UInfo; | ||||||
|  |         RESTAPI_utils::from_request(UInfo,*Request); | ||||||
|  |  | ||||||
|  |         if(UInfo.userRole == SecurityObjects::UNKNOWN) { | ||||||
|  |             BadRequest(RESTAPI::Errors::InvalidUserRole); | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         Poco::toLowerInPlace(UInfo.email); | ||||||
|  |         if(!Utils::ValidEMailAddress(UInfo.email)) { | ||||||
|  |             BadRequest(RESTAPI::Errors::InvalidEmailAddress); | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if(!UInfo.currentPassword.empty()) { | ||||||
|  |             if(!AuthService()->ValidatePassword(UInfo.currentPassword)) { | ||||||
|  |                 BadRequest(RESTAPI::Errors::InvalidPassword); | ||||||
|  |                 return; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if(UInfo.name.empty()) | ||||||
|  |             UInfo.name = UInfo.email; | ||||||
|  |  | ||||||
|  |         if(!Storage()->CreateUser(UInfo.email,UInfo)) { | ||||||
|  |             Logger_.information(Poco::format("Could not add user '%s'.",UInfo.email)); | ||||||
|  |             BadRequest(RESTAPI::Errors::RecordNotCreated); | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if(GetParameter("email_verification","false")=="true") { | ||||||
|  |             if(AuthService::VerifyEmail(UInfo)) | ||||||
|  |                 Logger_.information(Poco::format("Verification e-mail requested for %s",UInfo.email)); | ||||||
|  |             Storage()->UpdateUserInfo(UserInfo_.userinfo.email,UInfo.Id,UInfo); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if(!Storage()->GetUserByEmail(UInfo.email, UInfo)) { | ||||||
|  |             Logger_.information(Poco::format("User '%s' but not retrieved.",UInfo.email)); | ||||||
|  |             NotFound(); | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         Poco::JSON::Object  UserInfoObject; | ||||||
|  |         UInfo.to_json(UserInfoObject); | ||||||
|  |  | ||||||
|  |         ReturnObject(UserInfoObject); | ||||||
|  |  | ||||||
|  |         Logger_.information(Poco::format("User '%s' has been added by '%s')",UInfo.email, UserInfo_.userinfo.email)); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     void RESTAPI_user_handler::DoPut() { | ||||||
|  |         std::string Id = GetBinding("id", ""); | ||||||
|  |         if(Id.empty()) { | ||||||
|  |             BadRequest(RESTAPI::Errors::MissingUserID); | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         SecurityObjects::UserInfo   LocalObject; | ||||||
|  |         if(!Storage()->GetUserById(Id,LocalObject)) { | ||||||
|  |             NotFound(); | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         // some basic validations | ||||||
|  |         auto RawObject = ParseStream(); | ||||||
|  |         if(RawObject->has("userRole") && SecurityObjects::UserTypeFromString(RawObject->get("userRole").toString())==SecurityObjects::UNKNOWN) { | ||||||
|  |             BadRequest(RESTAPI::Errors::InvalidUserRole); | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         // The only valid things to change are: changePassword, name, | ||||||
|  |         if(RawObject->has("name")) | ||||||
|  |             LocalObject.name = RawObject->get("name").toString(); | ||||||
|  |         if(RawObject->has("description")) | ||||||
|  |             LocalObject.description = RawObject->get("description").toString(); | ||||||
|  |         if(RawObject->has("avatar")) | ||||||
|  |             LocalObject.avatar = RawObject->get("avatar").toString(); | ||||||
|  |         if(RawObject->has("changePassword")) | ||||||
|  |             LocalObject.changePassword = RawObject->get("changePassword").toString()=="true"; | ||||||
|  |         if(RawObject->has("owner")) | ||||||
|  |             LocalObject.owner = RawObject->get("owner").toString(); | ||||||
|  |         if(RawObject->has("location")) | ||||||
|  |             LocalObject.location = RawObject->get("location").toString(); | ||||||
|  |         if(RawObject->has("locale")) | ||||||
|  |             LocalObject.locale = RawObject->get("locale").toString(); | ||||||
|  |         if(RawObject->has("userRole")) | ||||||
|  |             LocalObject.userRole = SecurityObjects::UserTypeFromString(RawObject->get("userRole").toString()); | ||||||
|  |         if(RawObject->has("suspended")) | ||||||
|  |             LocalObject.suspended = RawObject->get("suspended").toString()=="true"; | ||||||
|  |         if(RawObject->has("blackListed")) | ||||||
|  |             LocalObject.blackListed = RawObject->get("blackListed").toString()=="true"; | ||||||
|  |         if(RawObject->has("notes")) { | ||||||
|  |             SecurityObjects::NoteInfoVec NIV; | ||||||
|  |             NIV = RESTAPI_utils::to_object_array<SecurityObjects::NoteInfo>(RawObject->get("notes").toString()); | ||||||
|  |             for(auto const &i:NIV) { | ||||||
|  |                 SecurityObjects::NoteInfo   ii{.created=(uint64_t)std::time(nullptr), .createdBy=UserInfo_.userinfo.email, .note=i.note}; | ||||||
|  |                 LocalObject.notes.push_back(ii); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         if(RawObject->has("currentPassword")) { | ||||||
|  |             if(!AuthService()->ValidatePassword(RawObject->get("currentPassword").toString())) { | ||||||
|  |                 BadRequest(RESTAPI::Errors::InvalidPassword); | ||||||
|  |                 return; | ||||||
|  |             } | ||||||
|  |             if(!AuthService()->SetPassword(RawObject->get("currentPassword").toString(),LocalObject)) { | ||||||
|  |                 BadRequest(RESTAPI::Errors::PasswordRejected); | ||||||
|  |                 return; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if(GetParameter("email_verification","false")=="true") { | ||||||
|  |             if(AuthService::VerifyEmail(LocalObject)) | ||||||
|  |                 Logger_.information(Poco::format("Verification e-mail requested for %s",LocalObject.email)); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if(Storage()->UpdateUserInfo(UserInfo_.userinfo.email,Id,LocalObject)) { | ||||||
|  |             Poco::JSON::Object  ModifiedObject; | ||||||
|  |             LocalObject.to_json(ModifiedObject); | ||||||
|  |             ReturnObject(ModifiedObject); | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |         BadRequest(RESTAPI::Errors::RecordNotUpdated); | ||||||
|  |     } | ||||||
|  | } | ||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user