mirror of
				https://github.com/Telecominfraproject/wlan-cloud-ucentralsec.git
				synced 2025-10-31 02:37:56 +00:00 
			
		
		
		
	Compare commits
	
		
			376 Commits
		
	
	
		
			release/v2
			...
			v2.5.0
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 96b712eba1 | ||
|   | 8b45bfd812 | ||
|   | e0a57a4846 | ||
|   | a72e286a83 | ||
|   | 2fb80c68dd | ||
|   | 0652c13139 | ||
|   | feb5ff1f2c | ||
|   | 5a9a62ff7e | ||
|   | bd331913e1 | ||
|   | 5756e31ae6 | ||
|   | 884b91af63 | ||
|   | fed43efadf | ||
|   | 48d3e56b79 | ||
|   | a0f9abea88 | ||
|   | 3e0ecda9d6 | ||
|   | ad38d8e84c | ||
|   | 27c581750c | ||
|   | e09ee35940 | ||
|   | 3c4d9612d3 | ||
|   | 6485b2426c | ||
|   | 3d3dbc6b4d | ||
|   | 8965b3c590 | ||
|   | 77941c4775 | ||
|   | ef9cd80df6 | ||
|   | 042f7619ec | ||
|   | 8a371d7eaf | ||
|   | ac92c7da85 | ||
|   | 7a8449efbf | ||
|   | 8365aa5463 | ||
|   | 1c4f961971 | ||
|   | 19c92edfcc | ||
|   | 3fd6e6b849 | ||
|   | bc6d8a8a79 | ||
|   | 29da9b4b8e | ||
|   | b3f1f35bb4 | ||
|   | a9bd44b3b2 | ||
|   | 01f457dd0c | ||
|   | f27f036e62 | ||
|   | 27f8d06cab | ||
|   | 7960cda46e | ||
|   | 9907f91c49 | ||
|   | b3b98d90ca | ||
|   | ef947c3e33 | ||
|   | a5eb0d792b | ||
|   | 151585f6e5 | ||
|   | 7fc7daf595 | ||
|   | ebe2df4dc7 | ||
|   | 9dcc19f4fe | ||
|   | 984ba88341 | ||
|   | b14eba63c3 | ||
|   | b8c02906ea | ||
|   | f3c3539f62 | ||
|   | 5fef83d726 | ||
|   | 874e28ffff | ||
|   | f84b8c4b5c | ||
|   | ae6a986e57 | ||
|   | 04cffd13c8 | ||
|   | 28635dcbdd | ||
|   | 6a67bf80af | ||
|   | 9460cc1e5b | ||
|   | 6d20c8408f | ||
|   | fd21917a93 | ||
|   | c47e9bc98d | ||
|   | 30431ab954 | ||
|   | 76175d8bbb | ||
|   | 36e16e3c8e | ||
|   | cc4e5848a5 | ||
|   | 8425950da7 | ||
|   | cf903a57ab | ||
|   | d38e4ca2fc | ||
|   | 584254cb07 | ||
|   | dcf7ff5f48 | ||
|   | 1039a53925 | ||
|   | a80bcd16d8 | ||
|   | fce18f6238 | ||
|   | f8c6dad974 | ||
|   | eec8662a3c | ||
|   | f26c6e1454 | ||
|   | 08a50db13c | ||
|   | 7b741048ff | ||
|   | 7fcd451e3b | ||
|   | c545be6ae9 | ||
|   | b1f489bf4f | ||
|   | 92910fe0c5 | ||
|   | eda30b3dc3 | ||
|   | 51dd7bdfa7 | ||
|   | 2eccf1ef06 | ||
|   | b37edca02c | ||
|   | e831619673 | ||
|   | 78ae79b1d5 | ||
|   | 2bc6d7c325 | ||
|   | 0f6a95a330 | ||
|   | 83a1d80d77 | ||
|   | 84bcb28328 | ||
|   | 4d03faf523 | ||
|   | 73096153b4 | ||
|   | 1cf9894f7d | ||
|   | f54cd95fc4 | ||
|   | 882226ccdb | ||
|   | e5f10ccd17 | ||
|   | c500ae36b1 | ||
|   | 6ead810ec6 | ||
|   | c6ac384cdb | ||
|   | f28e07a615 | ||
|   | c7c5401bc2 | ||
|   | 6264c7f3bb | ||
|   | c078bdb555 | ||
|   | 81131b8038 | ||
|   | 4633db416d | ||
|   | 6c14337333 | ||
|   | 784fc3256b | ||
|   | 2c513d8374 | ||
|   | d202b9e365 | ||
|   | b869da3b09 | ||
|   | f31195e854 | ||
|   | ec4ab520d8 | ||
|   | a9ade83094 | ||
|   | 977742d802 | ||
|   | b69b90b243 | ||
|   | ec0aa4e15a | ||
|   | 5fc313aa50 | ||
|   | a3975829e4 | ||
|   | e8cf7a6f21 | ||
|   | d27441d793 | ||
|   | ae5c32a8ec | ||
|   | 96c684fe5e | ||
|   | 8609990551 | ||
|   | 4566bb942c | ||
|   | e5d6f42433 | ||
|   | 524f79e825 | ||
|   | be46b46340 | ||
|   | 5ff0841112 | ||
|   | a6c115adb5 | ||
|   | 4d474fe92c | ||
|   | 4b46e0c9c9 | ||
|   | de0ddc769b | ||
|   | 4a11602fb9 | ||
|   | ef1f7098a6 | ||
|   | 38eebe6162 | ||
|   | 5124ed016c | ||
|   | fb632b6ce1 | ||
|   | 775d0c0a65 | ||
|   | fb2ddaa136 | ||
|   | f51e00c50c | ||
|   | da49bebb15 | ||
|   | 916d5cdf13 | ||
|   | 5eecfbfd30 | ||
|   | 32a5c81f1d | ||
|   | a72189f854 | ||
|   | 2be40d5d17 | ||
|   | f8407f7b7c | ||
|   | 2ec792a74b | ||
|   | 72f0b11f81 | ||
|   | 00965b78c7 | ||
|   | b2c06affa8 | ||
|   | 7b467850b6 | ||
|   | be89574363 | ||
|   | e4d3855251 | ||
|   | 57bd712634 | ||
|   | 797f4e9c80 | ||
|   | 309a856cd0 | ||
|   | 937a20beea | ||
|   | 2217a428c1 | ||
|   | ec82bdec24 | ||
|   | 40faa84d2b | ||
|   | cb3efcecb5 | ||
|   | e11d955529 | ||
|   | 5a4eafee7d | ||
|   | 2df45c26a4 | ||
|   | 311786f8d8 | ||
|   | 829de62967 | ||
|   | 55d1f9571d | ||
|   | 80a520c4cc | ||
|   | 040623fc8c | ||
|   | dc6eaaeb89 | ||
|   | 4953aa02dc | ||
|   | e794647469 | ||
|   | e87bdc9440 | ||
|   | a8f59e0fb5 | ||
|   | 2730a8c5e4 | ||
|   | b3ef448628 | ||
|   | 13fe7d295b | ||
|   | ef1eb190a2 | ||
|   | 370d4aca47 | ||
|   | a575d95b91 | ||
|   | 0477ab5349 | ||
|   | 730f8d169a | ||
|   | be7f50ccbb | ||
|   | 8d681988a9 | ||
|   | 8842f23a8e | ||
|   | 5e5150a73f | ||
|   | 9e79b73e20 | ||
|   | eb4dfc25f2 | ||
|   | bedec254c5 | ||
|   | 96a566a2b5 | ||
|   | ad2eb1711e | ||
|   | 7244bcb455 | ||
|   | 1db5201418 | ||
|   | 1bc635f553 | ||
|   | 257ac42d7c | ||
|   | acb38e5313 | ||
|   | 7940f0bd85 | ||
|   | 62c06d0bad | ||
|   | 494a199610 | ||
|   | 5307b0b35a | ||
|   | c58728f38e | ||
|   | 1f09c3b619 | ||
|   | d9c6388502 | ||
|   | 5e35906aec | ||
|   | 773618ae07 | ||
|   | cca4441ac7 | ||
|   | 730ca7b292 | ||
|   | 5b4dbb088f | ||
|   | bc11a19ee4 | ||
|   | c835e4d0b9 | ||
|   | f1a2ba90f6 | ||
|   | 5b96ef396f | ||
|   | c204d34bf4 | ||
|   | 4b982bf64b | ||
|   | 37298cc600 | ||
|   | 03619cc900 | ||
|   | f4fc6975e1 | ||
|   | 1f1d596c5a | ||
|   | a5802bf631 | ||
|   | 6471eabc82 | ||
|   | ab6fbaca11 | ||
|   | 1e8e5c18b2 | ||
|   | 3cf23af068 | ||
|   | 1a0b549731 | ||
|   | a835d2e571 | ||
|   | ff7455af24 | ||
|   | 48610bac5d | ||
|   | 7bd5b4d4e6 | ||
|   | e1a51c2a91 | ||
|   | cd0222f765 | ||
|   | 12fddd8bc4 | ||
|   | 9095d831db | ||
|   | 4e8f97df9b | ||
|   | 28808eae93 | ||
|   | 6c24a23863 | ||
|   | 5931c91054 | ||
|   | 9d956c13f7 | ||
|   | ea1adde361 | ||
|   | eaac1f1625 | ||
|   | c5f4c067bb | ||
|   | 31a9e4564b | ||
|   | a9affc29bb | ||
|   | 65fc0a1d10 | ||
|   | 82c01ce438 | ||
|   | 5f900883e8 | ||
|   | e97b8e64be | ||
|   | 6c90c75708 | ||
|   | a3d86c7cf9 | ||
|   | 50b6ac9522 | ||
|   | 15b947a34d | ||
|   | 160bd00a99 | ||
|   | 3c7daa537a | ||
|   | c5bab1d749 | ||
|   | 96c3244be0 | ||
|   | 7e4b515f60 | ||
|   | a63f80e497 | ||
|   | 2eae6cc73c | ||
|   | 96f215b3c2 | ||
|   | 9551384358 | ||
|   | b21c5c5e00 | ||
|   | 031d35256c | ||
|   | 5738fa47bb | ||
|   | fe17650333 | ||
|   | 7636568fb4 | ||
|   | c0ef77eb53 | ||
|   | 00742a5d0a | ||
|   | a96f673380 | ||
|   | 53ecdb471e | ||
|   | f80a0c5007 | ||
|   | 9e7d7ba67d | ||
|   | b508c0d054 | ||
|   | 79788dab44 | ||
|   | 8dec946c45 | ||
|   | 43ea5ac424 | ||
|   | 328ff158cb | ||
|   | 2b89d843c3 | ||
|   | 45a50483be | ||
|   | c8ae94a062 | ||
|   | 7b19143d6f | ||
|   | bc0c889098 | ||
|   | 6f8f81866f | ||
|   | f213c99816 | ||
|   | 423aca9892 | ||
|   | 4840ff887f | ||
|   | 61140868b5 | ||
|   | 56308dfa5e | ||
|   | 8ff25257ca | ||
|   | 9ca6853791 | ||
|   | 064c486158 | ||
|   | 0e58d04b32 | ||
|   | d695614567 | ||
|   | ed13053648 | ||
|   | 5cb9e7566e | ||
|   | b00938eab0 | ||
|   | b9495264ee | ||
|   | 22ac42221e | ||
|   | 559ce2dc88 | ||
|   | 75fbabdc0b | ||
|   | b5b7d27abd | ||
|   | 1a7bf8dba7 | ||
|   | 35bc0d8a5c | ||
|   | b8ff262e01 | ||
|   | c577a4d23a | ||
|   | 0f26f359dd | ||
|   | 117e820d1e | ||
|   | 670e61640f | ||
|   | 75aaf4f45b | ||
|   | 7161175f03 | ||
|   | cc83b29756 | ||
|   | 581cc76625 | ||
|   | 184c30d7bb | ||
|   | 6057b421ac | ||
|   | fcd8157020 | ||
|   | cd7a6f4ebd | ||
|   | 615bf04df6 | ||
|   | 819c32edcf | ||
|   | d805fd2d50 | ||
|   | 217c680fce | ||
|   | 796eed2e2f | ||
|   | 29226c81e4 | ||
|   | f7cb82b2f2 | ||
|   | 9119b65516 | ||
|   | 1540df93e8 | ||
|   | 66832e1581 | ||
|   | 84238702cf | ||
|   | fd63f7fd31 | ||
|   | 8b427e30a2 | ||
|   | af6dff3f57 | ||
|   | 7070da80f7 | ||
|   | 36f046e589 | ||
|   | 166fa840d2 | ||
|   | f4865c933a | ||
|   | 6531d550c2 | ||
|   | 1e8bf5063f | ||
|   | be8b55f5fd | ||
|   | c28f0cf929 | ||
|   | 405ca345be | ||
|   | 85ffd8b68c | ||
|   | 20227b0cd9 | ||
|   | e66a498889 | ||
|   | a65d22ccb3 | ||
|   | 0013f47cbf | ||
|   | 69da5c17cf | ||
|   | a199d4e095 | ||
|   | 448b5949d8 | ||
|   | 82a6d61724 | ||
|   | 21ba9f2bb1 | ||
|   | 9debb06f21 | ||
|   | 1af2afc530 | ||
|   | fc454ad4f9 | ||
|   | 99c8eb2900 | ||
|   | 18591e2add | ||
|   | 0e0cb8a0c7 | ||
|   | f7e791c125 | ||
|   | 9e6ef8bb1b | ||
|   | 956ec15532 | ||
|   | 8721354284 | ||
|   | 659fbf9dc1 | ||
|   | 1209b772ee | ||
|   | e0e8f5fae6 | ||
|   | d6e5f379a0 | ||
|   | 4dda1ee5b3 | ||
|   | abd65c347c | ||
|   | 767c0fb9f5 | ||
|   | dc3d6042d5 | ||
|   | fcedf63ef9 | ||
|   | 30861ed934 | ||
|   | ee537b3383 | ||
|   | 7d9d985142 | ||
|   | daa060c849 | ||
|   | f25047cbe7 | 
							
								
								
									
										79
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										79
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							| @@ -25,45 +25,46 @@ jobs: | ||||
|       DOCKER_REGISTRY_URL: tip-tip-wlan-cloud-ucentral.jfrog.io | ||||
|       DOCKER_REGISTRY_USERNAME: ucentral | ||||
|     steps: | ||||
|     - uses: actions/checkout@v2 | ||||
|  | ||||
|     - name: Build Docker image | ||||
|       run: docker build -t wlan-cloud-owsec:${{ github.sha }} . | ||||
|  | ||||
|     - name: Tag Docker image | ||||
|       run: | | ||||
|         TAGS="${{ github.sha }}" | ||||
|  | ||||
|         if [[ ${GITHUB_REF} == "refs/heads/"* ]] | ||||
|         then | ||||
|           CURRENT_TAG=$(echo ${GITHUB_REF#refs/heads/} | tr '/' '-') | ||||
|           TAGS="$TAGS $CURRENT_TAG" | ||||
|         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 | ||||
|  | ||||
|         echo "Result tags: $TAGS" | ||||
|  | ||||
|         for tag in $TAGS; do | ||||
|           docker tag wlan-cloud-owsec:${{ github.sha }} ${{ env.DOCKER_REGISTRY_URL }}/owsec:$tag | ||||
|         done | ||||
|  | ||||
|     - name: Log into Docker registry | ||||
|       if: startsWith(github.ref, 'refs/tags/') || startsWith(github.ref, 'refs/pull/') || github.ref == 'refs/heads/main' | ||||
|       uses: docker/login-action@v1 | ||||
|     - name: Checkout actions repo | ||||
|       uses: actions/checkout@v2 | ||||
|       with: | ||||
|         registry: ${{ env.DOCKER_REGISTRY_URL }} | ||||
|         username: ${{ env.DOCKER_REGISTRY_USERNAME }} | ||||
|         password: ${{ secrets.DOCKER_REGISTRY_PASSWORD }} | ||||
|         repository: Telecominfraproject/.github | ||||
|         path: github | ||||
|  | ||||
|     - name: Push Docker images | ||||
|       if: startsWith(github.ref, 'refs/tags/') || startsWith(github.ref, 'refs/pull/') || github.ref == 'refs/heads/main' | ||||
|     - name: Build and push Docker image | ||||
|       uses: ./github/composite-actions/docker-image-build | ||||
|       with: | ||||
|         image_name: owsec | ||||
|         registry: tip-tip-wlan-cloud-ucentral.jfrog.io | ||||
|         registry_user: ucentral | ||||
|         registry_password: ${{ secrets.DOCKER_REGISTRY_PASSWORD }} | ||||
|  | ||||
|   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: | | ||||
|         docker images | grep ${{ env.DOCKER_REGISTRY_URL }}/owsec | awk -F ' ' '{print $1":"$2}' | xargs -I {} docker push {} | ||||
|         echo ::set-output name=branch::$(echo ${GITHUB_BASE_REF##*/}) | ||||
|         echo ::set-output name=owgw_branch::$(echo ${GITHUB_BASE_REF##*/} | sed 's/main/master/g') | ||||
|  | ||||
|     - name: Checkout actions repo | ||||
|       uses: actions/checkout@v2 | ||||
|       with: | ||||
|         repository: Telecominfraproject/.github | ||||
|         path: github | ||||
|  | ||||
|     - name: Trigger testing of OpenWifi Docker Compose deployment and wait for result | ||||
|       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: '{"owgw_version": "${{ env.OWGW_BASE_BRANCH }}", "owgwui_version": "${{ env.BASE_BRANCH }}", "owsec_version": "${{ github.sha }}", "owfms_version": "${{ env.BASE_BRANCH }}", "owprov_version": "main", "owprovui_version": "main"}' | ||||
|   | ||||
							
								
								
									
										24
									
								
								.github/workflows/enforce-jira-issue-key.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								.github/workflows/enforce-jira-issue-key.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,24 @@ | ||||
| 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 }} | ||||
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -18,3 +18,4 @@ _deps | ||||
| *.csr | ||||
| /cmake-build/ | ||||
| /smake-build-debug/ | ||||
| test_scripts/curl/result.json | ||||
|   | ||||
							
								
								
									
										111
									
								
								CMakeLists.txt
									
									
									
									
									
								
							
							
						
						
									
										111
									
								
								CMakeLists.txt
									
									
									
									
									
								
							| @@ -1,5 +1,5 @@ | ||||
| cmake_minimum_required(VERSION 3.13) | ||||
| project(owsec VERSION 2.2.0) | ||||
| project(owsec VERSION 2.5.0) | ||||
|  | ||||
| set(CMAKE_CXX_STANDARD 17) | ||||
|  | ||||
| @@ -20,19 +20,32 @@ endif() | ||||
|  | ||||
| # Auto build increment. You must define BUILD_INCREMENT with cmake -DBUILD_INCREMENT=1 | ||||
| if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/build) | ||||
|     file(READ build BUILD_NUM) | ||||
|     file(READ ${CMAKE_CURRENT_SOURCE_DIR}/build BUILD_NUM) | ||||
|     if(BUILD_INCREMENT) | ||||
|         MATH(EXPR BUILD_NUM "${BUILD_NUM}+1") | ||||
|         file(WRITE build ${BUILD_NUM}) | ||||
|         file(WRITE ${CMAKE_CURRENT_SOURCE_DIR}/build ${BUILD_NUM}) | ||||
|     endif() | ||||
| else() | ||||
|     set(BUILD_NUM 1) | ||||
|     file(WRITE build ${BUILD_NUM}) | ||||
|     file(WRITE ${CMAKE_CURRENT_SOURCE_DIR}/build ${BUILD_NUM}) | ||||
| 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) | ||||
|  | ||||
| add_definitions(-DAPP_VERSION="${CMAKE_PROJECT_VERSION}" -DBUILD_NUMBER="${BUILD_NUM}") | ||||
| add_definitions(-DTIP_SECURITY_SERVICE="1") | ||||
|  | ||||
| set(Boost_USE_STATIC_LIBS OFF) | ||||
| @@ -41,48 +54,80 @@ set(Boost_USE_STATIC_RUNTIME OFF) | ||||
| find_package(Boost REQUIRED system) | ||||
| find_package(OpenSSL REQUIRED) | ||||
| find_package(ZLIB REQUIRED) | ||||
|  | ||||
| find_package(AWSSDK     REQUIRED COMPONENTS sns) | ||||
| find_package(nlohmann_json  REQUIRED) | ||||
| find_package(CppKafka REQUIRED) | ||||
| find_package(PostgreSQL REQUIRED) | ||||
| find_package(MySQL REQUIRED) | ||||
| 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 | ||||
|         build | ||||
|         src/ow_version.h.in | ||||
|         src/framework/CountryCodes.h | ||||
|         src/framework/KafkaTopics.h | ||||
|         src/framework/MicroService.h | ||||
|         src/framework/orm.h | ||||
|         src/framework/RESTAPI_errors.h | ||||
|         src/framework/RESTAPI_protocol.h | ||||
|         src/framework/StorageClass.h | ||||
|         src/framework/uCentral_Protocol.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/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/SpecialUserHelpers.h | ||||
|         src/AuthService.h src/AuthService.cpp | ||||
|                 src/KafkaManager.h src/KafkaManager.cpp | ||||
|         src/StorageService.cpp src/StorageService.h | ||||
|                 src/Utils.cpp src/Utils.h | ||||
|                 src/storage_setup.cpp | ||||
|                 src/storage_tables.cpp src/SMTPMailerService.cpp src/SMTPMailerService.h | ||||
|                 src/RESTAPI_users_handler.cpp src/RESTAPI_users_handler.h | ||||
|                 src/RESTAPI_user_handler.cpp src/RESTAPI_user_handler.h | ||||
|                 src/RESTAPI_action_links.cpp src/RESTAPI_action_links.h src/storage_users.cpp | ||||
|                 src/RESTAPI_InternalServer.cpp src/RESTAPI_InternalServer.h | ||||
|                 src/RESTAPI_validateToken_handler.cpp src/RESTAPI_validateToken_handler.h | ||||
|                 src/RESTAPI_systemEndpoints_handler.cpp src/RESTAPI_systemEndpoints_handler.h | ||||
|                 src/RESTAPI_AssetServer.cpp src/RESTAPI_AssetServer.h | ||||
|                 src/RESTAPI_avatarHandler.cpp src/RESTAPI_avatarHandler.h | ||||
|                 src/storage_avatar.cpp src/storage_avatar.h src/storage_users.h | ||||
|                 src/OpenWifiTypes.h src/RESTAPI_email_handler.cpp src/RESTAPI_email_handler.h | ||||
|                 src/storage_tokens.cpp | ||||
|                 src/RESTAPI_GenericServer.h src/RESTAPI_GenericServer.cpp | ||||
|                 src/RESTAPI_errors.h | ||||
|                 ) | ||||
|         src/SMTPMailerService.cpp src/SMTPMailerService.h | ||||
|         src/SMSSender.cpp src/SMSSender.h | ||||
|         src/MFAServer.cpp src/MFAServer.h | ||||
|         src/SMS_provider_aws.cpp src/SMS_provider_aws.h | ||||
|         src/SMS_provider.cpp src/SMS_provider.h | ||||
|         src/SMS_provider_twilio.cpp src/SMS_provider_twilio.h | ||||
|         src/ActionLinkManager.cpp src/ActionLinkManager.h | ||||
|         src/ACLProcessor.h | ||||
|         src/framework/OpenWifiTypes.h | ||||
|         src/storage/orm_users.cpp src/storage/orm_users.h | ||||
|         src/storage/orm_tokens.cpp src/storage/orm_tokens.h | ||||
|         src/storage/orm_preferences.cpp src/storage/orm_preferences.h | ||||
|         src/storage/orm_actionLinks.cpp src/storage/orm_actionLinks.h | ||||
|         src/storage/orm_avatar.cpp src/storage/orm_avatar.h | ||||
|         src/SpecialUserHelpers.h | ||||
|         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) | ||||
|  | ||||
| if(NOT SMALL_BUILD) | ||||
|     target_link_libraries(owsec PUBLIC | ||||
|             ${Poco_LIBRARIES} ${Boost_LIBRARIES} ${MySQL_LIBRARIES}  ${ZLIB_LIBRARIES} | ||||
|             CppKafka::cppkafka | ||||
|             CppKafka::cppkafka ${AWSSDK_LINK_LIBRARIES} | ||||
|             ) | ||||
|     if(UNIX AND NOT APPLE) | ||||
|         target_link_libraries(owsec PUBLIC PocoJSON) | ||||
|   | ||||
							
								
								
									
										86
									
								
								Dockerfile
									
									
									
									
									
								
							
							
						
						
									
										86
									
								
								Dockerfile
									
									
									
									
									
								
							| @@ -1,15 +1,27 @@ | ||||
| FROM alpine AS builder | ||||
| FROM alpine:3.15 AS build-base | ||||
|  | ||||
| RUN apk add --update --no-cache \ | ||||
|     openssl openssh \ | ||||
|     ncurses-libs \ | ||||
|     bash util-linux coreutils curl \ | ||||
|     make cmake gcc g++ libstdc++ libgcc git zlib-dev \ | ||||
|     openssl-dev boost-dev unixodbc-dev postgresql-dev mariadb-dev \ | ||||
|     apache2-utils yaml-dev apr-util-dev \ | ||||
|     librdkafka-dev | ||||
|     make cmake g++ git \ | ||||
|     unixodbc-dev postgresql-dev mariadb-dev \ | ||||
|     librdkafka-dev boost-dev openssl-dev \ | ||||
|     zlib-dev nlohmann-json \ | ||||
|     curl-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 | ||||
|  | ||||
| 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 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 | ||||
|  | ||||
| WORKDIR /cppkafka | ||||
| @@ -19,16 +31,48 @@ RUN cmake .. | ||||
| RUN cmake --build . --config Release -j8 | ||||
| RUN cmake --build . --target install | ||||
|  | ||||
| WORKDIR /poco | ||||
| FROM build-base AS json-schema-validator-build | ||||
|  | ||||
| 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 | ||||
| WORKDIR cmake-build | ||||
| 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 . --target install | ||||
|  | ||||
| FROM build-base AS owsec-build | ||||
|  | ||||
| ADD CMakeLists.txt build /owsec/ | ||||
| ADD cmake /owsec/cmake | ||||
| 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 | ||||
|  | ||||
| WORKDIR /owsec | ||||
| RUN mkdir cmake-build | ||||
| @@ -36,7 +80,7 @@ WORKDIR /owsec/cmake-build | ||||
| RUN cmake .. | ||||
| RUN cmake --build . --config Release -j8 | ||||
|  | ||||
| FROM alpine | ||||
| FROM alpine:3.15 | ||||
|  | ||||
| ENV OWSEC_USER=owsec \ | ||||
|     OWSEC_ROOT=/owsec-data \ | ||||
| @@ -48,16 +92,28 @@ RUN addgroup -S "$OWSEC_USER" && \ | ||||
| RUN mkdir /openwifi | ||||
| RUN mkdir -p "$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/ | ||||
|  | ||||
| COPY owsec.properties.tmpl ${OWSEC_CONFIG}/ | ||||
| RUN apk add --update --no-cache librdkafka su-exec gettext ca-certificates bash jq curl \ | ||||
|     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 wait-for-postgres.sh / | ||||
| 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 | ||||
|  | ||||
| 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 | ||||
|  | ||||
| ENTRYPOINT ["/docker-entrypoint.sh"] | ||||
|   | ||||
							
								
								
									
										103
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										103
									
								
								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) | ||||
| - 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. Loog for the `setgateway`  | ||||
| function. | ||||
| 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.  | ||||
| Look for the `setgateway` function. | ||||
|  | ||||
| ## Firewall Considerations | ||||
| The entire uCentral systems uses several MicroServices. In order for the whole system to work, you should provide the following port | ||||
| access | ||||
| access: | ||||
|  | ||||
| - Security | ||||
|   - Properties file: owsec.properties | ||||
| @@ -28,14 +28,21 @@ access | ||||
|     - ALB: 16101 | ||||
|  | ||||
| - Gateway: | ||||
|   - Properties file: ucentralgw.properties | ||||
|   - Properties file: owgw.properties | ||||
|   - Ports | ||||
|     - Public: 16002 | ||||
|     - Private: 17002 | ||||
|     - ALB: 16102 | ||||
|  | ||||
| - Firmware: | ||||
|   - Properties file: ucentralfms.properties | ||||
|   - Properties file: owfms.properties | ||||
|   - Ports | ||||
|     - Public: 16004 | ||||
|     - Private: 17004 | ||||
|     - ALB: 16104 | ||||
|  | ||||
| - Provisioning: | ||||
|   - Properties file: owprov.properties | ||||
|   - Ports | ||||
|     - Public: 16004 | ||||
|     - Private: 17004 | ||||
| @@ -79,7 +86,6 @@ 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 | ||||
| how hashes work and why they are safe. | ||||
|  | ||||
|  | ||||
| ### `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  | ||||
| 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) | ||||
| @@ -92,6 +98,40 @@ to get a sample. The default is | ||||
| ### `authentication.oldpasswords` | ||||
| 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 | ||||
| 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. | ||||
| @@ -160,10 +200,11 @@ Here are other important values you must set. | ||||
| openwifi.system.data = $OWSEC_ROOT/data | ||||
| openwifi.system.uri.private = https://localhost:17001 | ||||
| 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.service.key = $OWSEC_ROOT/certs/restapi-key.pem | ||||
| openwifi.service.key.password = mypassword | ||||
| ``` | ||||
|  | ||||
| #### `openwifi.system.data` | ||||
| The location of some important data files including the user name database. | ||||
|  | ||||
| @@ -173,3 +214,51 @@ This is the FQDN used internally between services. | ||||
| #### `openwifi.system.uri.public` | ||||
| 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.   | ||||
|  | ||||
| ```asm | ||||
| smssender.provider = aws | ||||
| smssender.aws.secretkey = *************************************** | ||||
| smssender.aws.accesskey = *************************************** | ||||
| smssender.aws.region = ************** | ||||
| ``` | ||||
|  | ||||
| #### Twilio | ||||
| For Twilio, you must provide the following | ||||
|  | ||||
| ```asm | ||||
| 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. | ||||
|  | ||||
| ```asm | ||||
| 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 down load the Goole Authenticator  | ||||
| on any other app that support the TOTP protocol. You should include the following in your configuration | ||||
|  | ||||
| ```asm | ||||
| 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_KEY=${RESTAPI_HOST_KEY:-"\$OWSEC_ROOT/certs/restapi-key.pem"} \ | ||||
|   RESTAPI_HOST_KEY_PASSWORD=${RESTAPI_HOST_KEY_PASSWORD:-"mypassword"} \ | ||||
|   RESTAPI_WWWASSETS=${RESTAPI_WWWASSETS:-"\$OWSEC_ROOT/wwwassets"} \ | ||||
|   RESTAPI_WWWASSETS=${RESTAPI_WWWASSETS:-"\$OWSEC_ROOT/persist/wwwassets"} \ | ||||
|   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_CERT=${INTERNAL_RESTAPI_HOST_CERT:-"\$OWSEC_ROOT/certs/restapi-cert.pem"} \ | ||||
| @@ -25,13 +25,25 @@ if [[ "$TEMPLATE_CONFIG" = 'true' && ! -f "$OWSEC_CONFIG"/owsec.properties ]]; t | ||||
|   SYSTEM_URI_UI=${SYSTEM_URI_UI:-"http://localhost"} \ | ||||
|   SERVICE_KEY=${SERVICE_KEY:-"\$OWSEC_ROOT/certs/restapi-key.pem"} \ | ||||
|   SERVICE_KEY_PASSWORD=${SERVICE_KEY_PASSWORD:-"mypassword"} \ | ||||
|   MAILER_HOSTNAME=${MAILER_HOSTNAME:-"smtp.gmail.com"} \ | ||||
|   MAILER_USERNAME=${MAILER_USERNAME:-"************************"} \ | ||||
|   MAILER_PASSWORD=${MAILER_PASSWORD:-"************************"} \ | ||||
|   SMSSENDER_ENABLED=${SMSSENDER_ENABLED:-"false"} \ | ||||
|   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:-""} \ | ||||
|   MAILER_ENABLED=${MAILER_ENABLED:-"false"} \ | ||||
|   MAILER_HOSTNAME=${MAILER_HOSTNAME:-"localhost"} \ | ||||
|   MAILER_USERNAME=${MAILER_USERNAME:-""} \ | ||||
|   MAILER_PASSWORD=${MAILER_PASSWORD:-""} \ | ||||
|   MAILER_SENDER=${MAILER_SENDER:-"OpenWIFI"} \ | ||||
|   MAILER_PORT=${MAILER_PORT:-"587"} \ | ||||
|   MAILER_TEMPLATES=${MAILER_TEMPLATES:-"\$OWSEC_ROOT/persist/templates"} \ | ||||
|   KAFKA_ENABLE=${KAFKA_ENABLE:-"true"} \ | ||||
|   KAFKA_BROKERLIST=${KAFKA_BROKERLIST:-"localhost:9092"} \ | ||||
|   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_POSTGRESQL_HOST=${STORAGE_TYPE_POSTGRESQL_HOST:-"localhost"} \ | ||||
|   STORAGE_TYPE_POSTGRESQL_USERNAME=${STORAGE_TYPE_POSTGRESQL_USERNAME:-"owsec"} \ | ||||
| @@ -43,7 +55,25 @@ if [[ "$TEMPLATE_CONFIG" = 'true' && ! -f "$OWSEC_CONFIG"/owsec.properties ]]; t | ||||
|   STORAGE_TYPE_MYSQL_PASSWORD=${STORAGE_TYPE_MYSQL_PASSWORD:-"owsec"} \ | ||||
|   STORAGE_TYPE_MYSQL_DATABASE=${STORAGE_TYPE_MYSQL_DATABASE:-"owsec"} \ | ||||
|   STORAGE_TYPE_MYSQL_PORT=${STORAGE_TYPE_MYSQL_PORT:-"3306"} \ | ||||
|   envsubst < $OWSEC_CONFIG/owsec.properties.tmpl > $OWSEC_CONFIG/owsec.properties | ||||
|   envsubst < /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 | ||||
|  | ||||
| if [ "$1" = '/openwifi/owsec' -a "$(id -u)" = '0' ]; then | ||||
|   | ||||
| @@ -5,14 +5,14 @@ name: owsec | ||||
| version: 0.1.0 | ||||
| dependencies: | ||||
| - name: postgresql | ||||
|   repository: https://charts.bitnami.com/bitnami | ||||
|   repository: https://tip.jfrog.io/artifactory/tip-wlan-cloud-ucentral-helm/ | ||||
|   version: 10.9.2 | ||||
|   condition: postgresql.enabled | ||||
| - name: mysql | ||||
|   repository: https://charts.bitnami.com/bitnami | ||||
|   repository: https://tip.jfrog.io/artifactory/tip-wlan-cloud-ucentral-helm/ | ||||
|   version: 8.8.3 | ||||
|   condition: mysql.enabled | ||||
| - name: mariadb | ||||
|   repository: https://charts.bitnami.com/bitnami | ||||
|   repository: https://tip.jfrog.io/artifactory/tip-wlan-cloud-ucentral-helm/ | ||||
|   version: 9.4.2 | ||||
|   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`: | ||||
|  | ||||
| ```bash | ||||
| $ helm install --name my-release git+https://github.com/Telecominfraproject/wlan-cloud-ucentralsec@helm?ref=main | ||||
| $ helm install --name my-release git+https://github.com/Telecominfraproject/wlan-cloud-ucentralsec@helm/owsec-0.1.0.tgz?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. | ||||
|   | ||||
| @@ -13,6 +13,7 @@ spec: | ||||
|   replicas: {{ .Values.replicaCount }} | ||||
|   strategy: | ||||
|     type: {{ .Values.strategyType }} | ||||
|   revisionHistoryLimit: {{ .Values.revisionHistoryLimit }} | ||||
|   selector: | ||||
|     matchLabels: | ||||
|       app.kubernetes.io/name: {{ include "owsec.name" . }} | ||||
| @@ -24,6 +25,9 @@ spec: | ||||
|     metadata: | ||||
|       annotations: | ||||
|         checksum/config: {{ include "owsec.config" . | sha256sum }} | ||||
|         {{- with .Values.podAnnotations }} | ||||
|         {{- toYaml . | nindent 8 }} | ||||
|         {{- end }} | ||||
|       labels: | ||||
|         app.kubernetes.io/name: {{ include "owsec.name" . }} | ||||
|         app.kubernetes.io/instance: {{ .Release.Name }} | ||||
| @@ -32,6 +36,16 @@ spec: | ||||
|         {{- end }} | ||||
|     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: | ||||
|  | ||||
|         - name: owsec | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| # System | ||||
| replicaCount: 1 | ||||
| strategyType: Recreate | ||||
| revisionHistoryLimit: 2 | ||||
|  | ||||
| nameOverride: "" | ||||
| fullnameOverride: "" | ||||
| @@ -8,16 +9,20 @@ fullnameOverride: "" | ||||
| images: | ||||
|   owsec: | ||||
|     repository: tip-tip-wlan-cloud-ucentral.jfrog.io/owsec | ||||
|     tag: main | ||||
|     tag: v2.5.0 | ||||
|     pullPolicy: Always | ||||
| #    regcred: | ||||
| #      registry: tip-tip-wlan-cloud-ucentral.jfrog.io | ||||
| #      username: username | ||||
| #      password: password | ||||
|   dockerize: | ||||
|     repository: tip-tip-wlan-cloud-ucentral.jfrog.io/dockerize | ||||
|     tag: 0.16.0 | ||||
|     pullPolicy: IfNotPresent | ||||
|  | ||||
| services: | ||||
|   owsec: | ||||
|     type: LoadBalancer | ||||
|     type: ClusterIP | ||||
|     ports: | ||||
|       restapi: | ||||
|         servicePort: 16001 | ||||
| @@ -35,9 +40,9 @@ checks: | ||||
|         path: / | ||||
|         port: 16101 | ||||
|     readiness: | ||||
|       httpGet: | ||||
|         path: / | ||||
|         port: 16101 | ||||
|       exec: | ||||
|         command: | ||||
|           - /readiness_check | ||||
|  | ||||
| ingresses: | ||||
|   restapi: | ||||
| @@ -94,6 +99,8 @@ tolerations: [] | ||||
|  | ||||
| affinity: {} | ||||
|  | ||||
| podAnnotations: {} | ||||
|  | ||||
| persistence: | ||||
|   enabled: true | ||||
|   # storageClassName: "-" | ||||
| @@ -106,8 +113,14 @@ persistence: | ||||
| public_env_variables: | ||||
|   OWSEC_ROOT: /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: | ||||
|   # -> Public part | ||||
| @@ -119,7 +132,7 @@ configProperties: | ||||
|   openwifi.restapi.host.0.port: 16001 | ||||
|   openwifi.restapi.host.0.cert: $OWSEC_ROOT/certs/restapi-cert.pem | ||||
|   openwifi.restapi.host.0.key: $OWSEC_ROOT/certs/restapi-key.pem | ||||
|   openwifi.restapi.wwwassets: $OWSEC_ROOT/wwwassets | ||||
|   openwifi.restapi.wwwassets: $OWSEC_ROOT/persist/wwwassets | ||||
|   openwifi.internal.restapi.host.0.backlog: 100 | ||||
|   openwifi.internal.restapi.host.0.security: relaxed | ||||
|   openwifi.internal.restapi.host.0.rootca: $OWSEC_ROOT/certs/restapi-ca.pem | ||||
| @@ -132,11 +145,17 @@ configProperties: | ||||
|   authentication.default.access: master | ||||
|   authentication.service.type: internal | ||||
|   # Mailer | ||||
|   mailer.enabled: "false" | ||||
|   mailer.hostname: smtp.gmail.com | ||||
|   mailer.sender: OpenWIFI | ||||
|   mailer.loginmethod: login | ||||
|   mailer.port: 587 | ||||
|   mailer.templates: $OWSEC_ROOT/templates | ||||
|   mailer.templates: $OWSEC_ROOT/persist/templates | ||||
|   # SMS | ||||
|   smssender.enabled: "false" | ||||
|   smssender.provider: "aws" | ||||
|   #smssender.aws.region: "" | ||||
|   #smssender.twilio.phonenumber: "" | ||||
|   # ALB | ||||
|   alb.enable: "true" | ||||
|   alb.port: 16101 | ||||
| @@ -176,22 +195,9 @@ configProperties: | ||||
|   openwifi.system.uri.ui: https://localhost | ||||
|   openwifi.system.commandchannel: /tmp/app_owsec | ||||
|   # Logging | ||||
|   logging.formatters.f1.class: PatternFormatter | ||||
|   logging.formatters.f1.pattern: "%Y-%m-%d %H:%M:%S %s: [%p] %t" | ||||
|   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 | ||||
|   logging.type: console | ||||
|   logging.path: $OWSEC_ROOT/logs | ||||
|   logging.level: debug | ||||
|  | ||||
|   # -> Secret part | ||||
|   # REST API | ||||
| @@ -203,6 +209,12 @@ configProperties: | ||||
|   # Mailer | ||||
|   mailer.username: no-reply@arilia.com | ||||
|   mailer.password: "**************************" | ||||
|   # SMS | ||||
|   #smssender.aws.secretkey: "" | ||||
|   #smssender.aws.accesskey: "" | ||||
|   #smssender.twilio.sid: "" | ||||
|   #smssender.twilio.token: "" | ||||
|   # | ||||
|   # Storage | ||||
|   ## PostgreSQL | ||||
|   storage.type.postgresql.username: stephb | ||||
|   | ||||
| @@ -1,8 +1,8 @@ | ||||
| openapi: 3.0.1 | ||||
| info: | ||||
|   title: uCentral Security API | ||||
|   description: A process to manage security logins | ||||
|   version: 2.0.0 | ||||
|   description: A process to manage security logins. | ||||
|   version: 2.5.0 | ||||
|   license: | ||||
|     name: BSD3 | ||||
|     url: https://github.com/Telecominfraproject/wlan-cloud-ucentralgw/blob/master/LICENSE | ||||
| @@ -51,6 +51,21 @@ components: | ||||
|             properties: | ||||
|               ErrorCode: | ||||
|                 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 | ||||
|               ErrorDetails: | ||||
|                 type: string | ||||
|               ErrorDescription: | ||||
| @@ -69,8 +84,20 @@ components: | ||||
|               Code: | ||||
|                 type: integer | ||||
| 
 | ||||
|   schemas: | ||||
|     BadRequest: | ||||
|       description: The requested operation failed. | ||||
|       content: | ||||
|         application/json: | ||||
|           schema: | ||||
|             properties: | ||||
|               ErrorCode: | ||||
|                 type: integer | ||||
|               ErrorDetails: | ||||
|                 type: string | ||||
|               ErrorDescription: | ||||
|                 type: integer | ||||
| 
 | ||||
|   schemas: | ||||
|     WebTokenRequest: | ||||
|       description: User Id and password. | ||||
|       type: object | ||||
| @@ -196,6 +223,40 @@ components: | ||||
|           items: | ||||
|             $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: | ||||
|       type: object | ||||
|       properties: | ||||
| @@ -267,10 +328,12 @@ components: | ||||
|           enum: | ||||
|             - root | ||||
|             - admin | ||||
|             - sub | ||||
|             - subscriber | ||||
|             - csr | ||||
|             - system | ||||
|             - special | ||||
|             - installer | ||||
|             - noc | ||||
|             - accounting | ||||
|         oauthType: | ||||
|           type: string | ||||
|           enum: | ||||
| @@ -287,8 +350,11 @@ components: | ||||
|         securityPolicyChange: | ||||
|           type: integer | ||||
|           format: int64 | ||||
|         modified: | ||||
|           type: integer | ||||
|           format: int64 | ||||
|         userTypeProprietaryInfo: | ||||
|           type: string | ||||
|           $ref: '#/components/schemas/UserLoginLoginExtensions' | ||||
| 
 | ||||
|     UserList: | ||||
|       type: object | ||||
| @@ -314,6 +380,57 @@ components: | ||||
|         text: | ||||
|           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 | ||||
| @@ -558,6 +675,22 @@ components: | ||||
|           items: | ||||
|             $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 | ||||
| @@ -590,20 +723,92 @@ paths: | ||||
|           schema: | ||||
|             type: boolean | ||||
|           required: false | ||||
|         - in: query | ||||
|           name: resendMFACode | ||||
|           schema: | ||||
|             type: boolean | ||||
|           required: false | ||||
|         - in: query | ||||
|           name: completeMFAChallenge | ||||
|           schema: | ||||
|             type: boolean | ||||
|           required: false | ||||
|       requestBody: | ||||
|         description: User id and password | ||||
|         required: true | ||||
|         content: | ||||
|           application/json: | ||||
|             schema: | ||||
|               $ref: '#/components/schemas/WebTokenRequest' | ||||
|               oneOf: | ||||
|                 - $ref: '#/components/schemas/WebTokenRequest' | ||||
|                 - $ref: '#/components/schemas/MFAChallengeResponse' | ||||
|       responses: | ||||
|         200: | ||||
|           description: successful operation | ||||
|           content: | ||||
|             application/json: | ||||
|               schema: | ||||
|                 $ref: '#/components/schemas/WebTokenResult' | ||||
|                 oneOf: | ||||
|                   - $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 | ||||
|       requestBody: | ||||
|         description: User id and password | ||||
|         required: true | ||||
|         content: | ||||
|           application/json: | ||||
|             schema: | ||||
|               oneOf: | ||||
|                 - $ref: '#/components/schemas/WebTokenRequest' | ||||
|                 - $ref: '#/components/schemas/MFAChallengeResponse' | ||||
|       responses: | ||||
|         200: | ||||
|           description: successful operation | ||||
|           content: | ||||
|             application/json: | ||||
|               schema: | ||||
|                 oneOf: | ||||
|                   - $ref: '#/components/schemas/WebTokenResult' | ||||
|                   - $ref: '#/components/schemas/MFAChallengeRequest' | ||||
|         403: | ||||
|           $ref: '#/components/responses/Unauthorized' | ||||
|         404: | ||||
| @@ -634,11 +839,36 @@ paths: | ||||
|         404: | ||||
|           $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: | ||||
|     get: | ||||
|       tags: | ||||
|         - Authentication | ||||
|       summary: retrieve the system layout | ||||
|       summary: Retrieve the system layout. | ||||
|       operationId: getSystemInfo | ||||
|       responses: | ||||
|         200: | ||||
| @@ -698,12 +928,58 @@ paths: | ||||
|         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 | ||||
|       responses: | ||||
|         200: | ||||
|           $ref: '#/components/schemas/UserList' | ||||
|         403: | ||||
|           $ref: '#/components/responses/Unauthorized' | ||||
|         404: | ||||
|           $ref: '#/components/responses/NotFound' | ||||
| 
 | ||||
|   /user/{id}: | ||||
|     get: | ||||
|       tags: | ||||
|         - User Management | ||||
|       operationId: getUser | ||||
|       summary: Retrieve the information for a single user | ||||
|       summary: Retrieve the information for a single user. | ||||
|       parameters: | ||||
|         - in: path | ||||
|           name: id | ||||
| @@ -723,7 +999,7 @@ paths: | ||||
|       tags: | ||||
|         - User Management | ||||
|       operationId: deleteUser | ||||
|       summary: Delete s single user | ||||
|       summary: Delete a single user. | ||||
|       parameters: | ||||
|         - in: path | ||||
|           name: id | ||||
| @@ -743,7 +1019,7 @@ paths: | ||||
|       tags: | ||||
|         - User Management | ||||
|       operationId: createUser | ||||
|       summary: Create a single user | ||||
|       summary: Create a single user. | ||||
|       parameters: | ||||
|         - in: path | ||||
|           name: id | ||||
| @@ -775,7 +1051,111 @@ paths: | ||||
|       tags: | ||||
|         - User Management | ||||
|       operationId: updateUser | ||||
|       summary: Modifying a single user | ||||
|       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 | ||||
|       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 | ||||
| @@ -807,7 +1187,7 @@ paths: | ||||
|       tags: | ||||
|         - Avatar | ||||
|       operationId: getAvatar | ||||
|       summary: Retrieve teh avatar associated with a user ID | ||||
|       summary: Retrieve the avatar associated with a user ID. | ||||
|       parameters: | ||||
|         - in: path | ||||
|           name: id | ||||
| @@ -841,7 +1221,7 @@ paths: | ||||
|       tags: | ||||
|         - Avatar | ||||
|       operationId: deleteAvatar | ||||
|       summary: Remove an Avatar associated with a user ID | ||||
|       summary: Remove an avatar associated with a user ID. | ||||
|       parameters: | ||||
|         - in: path | ||||
|           name: id | ||||
| @@ -861,7 +1241,7 @@ paths: | ||||
|       tags: | ||||
|         - Avatar | ||||
|       operationId: createAvatar | ||||
|       summary: Create an Avatar associated with a user ID | ||||
|       summary: Create an avatar associated with a user ID. | ||||
|       parameters: | ||||
|         - in: path | ||||
|           name: id | ||||
| @@ -897,8 +1277,8 @@ paths: | ||||
|   /email: | ||||
|     post: | ||||
|       tags: | ||||
|         - EMail | ||||
|       summary: Send test email with the system | ||||
|         - Email | ||||
|       summary: Send test email with the system. | ||||
|       operationId: Send a test email | ||||
|       requestBody: | ||||
|         description: The requested message | ||||
| @@ -925,6 +1305,185 @@ paths: | ||||
|                     items: | ||||
|                       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 | ||||
|           required: required | ||||
|           example: 1,2,3 | ||||
|       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' | ||||
| 
 | ||||
|   ######################################################################################### | ||||
|   ## | ||||
|   ## These are endpoints that all services in the uCentral stack must provide | ||||
| @@ -934,7 +1493,7 @@ paths: | ||||
|     get: | ||||
|       tags: | ||||
|         - 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 | ||||
|       parameters: | ||||
|         - in: query | ||||
| @@ -1000,11 +1559,32 @@ paths: | ||||
|         404: | ||||
|           $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: | ||||
|     post: | ||||
|       tags: | ||||
|         - System Commands | ||||
|       summary: Perform some systeme wide commands | ||||
|       summary: Perform some system wide commands. | ||||
|       operationId: systemCommand | ||||
|       requestBody: | ||||
|         description: Command details | ||||
| @@ -1019,7 +1599,7 @@ paths: | ||||
|                 - $ref: '#/components/schemas/SystemCommandGetSubsystemNames' | ||||
|       responses: | ||||
|         200: | ||||
|           description: Successfull command execution | ||||
|           description: Successful command execution | ||||
|           content: | ||||
|             application/json: | ||||
|               schema: | ||||
| @@ -1048,7 +1628,7 @@ paths: | ||||
| 
 | ||||
|       responses: | ||||
|         200: | ||||
|           description: Successfull command execution | ||||
|           description: Successful command execution | ||||
|           content: | ||||
|             application/json: | ||||
|               schema: | ||||
| @@ -40,9 +40,21 @@ openwifi.system.commandchannel = /tmp/app.ucentralsec | ||||
| openwifi.service.key = $OWSEC_ROOT/certs/restapi-key.pem | ||||
| 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 | ||||
| # | ||||
| mailer.enabled = false | ||||
| mailer.hostname = smtp.gmail.com | ||||
| mailer.username = ************************ | ||||
| mailer.password = ************************ | ||||
| @@ -74,6 +86,7 @@ openwifi.document.policy.access = /wwwassets/access_policy.html | ||||
| openwifi.document.policy.password = /wwwassets/password_policy.html | ||||
| openwifi.avatar.maxsize = 2000000 | ||||
|  | ||||
| totp.issuer = OpenWiFi | ||||
| # | ||||
| # 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 | ||||
| @@ -106,44 +119,12 @@ storage.type.mysql.database = ucentral | ||||
| storage.type.mysql.port = 3306 | ||||
| storage.type.mysql.connectiontimeout = 60 | ||||
|  | ||||
|  | ||||
| ######################################################################## | ||||
| ######################################################################## | ||||
| # | ||||
| # Logging: please leave as is for now. | ||||
| # | ||||
| ######################################################################## | ||||
| logging.formatters.f1.class = PatternFormatter | ||||
| logging.formatters.f1.pattern = %s: [%p] %t | ||||
| 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 | ||||
|  | ||||
|  | ||||
|  | ||||
| logging.type = file | ||||
| logging.path = $OWSEC_ROOT/logs | ||||
| logging.level = debug | ||||
| @@ -40,16 +40,28 @@ openwifi.system.commandchannel = /tmp/app.ucentralsec | ||||
| openwifi.service.key = ${SERVICE_KEY} | ||||
| 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 | ||||
| # | ||||
| mailer.enabled = ${MAILER_ENABLED} | ||||
| mailer.hostname = ${MAILER_HOSTNAME} | ||||
| mailer.username = ${MAILER_USERNAME} | ||||
| mailer.password = ${MAILER_PASSWORD} | ||||
| mailer.sender = ${MAILER_SENDER} | ||||
| mailer.loginmethod = login | ||||
| mailer.port = ${MAILER_PORT} | ||||
| mailer.templates = $UCENTRALSEC_ROOT/templates | ||||
| mailer.templates = ${MAILER_TEMPLATES} | ||||
|  | ||||
|  | ||||
| ############################# | ||||
| @@ -71,8 +83,8 @@ openwifi.kafka.brokerlist = ${KAFKA_BROKERLIST} | ||||
| openwifi.kafka.auto.commit = false | ||||
| openwifi.kafka.queue.buffering.max.ms = 50 | ||||
|  | ||||
| openwifi.document.policy.access = /wwwassets/access_policy.html | ||||
| openwifi.document.policy.password = /wwwassets/password_policy.html | ||||
| openwifi.document.policy.access = ${DOCUMENT_POLICY_ACCESS} | ||||
| openwifi.document.policy.password = ${DOCUMENT_POLICY_PASSWORD} | ||||
| openwifi.avatar.maxsize = 2000000 | ||||
| # | ||||
| # This section select which form of persistence you need | ||||
| @@ -110,37 +122,6 @@ storage.type.mysql.connectiontimeout = 60 | ||||
| # Logging: please leave as is for now. | ||||
| # | ||||
| ######################################################################## | ||||
| logging.formatters.f1.class = PatternFormatter | ||||
| logging.formatters.f1.pattern = %Y-%m-%d %H:%M:%S %s: [%p] %t | ||||
| 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 | ||||
|  | ||||
|  | ||||
|  | ||||
| logging.type = console | ||||
| logging.path = $OWSEC_ROOT/logs | ||||
| logging.level = debug | ||||
|   | ||||
							
								
								
									
										59
									
								
								readiness_check
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										59
									
								
								readiness_check
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,59 @@ | ||||
| #!/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 | ||||
							
								
								
									
										84
									
								
								src/ACLProcessor.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										84
									
								
								src/ACLProcessor.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,84 @@ | ||||
| // | ||||
| // 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 | ||||
|         }; | ||||
| /* | ||||
|     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 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 | ||||
| @@ -1,118 +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. | ||||
| // | ||||
|  | ||||
| #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 | ||||
							
								
								
									
										97
									
								
								src/ActionLinkManager.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										97
									
								
								src/ActionLinkManager.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,97 @@ | ||||
| // | ||||
| // 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) && !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(Poco::format("Send password reset link to %s",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(Poco::format("Send email verification link to %s",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(Poco::format("Send subscriber password reset link to %s",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(Poco::format("Send subscriber email verification link to %s",UInfo.email)); | ||||
|                             } | ||||
|                             StorageService()->ActionLinksDB().SentAction(i.id); | ||||
|                         } | ||||
|                         break; | ||||
|  | ||||
|                     default: { | ||||
|                         StorageService()->ActionLinksDB().SentAction(i.id); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
| } | ||||
							
								
								
									
										43
									
								
								src/ActionLinkManager.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								src/ActionLinkManager.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,43 @@ | ||||
| // | ||||
| // 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 | ||||
|         }; | ||||
| */ | ||||
|         static ActionLinkManager * instance() { | ||||
|             static auto * instance_ = new ActionLinkManager; | ||||
|             return instance_; | ||||
|         } | ||||
|  | ||||
|         int Start() final; | ||||
|         void Stop() final; | ||||
|         void run(); | ||||
|  | ||||
|     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 | ||||
| @@ -1,93 +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 <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; | ||||
| 	} | ||||
| } | ||||
| @@ -1,45 +0,0 @@ | ||||
| // | ||||
| // 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,19 +11,17 @@ | ||||
| #include "Poco/Net/OAuth20Credentials.h" | ||||
| #include "Poco/JWT/Token.h" | ||||
| #include "Poco/JWT/Signer.h" | ||||
| #include "Poco/StringTokenizer.h" | ||||
|  | ||||
| #include "Daemon.h" | ||||
| #include "RESTAPI_handler.h" | ||||
| #include "framework/MicroService.h" | ||||
| #include "StorageService.h" | ||||
| #include "AuthService.h" | ||||
| #include "Utils.h" | ||||
| #include "KafkaManager.h" | ||||
| #include "Kafka_topics.h" | ||||
| #include "framework/KafkaTopics.h" | ||||
|  | ||||
| #include "SMTPMailerService.h" | ||||
| #include "MFAServer.h" | ||||
|  | ||||
| namespace OpenWifi { | ||||
|     class AuthService *AuthService::instance_ = nullptr; | ||||
|  | ||||
|     AuthService::ACCESS_TYPE AuthService::IntToAccessType(int C) { | ||||
| 		switch (C) { | ||||
| @@ -45,101 +43,174 @@ namespace OpenWifi { | ||||
| 	} | ||||
|  | ||||
|     int AuthService::Start() { | ||||
| 		Signer_.setRSAKey(Daemon()->Key()); | ||||
| 		Signer_.setRSAKey(MicroService::instance().Key()); | ||||
| 		Signer_.addAllAlgorithms(); | ||||
| 		Logger_.notice("Starting..."); | ||||
|         Secure_ = Daemon()->ConfigGetBool("authentication.enabled",true); | ||||
|         DefaultPassword_ = Daemon()->ConfigGetString("authentication.default.password",""); | ||||
|         DefaultUserName_ = Daemon()->ConfigGetString("authentication.default.username",""); | ||||
|         Mechanism_ = Daemon()->ConfigGetString("authentication.service.type","internal"); | ||||
|         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); | ||||
|         HowManyOldPassword_ = Daemon()->ConfigGetInt("authentication.oldpasswords", 5); | ||||
| 		Logger().notice("Starting..."); | ||||
|         TokenAging_ = (uint64_t) MicroService::instance().ConfigGetInt("authentication.token.ageing", 30 * 24 * 60 * 60); | ||||
|         HowManyOldPassword_ = MicroService::instance().ConfigGetInt("authentication.oldpasswords", 5); | ||||
|  | ||||
|         AccessPolicy_ = MicroService::instance().ConfigPath("openwifi.document.policy.access", "/wwwassets/access_policy.html"); | ||||
|         PasswordPolicy_ = MicroService::instance().ConfigPath("openwifi.document.policy.password", "/wwwassets/password_policy.html"); | ||||
|         PasswordValidation_ = PasswordValidationStr_ = MicroService::instance().ConfigGetString("authentication.validation.expression","^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[#?!@$%^&*-]).{8,}$"); | ||||
|  | ||||
|         SubPasswordValidation_ = SubPasswordValidationStr_ = MicroService::instance().ConfigGetString("subscriber.validation.expression","^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[#?!@$%^&*-]).{8,}$"); | ||||
|         SubAccessPolicy_ = MicroService::instance().ConfigPath("subscriber.policy.access", "/wwwassets/access_policy.html"); | ||||
|         SubPasswordPolicy_ = MicroService::instance().ConfigPath("subscriber.policy.password", "/wwwassets/password_policy.html"); | ||||
|  | ||||
|         return 0; | ||||
|     } | ||||
|  | ||||
|     void AuthService::Stop() { | ||||
| 		Logger_.notice("Stopping..."); | ||||
| 		Logger().notice("Stopping..."); | ||||
|     } | ||||
|  | ||||
| 	bool AuthService::IsAuthorized(Poco::Net::HTTPServerRequest & Request, std::string & SessionToken, SecurityObjects::UserInfoAndPolicy & UInfo ) | ||||
| 	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::string CallToken; | ||||
|  | ||||
|         Expired = false; | ||||
| 		try { | ||||
| 		    std::string CallToken; | ||||
| 		    Poco::Net::OAuth20Credentials Auth(Request); | ||||
|  | ||||
| 		    if (Auth.getScheme() == "Bearer") { | ||||
| 		        CallToken = Auth.getBearerToken(); | ||||
| 		    } | ||||
| 		} catch(const Poco::Exception &E) { | ||||
|  | ||||
|             if(CallToken.empty()) { | ||||
|                 return false; | ||||
|             } | ||||
|  | ||||
| 		if(!CallToken.empty()) { | ||||
| 		    if(Storage()->IsTokenRevoked(CallToken)) | ||||
|             SecurityObjects::WebToken   WT; | ||||
|             uint64_t                    RevocationDate=0; | ||||
|             std::string                 UserId; | ||||
|             if(StorageService()->UserTokenDB().GetToken(CallToken, WT, UserId, RevocationDate)) { | ||||
|                 if(RevocationDate!=0) | ||||
|                     return false; | ||||
| 		    auto Client = UserCache_.find(CallToken); | ||||
| 		    if( Client == UserCache_.end() ) | ||||
| 		        return ValidateToken(CallToken, CallToken, UInfo); | ||||
|  | ||||
| 		    if((Client->second.webtoken.created_ + Client->second.webtoken.expires_in_) > time(nullptr)) { | ||||
|                 Expired = (WT.created_ + WT.expires_in_) < time(nullptr); | ||||
|                 if(StorageService()->UserDB().GetUserById(UserId,UInfo.userinfo)) { | ||||
|                     UInfo.webtoken = WT; | ||||
|                     SessionToken = CallToken; | ||||
| 		        UInfo = Client->second ; | ||||
|                     return true; | ||||
|                 } | ||||
| 		    UserCache_.erase(CallToken); | ||||
| 		    Storage()->RevokeToken(CallToken); | ||||
|             } | ||||
|             return false; | ||||
| 		} catch(const Poco::Exception &E) { | ||||
| 		    Logger().log(E); | ||||
| 		} | ||||
| 		return false; | ||||
|     } | ||||
|  | ||||
| 		return false; | ||||
|     } | ||||
|  | ||||
|     bool AuthService::DeleteUserFromCache(const std::string &UserName) { | ||||
|     bool AuthService::IsSubAuthorized(Poco::Net::HTTPServerRequest & Request, std::string & SessionToken, SecurityObjects::UserInfoAndPolicy & UInfo, bool & Expired ) | ||||
|     { | ||||
|         std::lock_guard	Guard(Mutex_); | ||||
|         Expired = false; | ||||
|         try { | ||||
|             std::string CallToken; | ||||
|             Poco::Net::OAuth20Credentials Auth(Request); | ||||
|             if (Auth.getScheme() == "Bearer") { | ||||
|                 CallToken = Auth.getBearerToken(); | ||||
|             } | ||||
|  | ||||
|         for(auto i=UserCache_.begin();i!=UserCache_.end();) { | ||||
|             if (i->second.userinfo.email==UserName) { | ||||
|                 Logout(i->first); | ||||
|                 i = UserCache_.erase(i); | ||||
|             } else { | ||||
|                 ++i; | ||||
|             } | ||||
|             if(CallToken.empty()) { | ||||
|                 return false; | ||||
|             } | ||||
|  | ||||
|             SecurityObjects::WebToken   WT; | ||||
|             uint64_t                    RevocationDate=0; | ||||
|             std::string                 UserId; | ||||
|             if(StorageService()->SubTokenDB().GetToken(CallToken, WT, UserId, RevocationDate)) { | ||||
|                 if(RevocationDate!=0) | ||||
|                     return false; | ||||
|                 Expired = (WT.created_ + WT.expires_in_) < time(nullptr); | ||||
|                 if(StorageService()->SubDB().GetUserById(UserId,UInfo.userinfo)) { | ||||
|                     UInfo.webtoken = WT; | ||||
|                     SessionToken = CallToken; | ||||
|                     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) { | ||||
|         return std::regex_match(Password, PasswordValidation_); | ||||
|     } | ||||
|  | ||||
|     void AuthService::Logout(const std::string &token) { | ||||
| 		std::lock_guard		Guard(Mutex_); | ||||
|  | ||||
| 		UserCache_.erase(token); | ||||
|     bool AuthService::ValidateSubPassword(const std::string &Password) { | ||||
|         return std::regex_match(Password, SubPasswordValidation_); | ||||
|     } | ||||
|  | ||||
|     void AuthService::RemoveTokenSystemWide(const std::string &token) { | ||||
|         try { | ||||
|             if(KafkaManager()->Enabled()) { | ||||
|                 Poco::JSON::Object Obj; | ||||
|                 Obj.set("event", "remove-token"); | ||||
|             Obj.set("id", Daemon()->ID()); | ||||
|                 Obj.set("id", MicroService::instance().ID()); | ||||
|                 Obj.set("token", token); | ||||
|                 std::stringstream ResultText; | ||||
|                 Poco::JSON::Stringifier::stringify(Obj, ResultText); | ||||
|             std::string Tmp{token}; | ||||
|             Storage()->RevokeToken(Tmp); | ||||
|             KafkaManager()->PostMessage(KafkaTopics::SERVICE_EVENTS, Daemon()->PrivateEndPoint(), ResultText.str(), | ||||
|                 KafkaManager()->PostMessage(KafkaTopics::SERVICE_EVENTS, MicroService::instance().PrivateEndPoint(), | ||||
|                                             ResultText.str(), | ||||
|                                             false); | ||||
|             } | ||||
|         } catch (const Poco::Exception &E) { | ||||
|             Logger_.log(E); | ||||
|             Logger().log(E); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     std::string AuthService::GenerateToken(const std::string & Identity, ACCESS_TYPE Type) { | ||||
|     void AuthService::Logout(const std::string &Token, bool EraseFromCache) { | ||||
| 		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, 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, ACCESS_TYPE Type) { | ||||
|         std::string Identity(UserName + ":" + Poco::format("%d",(int)std::time(nullptr)) + ":" + 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_); | ||||
|  | ||||
| 		Poco::JWT::Token	T; | ||||
| @@ -159,29 +230,6 @@ namespace OpenWifi { | ||||
| 		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) | ||||
|     { | ||||
|         std::lock_guard		Guard(Mutex_); | ||||
| @@ -192,50 +240,170 @@ namespace OpenWifi { | ||||
|         UInfo.webtoken.expires_in_ = TokenAging_ ; | ||||
|         UInfo.webtoken.idle_timeout_ = 5 * 60; | ||||
|         UInfo.webtoken.token_type_ = "Bearer"; | ||||
|         UInfo.webtoken.access_token_ = GenerateToken(UInfo.userinfo.Id,USERNAME); | ||||
|         UInfo.webtoken.id_token_ = GenerateToken(UInfo.userinfo.Id,USERNAME); | ||||
|         UInfo.webtoken.refresh_token_ = GenerateToken(UInfo.userinfo.Id,CUSTOM); | ||||
|         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; | ||||
|         UserCache_[UInfo.webtoken.access_token_] = UInfo; | ||||
|         Storage()->SetLastLogin(UInfo.userinfo.Id); | ||||
|         Storage()->AddToken(UInfo.webtoken.username_, UInfo.webtoken.access_token_, | ||||
|         StorageService()->UserDB().SetLastLogin(UInfo.userinfo.id); | ||||
|         StorageService()->UserTokenDB().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()->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) { | ||||
|         auto NewPasswordHash = ComputePasswordHash(UInfo.email, NewPassword); | ||||
|         for (auto const &i:UInfo.lastPasswords) { | ||||
|             if (i == NewPasswordHash) { | ||||
|         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()); | ||||
|         } | ||||
|         UInfo.lastPasswords.push_back(NewPasswordHash); | ||||
|         UInfo.currentPassword = NewPasswordHash; | ||||
|  | ||||
|         auto NewHash = ComputeNewPasswordHash(UInfo.email,NewPassword); | ||||
|         UInfo.lastPasswords.push_back(NewHash); | ||||
|         UInfo.currentPassword = NewHash; | ||||
|         UInfo.changePassword = false; | ||||
|         return true; | ||||
|     } | ||||
|  | ||||
|     AuthService::AUTH_ERROR AuthService::Authorize( std::string & UserName, const std::string & Password, const std::string & NewPassword, SecurityObjects::UserInfoAndPolicy & UInfo ) | ||||
|     bool AuthService::SetSubPassword(const std::string &NewPassword, SecurityObjects::UserInfo & 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 , bool & Expired ) | ||||
|     { | ||||
|         std::lock_guard		Guard(Mutex_); | ||||
|         SecurityObjects::AclTemplate	ACL; | ||||
|  | ||||
|         Poco::toLowerInPlace(UserName); | ||||
|         auto PasswordHash = ComputePasswordHash(UserName, Password); | ||||
|  | ||||
|         if(Storage()->GetUserByEmail(UserName,UInfo.userinfo)) { | ||||
|         if(StorageService()->UserDB().GetUserByEmail(UserName,UInfo.userinfo)) { | ||||
|             if(UInfo.userinfo.waitingForEmailCheck) { | ||||
|                 return USERNAME_PENDING_VERIFICATION; | ||||
|             } | ||||
|  | ||||
|             if(PasswordHash != UInfo.userinfo.currentPassword) { | ||||
|             if(!ValidatePasswordHash(UserName,Password,UInfo.userinfo.currentPassword)) { | ||||
|                 return INVALID_CREDENTIALS; | ||||
|             } | ||||
|  | ||||
| @@ -255,60 +423,89 @@ namespace OpenWifi { | ||||
|                 } | ||||
|                 UInfo.userinfo.lastPasswordChange = std::time(nullptr); | ||||
|                 UInfo.userinfo.changePassword = false; | ||||
|                 Storage()->UpdateUserInfo(AUTHENTICATION_SYSTEM, UInfo.userinfo.Id,UInfo.userinfo); | ||||
|                 UInfo.userinfo.modified = std::time(nullptr); | ||||
|                 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. | ||||
|             UInfo.userinfo.lastLogin=std::time(nullptr); | ||||
|             Storage()->SetLastLogin(UInfo.userinfo.Id); | ||||
|             StorageService()->UserDB().SetLastLogin(UInfo.userinfo.id); | ||||
|             CreateToken(UserName, UInfo ); | ||||
|  | ||||
|             return SUCCESS; | ||||
|         } | ||||
|  | ||||
|         if(((UserName == DefaultUserName_) && (DefaultPassword_== ComputePasswordHash(UserName,Password))) || !Secure_) | ||||
|         { | ||||
|             ACL.PortalLogin_ = ACL.Read_ = ACL.ReadWrite_ = ACL.ReadWriteCreate_ = ACL.Delete_ = true; | ||||
|             UInfo.webtoken.acl_template_ = ACL; | ||||
|             UInfo.userinfo.email = DefaultUserName_; | ||||
|             UInfo.userinfo.currentPassword = DefaultPassword_; | ||||
|             UInfo.userinfo.name = DefaultUserName_; | ||||
|             CreateToken(UserName, UInfo ); | ||||
|             return SUCCESS; | ||||
|         } | ||||
|         return INVALID_CREDENTIALS; | ||||
|     } | ||||
|  | ||||
|     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()); | ||||
|     UNAUTHORIZED_REASON AuthService::AuthorizeSub( std::string & UserName, const std::string & Password, const std::string & NewPassword, SecurityObjects::UserInfoAndPolicy & UInfo , bool & Expired ) | ||||
|     { | ||||
|         std::lock_guard		Guard(Mutex_); | ||||
|  | ||||
|         Poco::toLowerInPlace(UserName); | ||||
|  | ||||
|         if(StorageService()->SubDB().GetUserByEmail(UserName,UInfo.userinfo)) { | ||||
|             if(UInfo.userinfo.waitingForEmailCheck) { | ||||
|                 return USERNAME_PENDING_VERIFICATION; | ||||
|             } | ||||
|  | ||||
|     bool AuthService::SendEmailToUser(std::string &Email, EMAIL_REASON Reason) { | ||||
|             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 = std::time(nullptr); | ||||
|                 UInfo.userinfo.changePassword = false; | ||||
|                 UInfo.userinfo.modified = std::time(nullptr); | ||||
|                 StorageService()->SubDB().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. | ||||
|             UInfo.userinfo.lastLogin=std::time(nullptr); | ||||
|             StorageService()->SubDB().SetLastLogin(UInfo.userinfo.id); | ||||
|             CreateSubToken(UserName, UInfo ); | ||||
|  | ||||
|             return SUCCESS; | ||||
|         } | ||||
|  | ||||
|         return INVALID_CREDENTIALS; | ||||
|     } | ||||
|  | ||||
|     bool AuthService::SendEmailToUser(const std::string &LinkId, std::string &Email, EMAIL_REASON Reason) { | ||||
|         SecurityObjects::UserInfo   UInfo; | ||||
|  | ||||
|         if(Storage()->GetUserByEmail(Email,UInfo)) { | ||||
|         if(StorageService()->UserDB().GetUserByEmail(Email,UInfo)) { | ||||
|             switch (Reason) { | ||||
|  | ||||
|                 case FORGOT_PASSWORD: { | ||||
|                         MessageAttributes Attrs; | ||||
|  | ||||
|                         Attrs[RECIPIENT_EMAIL] = UInfo.email; | ||||
|                         Attrs[LOGO] = "logo.jpg"; | ||||
|                         Attrs[LOGO] = GetLogoAssetURI(); | ||||
|                         Attrs[SUBJECT] = "Password reset link"; | ||||
|                         Attrs[ACTION_LINK] = | ||||
|                                 Daemon()->GetPublicAPIEndPoint() + "/actionLink?action=password_reset&id=" + UInfo.Id ; | ||||
|                         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] = "logo.jpg"; | ||||
|                         Attrs[LOGO] = GetLogoAssetURI(); | ||||
|                         Attrs[SUBJECT] = "EMail Address Verification"; | ||||
|                         Attrs[ACTION_LINK] = | ||||
|                                 Daemon()->GetPublicAPIEndPoint() + "/actionLink?action=email_verification&id=" + UInfo.Id ; | ||||
|                         Attrs[ACTION_LINK] = MicroService::instance().GetPublicAPIEndPoint() + "/actionLink?action=email_verification&id=" + LinkId ; | ||||
|                         SMTPMailerService()->SendMessage(UInfo.email, "email_verification.txt", Attrs); | ||||
|                         UInfo.waitingForEmailCheck = true; | ||||
|                     } | ||||
| @@ -317,33 +514,113 @@ namespace OpenWifi { | ||||
|                 default: | ||||
|                     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] = "EMail 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; | ||||
|  | ||||
|                 default: | ||||
|                     break; | ||||
|             } | ||||
|             return true; | ||||
|         } | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     bool AuthService::VerifyEmail(SecurityObjects::UserInfo &UInfo) { | ||||
|         MessageAttributes Attrs; | ||||
|         SecurityObjects::ActionLink A; | ||||
|  | ||||
|         Attrs[RECIPIENT_EMAIL] = UInfo.email; | ||||
|         Attrs[LOGO] = "logo.jpg"; | ||||
|         Attrs[SUBJECT] = "EMail Address Verification"; | ||||
|         Attrs[ACTION_LINK] = | ||||
|                 Daemon()->GetPublicAPIEndPoint() + "/actionLink?action=email_verification&id=" + UInfo.Id ; | ||||
|         SMTPMailerService()->SendMessage(UInfo.email, "email_verification.txt", Attrs); | ||||
|         A.action = OpenWifi::SecurityObjects::LinkActions::VERIFY_EMAIL; | ||||
|         A.userId = UInfo.email; | ||||
|         A.id = MicroService::CreateUUID(); | ||||
|         A.created = std::time(nullptr); | ||||
|         A.expires = A.created + 24*60*60; | ||||
|         A.userAction = true; | ||||
|         StorageService()->ActionLinksDB().CreateAction(A); | ||||
|         UInfo.waitingForEmailCheck = true; | ||||
|         return true; | ||||
|     } | ||||
|  | ||||
|     bool AuthService::IsValidToken(const std::string &Token, SecurityObjects::WebToken &WebToken, SecurityObjects::UserInfo &UserInfo) { | ||||
|         std::lock_guard G(Mutex_); | ||||
|         auto It = UserCache_.find(Token); | ||||
|     bool AuthService::VerifySubEmail(SecurityObjects::UserInfo &UInfo) { | ||||
|         SecurityObjects::ActionLink A; | ||||
|  | ||||
|         if(It==UserCache_.end()) | ||||
|             return false; | ||||
|         WebToken = It->second.webtoken; | ||||
|         UserInfo = It->second.userinfo; | ||||
|         A.action = OpenWifi::SecurityObjects::LinkActions::SUB_VERIFY_EMAIL; | ||||
|         A.userId = UInfo.email; | ||||
|         A.id = MicroService::CreateUUID(); | ||||
|         A.created = std::time(nullptr); | ||||
|         A.expires = A.created + 24*60*60; | ||||
|         A.userAction = false; | ||||
|         StorageService()->ActionLinksDB().CreateAction(A); | ||||
|         UInfo.waitingForEmailCheck = true; | ||||
|         return true; | ||||
|     } | ||||
|  | ||||
|     bool AuthService::IsValidToken(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()->UserTokenDB().GetToken(TToken, WT, UserId, RevocationDate)) { | ||||
|             if(RevocationDate!=0) | ||||
|                 return false; | ||||
|             Expired = (WT.created_ + WT.expires_in_) < std::time(nullptr); | ||||
|             if(StorageService()->UserDB().GetUserById(UserId,UserInfo)) { | ||||
|                 WebToken = WT; | ||||
|                 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_) < std::time(nullptr); | ||||
|             if(StorageService()->SubDB().GetUserById(UserId,UserInfo)) { | ||||
|                 WebToken = WT; | ||||
|                 return true; | ||||
|             } | ||||
|             return false; | ||||
|         } | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
| }  // end of namespace | ||||
|   | ||||
| @@ -11,15 +11,17 @@ | ||||
|  | ||||
| #include <regex> | ||||
|  | ||||
| #include "SubSystemServer.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 "Poco/Crypto/DigestEngine.h" | ||||
| #include "Poco/HMACEngine.h" | ||||
| #include "Poco/ExpireLRUCache.h" | ||||
|  | ||||
| #include "RESTAPI_SecurityObjects.h" | ||||
| #include "framework/MicroService.h" | ||||
| #include "RESTObjects/RESTAPI_SecurityObjects.h" | ||||
|  | ||||
| namespace OpenWifi{ | ||||
|  | ||||
| @@ -34,16 +36,6 @@ namespace OpenWifi{ | ||||
|             CUSTOM | ||||
|         }; | ||||
|  | ||||
|         enum AUTH_ERROR { | ||||
|             SUCCESS, | ||||
|             PASSWORD_CHANGE_REQUIRED, | ||||
|             INVALID_CREDENTIALS, | ||||
|             PASSWORD_ALREADY_USED, | ||||
|             USERNAME_PENDING_VERIFICATION, | ||||
|             PASSWORD_INVALID, | ||||
|             INTERNAL_ERROR | ||||
|         }; | ||||
|  | ||||
|         enum EMAIL_REASON { | ||||
|             FORGOT_PASSWORD, | ||||
|             EMAIL_VERIFICATION | ||||
| @@ -52,58 +44,122 @@ namespace OpenWifi{ | ||||
|         static ACCESS_TYPE IntToAccessType(int C); | ||||
|         static int AccessTypeToInt(ACCESS_TYPE T); | ||||
|  | ||||
|         static AuthService *instance() { | ||||
|             if (instance_ == nullptr) { | ||||
|                 instance_ = new AuthService; | ||||
|             } | ||||
|         static auto instance() { | ||||
|             static auto instance_ = new AuthService; | ||||
|             return instance_; | ||||
|         } | ||||
|  | ||||
|         int Start() override; | ||||
|         void Stop() override; | ||||
|  | ||||
|         [[nodiscard]] bool IsAuthorized(Poco::Net::HTTPServerRequest & Request,std::string &SessionToken, SecurityObjects::UserInfoAndPolicy & UInfo ); | ||||
|         [[nodiscard]] AUTH_ERROR Authorize( std::string & UserName, const std::string & Password, const std::string & NewPassword, SecurityObjects::UserInfoAndPolicy & UInfo ); | ||||
|         [[nodiscard]] bool IsAuthorized(Poco::Net::HTTPServerRequest & Request,std::string &SessionToken, SecurityObjects::UserInfoAndPolicy & UInfo, bool & Expired); | ||||
|         [[nodiscard]] UNAUTHORIZED_REASON Authorize( std::string & UserName, const std::string & Password, const std::string & NewPassword, SecurityObjects::UserInfoAndPolicy & UInfo, bool & Expired ); | ||||
|         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]] const std:: string & PasswordValidationExpression() const { return PasswordValidationStr_;}; | ||||
|         void Logout(const std::string &token); | ||||
|         void Logout(const std::string &token, bool EraseFromCache=true); | ||||
|  | ||||
|         [[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 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]] 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 SendEmailToUser(std::string &Email, EMAIL_REASON Reason); | ||||
|         [[nodiscard]] bool DeleteUserFromCache(const std::string &UserName); | ||||
|         [[nodiscard]] static bool VerifySubEmail(SecurityObjects::UserInfo &UInfo); | ||||
|  | ||||
|         [[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_; } | ||||
|  | ||||
|     private: | ||||
| 		static AuthService *instance_; | ||||
| 		bool    			Secure_ = false ; | ||||
| 		std::string     	DefaultUserName_; | ||||
| 		std::string			DefaultPassword_; | ||||
| 		std::string     	Mechanism_; | ||||
| 		Poco::JWT::Signer	Signer_; | ||||
| 		Poco::SHA2Engine	SHA2_; | ||||
| 		SecurityObjects::UserInfoCache UserCache_; | ||||
|  | ||||
| 		std::string         AccessPolicy_; | ||||
| 		std::string         PasswordPolicy_; | ||||
| 		std::string         SubAccessPolicy_; | ||||
| 		std::string         SubPasswordPolicy_; | ||||
| 		std::string         PasswordValidationStr_; | ||||
|         std::string         SubPasswordValidationStr_; | ||||
|         std::regex          PasswordValidation_; | ||||
|         std::regex          SubPasswordValidation_; | ||||
|  | ||||
|         uint64_t            TokenAging_ = 30 * 24 * 60 * 60; | ||||
|         uint64_t            HowManyOldPassword_=5; | ||||
|  | ||||
|         class SHA256Engine : public Poco::Crypto::DigestEngine | ||||
|                 { | ||||
|                 public: | ||||
|                     enum | ||||
|                     { | ||||
|                         BLOCK_SIZE = 64, | ||||
|                         DIGEST_SIZE = 32 | ||||
|                     }; | ||||
|  | ||||
|                     SHA256Engine() | ||||
|                     : DigestEngine("SHA256") | ||||
|                     { | ||||
|                     } | ||||
|  | ||||
|                 }; | ||||
|  | ||||
|         Poco::HMACEngine<SHA256Engine> HMAC_{"tipopenwifi"}; | ||||
|  | ||||
|         AuthService() noexcept: | ||||
|             SubSystemServer("Authentication", "AUTH-SVR", "authentication") | ||||
|         { | ||||
|         } | ||||
|     }; | ||||
|  | ||||
|     inline AuthService * AuthService() { return AuthService::instance(); } | ||||
|     inline auto 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 | ||||
|  | ||||
|   | ||||
| @@ -19,13 +19,19 @@ | ||||
|  | ||||
| #include "Daemon.h" | ||||
|  | ||||
| #include "ALBHealthCheckServer.h" | ||||
| #include "KafkaManager.h" | ||||
| #include <aws/core/Aws.h> | ||||
| #include <aws/s3/model/CreateBucketRequest.h> | ||||
| #include <aws/s3/model/PutObjectRequest.h> | ||||
| #include <aws/s3/model/AccessControlPolicy.h> | ||||
| #include <aws/s3/model/PutBucketAclRequest.h> | ||||
| #include <aws/s3/model/GetBucketAclRequest.h> | ||||
|  | ||||
| #include "StorageService.h" | ||||
| #include "RESTAPI_server.h" | ||||
| #include "SMTPMailerService.h" | ||||
| #include "RESTAPI_InternalServer.h" | ||||
| #include "AuthService.h" | ||||
| #include "SMSSender.h" | ||||
| #include "ActionLinkManager.h" | ||||
| #include "TotpCache.h" | ||||
|  | ||||
| namespace OpenWifi { | ||||
|     class Daemon *Daemon::instance_ = nullptr; | ||||
| @@ -37,32 +43,47 @@ namespace OpenWifi { | ||||
|                                    vDAEMON_CONFIG_ENV_VAR, | ||||
|                                    vDAEMON_APP_NAME, | ||||
|                                    vDAEMON_BUS_TIMER, | ||||
|                                    Types::SubSystemVec{ | ||||
|                                            Storage(), | ||||
|                                            RESTAPI_Server(), | ||||
|                                            RESTAPI_InternalServer(), | ||||
|                                    SubSystemVec{ | ||||
|                                            StorageService(), | ||||
|                                            SMSSender(), | ||||
|                                            ActionLinkManager(), | ||||
|                                            SMTPMailerService(), | ||||
|                                            RESTAPI_RateLimiter(), | ||||
|                                            TotpCache(), | ||||
|                                            AuthService() | ||||
|                                    }); | ||||
|         } | ||||
|         return instance_; | ||||
|     } | ||||
|  | ||||
|     void Daemon::initialize(Poco::Util::Application &self) { | ||||
|         MicroService::initialize(*this); | ||||
|     void Daemon::initialize() { | ||||
|         AssetDir_ = MicroService::instance().ConfigPath("openwifi.restapi.wwwassets"); | ||||
|     } | ||||
|  | ||||
|     void MicroServicePostInitialization() { | ||||
|         Daemon()->initialize(); | ||||
|     } | ||||
| } | ||||
|  | ||||
| int main(int argc, char **argv) { | ||||
|     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 ExitCode =  App->run(argc, argv); | ||||
|         delete App; | ||||
|  | ||||
|             ExitCode =  App->run(argc, argv); | ||||
|         } | ||||
|         ShutdownAPI(AwsOptions); | ||||
|         return ExitCode; | ||||
|  | ||||
|     } catch (Poco::Exception &exc) { | ||||
|         std::cerr << exc.displayText() << std::endl; | ||||
|         std::cout << exc.displayText() << std::endl; | ||||
|         return Poco::Util::Application::EXIT_SOFTWARE; | ||||
|     } | ||||
| } | ||||
|   | ||||
							
								
								
									
										10
									
								
								src/Daemon.h
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								src/Daemon.h
									
									
									
									
									
								
							| @@ -20,9 +20,7 @@ | ||||
| #include "Poco/Crypto/CipherFactory.h" | ||||
| #include "Poco/Crypto/Cipher.h" | ||||
|  | ||||
|  | ||||
| #include "OpenWifiTypes.h" | ||||
| #include "MicroService.h" | ||||
| #include "framework/MicroService.h" | ||||
|  | ||||
| namespace OpenWifi { | ||||
|  | ||||
| @@ -39,13 +37,15 @@ namespace OpenWifi { | ||||
|                         const std::string & ConfigEnv, | ||||
|                         const std::string & AppName, | ||||
|                         uint64_t BusTimer, | ||||
|                         const Types::SubSystemVec & SubSystems) : | ||||
|                         const SubSystemVec & SubSystems) : | ||||
|                 MicroService( PropFile, RootEnv, ConfigEnv, AppName, BusTimer, SubSystems) {}; | ||||
|  | ||||
|         void initialize(Poco::Util::Application &self) override; | ||||
|         void initialize(); | ||||
|         static Daemon *instance(); | ||||
|         inline const std::string & AssetDir() { return AssetDir_; } | ||||
|     private: | ||||
|         static Daemon 		*instance_; | ||||
|         std::string         AssetDir_; | ||||
|     }; | ||||
|  | ||||
|     inline Daemon * Daemon() { return Daemon::instance(); } | ||||
|   | ||||
| @@ -1,221 +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 <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 | ||||
| @@ -1,74 +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. | ||||
| // | ||||
|  | ||||
| #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 | ||||
							
								
								
									
										119
									
								
								src/MFAServer.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										119
									
								
								src/MFAServer.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,119 @@ | ||||
| // | ||||
| // 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 = std::time(nullptr); | ||||
|  | ||||
|         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(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 = std::time(nullptr); | ||||
|         for(auto i=begin(Cache_);i!=end(Cache_);) { | ||||
|             if((Now-i->second.Created)>300) { | ||||
|                 i = Cache_.erase(i); | ||||
|             } else { | ||||
|                 ++i; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										68
									
								
								src/MFAServer.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										68
									
								
								src/MFAServer.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,68 @@ | ||||
| // | ||||
| // Created by stephane bourque on 2021-10-11. | ||||
| // | ||||
|  | ||||
| #ifndef OWSEC_MFASERVER_H | ||||
| #define OWSEC_MFASERVER_H | ||||
|  | ||||
| #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(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() { | ||||
|             char buf[16]; | ||||
|             std::sprintf(buf,"%06llu",MicroService::instance().Random(1,999999)); | ||||
|             return buf; | ||||
|         } | ||||
|  | ||||
|     private: | ||||
|         MFAChallengeCache   Cache_; | ||||
|         MFAServer() noexcept: | ||||
|             SubSystemServer("MFServer", "MFA-SVR", "mfa") | ||||
|             { | ||||
|             } | ||||
|  | ||||
|         void CleanCache(); | ||||
|     }; | ||||
|  | ||||
|     inline auto MFAServer() { return MFAServer::instance(); } | ||||
| } | ||||
|  | ||||
| #endif //OWSEC_MFASERVER_H | ||||
| @@ -1,532 +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 <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; | ||||
| 	} | ||||
| } | ||||
| @@ -1,183 +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. | ||||
| // | ||||
|  | ||||
| #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 | ||||
| @@ -1,71 +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 <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; | ||||
| 	} | ||||
| } | ||||
| @@ -1,33 +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. | ||||
| // | ||||
|  | ||||
| #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 | ||||
| @@ -1,106 +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. | ||||
| // | ||||
|  | ||||
| #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 | ||||
							
								
								
									
										162
									
								
								src/RESTAPI/RESTAPI_action_links.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										162
									
								
								src/RESTAPI/RESTAPI_action_links.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,162 @@ | ||||
| // | ||||
| // 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 | ||||
|             return DoReturnA404(); | ||||
|     } | ||||
|  | ||||
|     void RESTAPI_action_links::DoPost() { | ||||
|         auto Action = GetParameter("action",""); | ||||
|  | ||||
|         if(Action=="password_reset") | ||||
|             return CompleteResetPassword(); | ||||
|         else | ||||
|             return DoReturnA404(); | ||||
|     } | ||||
|  | ||||
|     void RESTAPI_action_links::RequestResetPassword(SecurityObjects::ActionLink &Link) { | ||||
|         Logger_.information(Poco::format("REQUEST-PASSWORD-RESET(%s): For ID=%s", 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::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("password1","blu"); | ||||
|             auto Id = Form.get("id",""); | ||||
|             auto Now = std::time(nullptr); | ||||
|  | ||||
|             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 = std::time(nullptr); | ||||
|             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::DoEmailVerification(SecurityObjects::ActionLink &Link) { | ||||
|         auto Now = std::time(nullptr); | ||||
|  | ||||
|         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(Poco::format("EMAIL-VERIFICATION(%s): For ID=%s", Request->clientAddress().toString(), UInfo.email)); | ||||
|         UInfo.waitingForEmailCheck = false; | ||||
|         UInfo.validated = true; | ||||
|         UInfo.lastEmailCheck = std::time(nullptr); | ||||
|         UInfo.validationDate = std::time(nullptr); | ||||
|         UInfo.modified  = std::time(nullptr); | ||||
|         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); | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -2,35 +2,28 @@ | ||||
| // Created by stephane bourque on 2021-06-22.
 | ||||
| //
 | ||||
| 
 | ||||
| #ifndef UCENTRALSEC_RESTAPI_ACTION_LINKS_H | ||||
| #define UCENTRALSEC_RESTAPI_ACTION_LINKS_H | ||||
| #pragma once | ||||
| 
 | ||||
| 
 | ||||
| #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" | ||||
| #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, bool Internal) | ||||
|         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) {} | ||||
|                                         false, | ||||
|                                         true, RateLimit{.Interval=1000,.MaxCalls=10}) {} | ||||
|         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 RequestResetPassword(SecurityObjects::ActionLink &Link); | ||||
|         void CompleteResetPassword(); | ||||
|         void DoEmailVerification(SecurityObjects::ActionLink &Link); | ||||
|         void DoReturnA404(); | ||||
| 
 | ||||
|         void DoGet() final; | ||||
| @@ -39,5 +32,3 @@ namespace OpenWifi { | ||||
|         void DoPut() final {}; | ||||
|     }; | ||||
| } | ||||
| 
 | ||||
| #endif //UCENTRALSEC_RESTAPI_ACTION_LINKS_H
 | ||||
| @@ -2,26 +2,23 @@ | ||||
| // Created by stephane bourque on 2021-07-10.
 | ||||
| //
 | ||||
| 
 | ||||
| #include "RESTAPI_AssetServer.h" | ||||
| #include "RESTAPI_asset_server.h" | ||||
| #include "Poco/File.h" | ||||
| #include "framework/RESTAPI_protocol.h" | ||||
| #include "Daemon.h" | ||||
| #include "RESTAPI_server.h" | ||||
| #include "Utils.h" | ||||
| #include "RESTAPI_protocol.h" | ||||
| 
 | ||||
| namespace OpenWifi { | ||||
|     void RESTAPI_AssetServer::DoGet() { | ||||
|     void RESTAPI_asset_server::DoGet() { | ||||
|         Poco::File  AssetFile; | ||||
| 
 | ||||
|         if(Request->getURI().find("/favicon.ico") != std::string::npos) { | ||||
|             AssetFile = RESTAPI_Server()->AssetDir() + "/favicon.ico"; | ||||
|             AssetFile = Daemon()->AssetDir() + "/favicon.ico"; | ||||
|         } else { | ||||
|             std::string AssetName = GetBinding(RESTAPI::Protocol::ID, ""); | ||||
|             AssetFile = RESTAPI_Server()->AssetDir() + "/" + AssetName; | ||||
|             AssetFile = Daemon()->AssetDir() + "/" + AssetName; | ||||
|         } | ||||
|         if(!AssetFile.isFile()) { | ||||
|             NotFound(); | ||||
|             return; | ||||
|             return NotFound(); | ||||
|         } | ||||
|         SendFile(AssetFile); | ||||
|     } | ||||
| @@ -2,15 +2,14 @@ | ||||
| // Created by stephane bourque on 2021-07-10.
 | ||||
| //
 | ||||
| 
 | ||||
| #ifndef UCENTRALSEC_RESTAPI_ASSETSERVER_H | ||||
| #define UCENTRALSEC_RESTAPI_ASSETSERVER_H | ||||
| #pragma once | ||||
| 
 | ||||
| #include "RESTAPI_handler.h" | ||||
| #include "../framework/MicroService.h" | ||||
| 
 | ||||
| namespace OpenWifi { | ||||
|     class RESTAPI_AssetServer : public RESTAPIHandler { | ||||
|     class RESTAPI_asset_server : public RESTAPIHandler { | ||||
|     public: | ||||
|         RESTAPI_AssetServer(const RESTAPIHandler::BindingMap &bindings, Poco::Logger &L, RESTAPI_GenericServer &Server, bool Internal) | ||||
|         RESTAPI_asset_server(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, | ||||
| @@ -19,7 +18,8 @@ namespace OpenWifi { | ||||
|                                           Poco::Net::HTTPRequest::HTTP_DELETE, | ||||
|                                           Poco::Net::HTTPRequest::HTTP_OPTIONS}, | ||||
|                                           Server, | ||||
|                                           Internal) {} | ||||
|                                           TransactionId, | ||||
|                                           Internal, false) {} | ||||
|         static const std::list<const char *> PathName() { return std::list<const char *>{"/wwwassets/{id}" , | ||||
|                                                                                          "/favicon.ico"}; }; | ||||
|         void DoGet() final; | ||||
| @@ -32,5 +32,3 @@ namespace OpenWifi { | ||||
|     }; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| #endif //UCENTRALSEC_RESTAPI_ASSETSERVER_H
 | ||||
| @@ -5,12 +5,11 @@ | ||||
| #include <fstream> | ||||
| #include <iostream> | ||||
| 
 | ||||
| #include "RESTAPI_avatarHandler.h" | ||||
| #include "RESTAPI_avatar_handler.h" | ||||
| #include "StorageService.h" | ||||
| #include "Daemon.h" | ||||
| #include "Poco/Net/HTMLForm.h" | ||||
| #include "Utils.h" | ||||
| #include "RESTAPI_protocol.h" | ||||
| #include "framework/RESTAPI_protocol.h" | ||||
| #include "framework/MicroService.h" | ||||
| 
 | ||||
| namespace OpenWifi { | ||||
| 
 | ||||
| @@ -23,34 +22,26 @@ namespace OpenWifi { | ||||
|             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(); | ||||
|         Poco::StreamCopier::copyStream(InputStream, OutputStream_); | ||||
|         Length_ = OutputStream_.str().size(); | ||||
|     }; | ||||
| 
 | ||||
|     void RESTAPI_avatarHandler::DoPost() { | ||||
|         std::string Id = GetBinding(RESTAPI::Protocol::ID, ""); | ||||
|     void RESTAPI_avatar_handler::DoPost() { | ||||
|         std::string Id = UserInfo_.userinfo.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); | ||||
| 
 | ||||
|         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()<Daemon()->ConfigGetInt("openwifi.avatar.maxsize",2000000)) { | ||||
| 
 | ||||
|         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(Poco::format("Uploaded avatar: %s Type: %s", partHandler.Name(), partHandler.ContentType())); | ||||
|             Storage()->SetAvatar(UserInfo_.userinfo.email, | ||||
|                                  Id, TmpFile, partHandler.ContentType(), partHandler.Name()); | ||||
|             StorageService()->AvatarDB().SetAvatar(UserInfo_.userinfo.email, | ||||
|                                  Id, SS.str(), partHandler.ContentType(), partHandler.Name()); | ||||
|             StorageService()->UserDB().SetAvatar(Id,"1"); | ||||
|         } else { | ||||
|             Answer.set(RESTAPI::Protocol::AVATARID, Id); | ||||
|             Answer.set(RESTAPI::Protocol::ERRORCODE, 13); | ||||
| @@ -59,31 +50,31 @@ namespace OpenWifi { | ||||
|         ReturnObject(Answer); | ||||
|     } | ||||
| 
 | ||||
|     void RESTAPI_avatarHandler::DoGet() { | ||||
|     void RESTAPI_avatar_handler::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); | ||||
|             return NotFound(); | ||||
|         } | ||||
| 
 | ||||
|     void RESTAPI_avatarHandler::DoDelete() { | ||||
|         std::string Type, Name, AvatarContent; | ||||
|         if (!StorageService()->AvatarDB().GetAvatar(UserInfo_.userinfo.email, Id, AvatarContent, Type, Name)) { | ||||
|             return NotFound(); | ||||
|         } | ||||
|         return SendFileContent(AvatarContent, Type, Name); | ||||
|     } | ||||
| 
 | ||||
|     void RESTAPI_avatar_handler::DoDelete() { | ||||
|         std::string Id = GetBinding(RESTAPI::Protocol::ID, ""); | ||||
|         if (Id.empty()) { | ||||
|             NotFound(); | ||||
|             return; | ||||
| 
 | ||||
|         if(UserInfo_.userinfo.userRole!=SecurityObjects::ROOT && Id!=UserInfo_.userinfo.id) { | ||||
|             return UnAuthorized(RESTAPI::Errors::InsufficientAccessRights, ACCESS_DENIED); | ||||
|         } | ||||
|         if (!Storage()->DeleteAvatar(UserInfo_.userinfo.email, Id)) { | ||||
|             NotFound(); | ||||
|             return; | ||||
| 
 | ||||
|         if (!StorageService()->AvatarDB().DeleteAvatar(UserInfo_.userinfo.email, Id)) { | ||||
|             return NotFound(); | ||||
|         } | ||||
| 
 | ||||
|         StorageService()->UserDB().SetAvatar(Id,""); | ||||
|         OK(); | ||||
|     } | ||||
| } | ||||
| @@ -1,39 +1,36 @@ | ||||
| //
 | ||||
| // Created by stephane bourque on 2021-07-15.
 | ||||
| //
 | ||||
| #pragma once | ||||
| 
 | ||||
| #ifndef UCENTRALSEC_RESTAPI_AVATARHANDLER_H | ||||
| #define UCENTRALSEC_RESTAPI_AVATARHANDLER_H | ||||
| 
 | ||||
| 
 | ||||
| #include "RESTAPI_handler.h" | ||||
| #include "framework/MicroService.h" | ||||
| 
 | ||||
| namespace OpenWifi { | ||||
| 
 | ||||
|     class AvatarPartHandler : public Poco::Net::PartHandler { | ||||
|     public: | ||||
|         AvatarPartHandler(std::string Id, Poco::Logger &Logger, Poco::TemporaryFile &TmpFile) : | ||||
|         AvatarPartHandler(std::string Id, Poco::Logger &Logger, std::stringstream & ofs) : | ||||
|                 Id_(std::move(Id)), | ||||
|                 Logger_(Logger), | ||||
|                 TempFile_(TmpFile){ | ||||
|                 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_; } | ||||
|         [[nodiscard]] std::string FileName() const { return TempFile_.path(); } | ||||
| 
 | ||||
|     private: | ||||
|         uint64_t        Length_ = 0; | ||||
|         std::string     FileType_; | ||||
|         std::string     Name_; | ||||
|         std::string     Id_; | ||||
|         Poco::Logger    &Logger_; | ||||
|         Poco::TemporaryFile &TempFile_; | ||||
|         std::stringstream &OutputStream_; | ||||
|     }; | ||||
| 
 | ||||
|     class RESTAPI_avatarHandler : public RESTAPIHandler { | ||||
|     class RESTAPI_avatar_handler : public RESTAPIHandler { | ||||
|     public: | ||||
|         RESTAPI_avatarHandler(const RESTAPIHandler::BindingMap &bindings, Poco::Logger &L, RESTAPI_GenericServer &Server, bool Internal) | ||||
|         RESTAPI_avatar_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, | ||||
| @@ -41,6 +38,7 @@ namespace OpenWifi { | ||||
|                                          Poco::Net::HTTPRequest::HTTP_DELETE, | ||||
|                                          Poco::Net::HTTPRequest::HTTP_OPTIONS}, | ||||
|                                          Server, | ||||
|                                          TransactionId, | ||||
|                                          Internal) {} | ||||
|         static const std::list<const char *> PathName() { return std::list<const char *>{"/api/v1/avatar/{id}"}; }; | ||||
| 
 | ||||
| @@ -51,4 +49,3 @@ namespace OpenWifi { | ||||
| 
 | ||||
|     }; | ||||
| } | ||||
| #endif //UCENTRALSEC_RESTAPI_AVATARHANDLER_H
 | ||||
							
								
								
									
										17
									
								
								src/RESTAPI/RESTAPI_db_helpers.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								src/RESTAPI/RESTAPI_db_helpers.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,17 @@ | ||||
| // | ||||
| // Created by stephane bourque on 2022-01-01. | ||||
| // | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include "framework/orm.h" | ||||
|  | ||||
| namespace OpenWifi { | ||||
|  | ||||
|     inline void Sanitize(const SecurityObjects::UserInfoAndPolicy &User, SecurityObjects::UserInfo & U) { | ||||
|         U.currentPassword.clear(); | ||||
|         U.lastPasswords.clear(); | ||||
|         U.oauthType.clear(); | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -8,9 +8,9 @@ | ||||
| #include "Poco/Exception.h" | ||||
| #include "Poco/JSON/Parser.h" | ||||
| 
 | ||||
| #include "Daemon.h" | ||||
| #include "SMTPMailerService.h" | ||||
| #include "RESTAPI_errors.h" | ||||
| #include "framework/RESTAPI_errors.h" | ||||
| #include "framework/MicroService.h" | ||||
| 
 | ||||
| namespace OpenWifi { | ||||
|     void RESTAPI_email_handler::DoPost() { | ||||
| @@ -18,18 +18,20 @@ namespace OpenWifi { | ||||
|         if (Obj->has("subject") && | ||||
|             Obj->has("from") && | ||||
|             Obj->has("text") && | ||||
|             Obj->has("recipients")) { | ||||
|             auto   Recipients = Obj->getArray("recipients"); | ||||
|             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] = Recipients->get(0).toString(); | ||||
|             Attrs[RECIPIENT_EMAIL] = Recipient; | ||||
|             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; | ||||
|             Attrs[SENDER] = Obj->get("from").toString(); | ||||
|             if(SMTPMailerService()->SendMessage(Recipient, "password_reset.txt", Attrs)) { | ||||
|                 return OK(); | ||||
|             } | ||||
|             ReturnStatus(Poco::Net::HTTPResponse::HTTP_SERVICE_UNAVAILABLE); | ||||
|             return; | ||||
|             return ReturnStatus(Poco::Net::HTTPResponse::HTTP_SERVICE_UNAVAILABLE); | ||||
|         } | ||||
|         BadRequest(RESTAPI::Errors::MissingOrInvalidParameters); | ||||
|     } | ||||
| @@ -2,20 +2,19 @@ | ||||
| // Created by stephane bourque on 2021-09-02.
 | ||||
| //
 | ||||
| 
 | ||||
| #ifndef OWSEC_RESTAPI_EMAIL_HANDLER_H | ||||
| #define OWSEC_RESTAPI_EMAIL_HANDLER_H | ||||
| #pragma once | ||||
| 
 | ||||
| 
 | ||||
| #include "RESTAPI_handler.h" | ||||
| #include "framework/MicroService.h" | ||||
| 
 | ||||
| namespace OpenWifi { | ||||
|     class RESTAPI_email_handler : public RESTAPIHandler { | ||||
|     public: | ||||
|         RESTAPI_email_handler(const RESTAPIHandler::BindingMap &bindings, Poco::Logger &L, RESTAPI_GenericServer &Server, bool Internal) | ||||
|         RESTAPI_email_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 const std::list<const char *> PathName() { return std::list<const char *>{"/api/v1/email"};} | ||||
|         void DoGet() final {}; | ||||
| @@ -24,5 +23,3 @@ namespace OpenWifi { | ||||
|         void DoPut() final {}; | ||||
|     }; | ||||
| } | ||||
| 
 | ||||
| #endif //OWSEC_RESTAPI_EMAIL_HANDLER_H
 | ||||
							
								
								
									
										159
									
								
								src/RESTAPI/RESTAPI_oauth2_handler.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										159
									
								
								src/RESTAPI/RESTAPI_oauth2_handler.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,159 @@ | ||||
| // | ||||
| //	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/RESTAPI_protocol.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::ExpiredToken,EXPIRED_TOKEN); | ||||
|             return UnAuthorized(RESTAPI::Errors::MissingAuthenticationInformation, INVALID_TOKEN); | ||||
|         } | ||||
|         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; | ||||
|             SecurityObjects::UserInfo   ReturnedUser = UserInfo_.userinfo; | ||||
|             Sanitize(UserInfo_, ReturnedUser); | ||||
|             ReturnedUser.to_json(Me); | ||||
|             return ReturnObject(Me); | ||||
|         } | ||||
|         BadRequest(RESTAPI::Errors::UnrecognizedRequest); | ||||
| 	} | ||||
|  | ||||
|     void RESTAPI_oauth2_handler::DoDelete() { | ||||
| 	    bool Expired = false, Contacted=false; | ||||
| 	    if (!IsAuthorized(Expired, Contacted)) { | ||||
| 	        if(Expired) | ||||
| 	            return UnAuthorized(RESTAPI::Errors::ExpiredToken,EXPIRED_TOKEN); | ||||
| 	        return UnAuthorized(RESTAPI::Errors::MissingAuthenticationInformation, INVALID_TOKEN); | ||||
| 	    } | ||||
|  | ||||
|         auto Token = GetBinding(RESTAPI::Protocol::TOKEN, "..."); | ||||
|         if (Token == SessionToken_) { | ||||
|             AuthService()->Logout(Token); | ||||
|             return ReturnStatus(Poco::Net::HTTPResponse::HTTP_NO_CONTENT, true); | ||||
|         } | ||||
|  | ||||
|         Logger_.information(Poco::format("BAD-LOGOUT(%s): Request for %s", Request->clientAddress().toString(), UserInfo_.userinfo.email)); | ||||
|         NotFound(); | ||||
| 	} | ||||
|  | ||||
| 	void RESTAPI_oauth2_handler::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, AuthService()->GetAccessPolicy()); | ||||
|             Answer.set(RESTAPI::Protocol::PASSWORDPOLICY, AuthService()->GetPasswordPolicy()); | ||||
|             return ReturnObject(Answer); | ||||
|         } | ||||
|  | ||||
|         if(GetBoolParameter(RESTAPI::Protocol::FORGOTPASSWORD,false)) { | ||||
|             SecurityObjects::UserInfo UInfo1; | ||||
|             auto UserExists = StorageService()->UserDB().GetUserByEmail(userId,UInfo1); | ||||
|             if(UserExists) { | ||||
|                 Logger_.information(Poco::format("FORGOTTEN-PASSWORD(%s): Request for %s", Request->clientAddress().toString(), userId)); | ||||
|                 SecurityObjects::ActionLink NewLink; | ||||
|  | ||||
|                 NewLink.action = OpenWifi::SecurityObjects::LinkActions::FORGOT_PASSWORD; | ||||
|                 NewLink.id = MicroService::CreateUUID(); | ||||
|                 NewLink.userId = UInfo1.id; | ||||
|                 NewLink.created = std::time(nullptr); | ||||
|                 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,false)) { | ||||
|             Logger_.information(Poco::format("RESEND-MFA-CODE(%s): Request for %s", Request->clientAddress().toString(), userId)); | ||||
|             if(Obj->has("uuid")) { | ||||
|                 auto uuid = Obj->get("uuid").toString(); | ||||
|                 if(MFAServer()->ResendCode(uuid)) | ||||
|                     return OK(); | ||||
|             } | ||||
|             return UnAuthorized(RESTAPI::Errors::InvalidCredentials, BAD_MFA_TRANSACTION); | ||||
|         } | ||||
|  | ||||
|         if(GetBoolParameter(RESTAPI::Protocol::COMPLETEMFACHALLENGE,false)) { | ||||
|             Logger_.information(Poco::format("COMPLETE-MFA-CHALLENGE(%s): Request for %s", 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::InvalidCredentials, 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::InvalidCredentials, Code); | ||||
|                 case PASSWORD_INVALID: | ||||
|                     return UnAuthorized(RESTAPI::Errors::InvalidPassword, Code); | ||||
|                 case PASSWORD_ALREADY_USED: | ||||
|                     return UnAuthorized(RESTAPI::Errors::PasswordRejected, Code); | ||||
|                 case USERNAME_PENDING_VERIFICATION: | ||||
|                     return UnAuthorized(RESTAPI::Errors::UserPendingVerification, Code); | ||||
|                 case PASSWORD_CHANGE_REQUIRED: | ||||
|                     return UnAuthorized(RESTAPI::Errors::PasswordMustBeChanged, Code); | ||||
|                 default: | ||||
|                     return UnAuthorized(RESTAPI::Errors::InvalidCredentials); break; | ||||
|             } | ||||
|             return; | ||||
|         } | ||||
| 	} | ||||
| } | ||||
| @@ -6,22 +6,21 @@ | ||||
| //	Arilia Wireless Inc.
 | ||||
| //
 | ||||
| 
 | ||||
| #ifndef UCENTRAL_RESTAPI_OAUTH2HANDLER_H | ||||
| #define UCENTRAL_RESTAPI_OAUTH2HANDLER_H | ||||
| 
 | ||||
| #include "RESTAPI_handler.h" | ||||
| #pragma once | ||||
| #include "framework/MicroService.h" | ||||
| 
 | ||||
| namespace OpenWifi { | ||||
| 	class RESTAPI_oauth2Handler : public RESTAPIHandler { | ||||
| 	class RESTAPI_oauth2_handler : public RESTAPIHandler { | ||||
| 	  public: | ||||
| 	    RESTAPI_oauth2Handler(const RESTAPIHandler::BindingMap &bindings, Poco::Logger &L, RESTAPI_GenericServer &Server, bool Internal) | ||||
| 	    RESTAPI_oauth2_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, | ||||
| 													  Internal, false) {} | ||||
|                                                       TransactionId, | ||||
| 													  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"}; }; | ||||
| 		void DoGet() final; | ||||
| 		void DoPost() final; | ||||
| @@ -29,4 +28,5 @@ namespace OpenWifi { | ||||
| 		void DoPut() final {}; | ||||
| 	}; | ||||
| } | ||||
| #endif //UCENTRAL_RESTAPI_OAUTH2HANDLER_H
 | ||||
| 
 | ||||
| 
 | ||||
							
								
								
									
										36
									
								
								src/RESTAPI/RESTAPI_preferences.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								src/RESTAPI/RESTAPI_preferences.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,36 @@ | ||||
| // | ||||
| // 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; | ||||
|  | ||||
|         auto RawObject = ParseStream(); | ||||
|         if(!P.from_json(RawObject)) { | ||||
|             return BadRequest(RESTAPI::Errors::InvalidJSONDocument); | ||||
|         } | ||||
|  | ||||
|         P.id = UserInfo_.userinfo.id; | ||||
|         P.modified = std::time(nullptr); | ||||
|         StorageService()->PreferencesDB().SetPreferences(P); | ||||
|  | ||||
|         Poco::JSON::Object  Answer; | ||||
|         P.to_json(Answer); | ||||
|         ReturnObject(Answer); | ||||
|     } | ||||
|  | ||||
| } | ||||
							
								
								
									
										27
									
								
								src/RESTAPI/RESTAPI_preferences.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								src/RESTAPI/RESTAPI_preferences.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,27 @@ | ||||
| // | ||||
| // 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 const std::list<const char *> PathName() { return std::list<const char *>{"/api/v1/preferences"}; }; | ||||
|         void DoGet() final; | ||||
|         void DoPut() final; | ||||
|         void DoPost() final {}; | ||||
|         void DoDelete() final {}; | ||||
|     }; | ||||
| } | ||||
							
								
								
									
										73
									
								
								src/RESTAPI/RESTAPI_routers.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										73
									
								
								src/RESTAPI/RESTAPI_routers.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,73 @@ | ||||
| // | ||||
| // 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" | ||||
|  | ||||
| namespace OpenWifi { | ||||
|  | ||||
|     Poco::Net::HTTPRequestHandler * RESTAPI_ExtRouter(const char *Path, RESTAPIHandler::BindingMap &Bindings, | ||||
|                                                             Poco::Logger & L, RESTAPI_GenericServer & S, uint64_t TransactionId) { | ||||
|         return RESTAPI_Router< | ||||
|             RESTAPI_oauth2_handler, | ||||
|             RESTAPI_users_handler, | ||||
|             RESTAPI_user_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 | ||||
|         >(Path, Bindings, L, S,TransactionId); | ||||
|     } | ||||
|  | ||||
|     Poco::Net::HTTPRequestHandler * RESTAPI_IntRouter(const char *Path, RESTAPIHandler::BindingMap &Bindings, | ||||
|                                                             Poco::Logger & L, RESTAPI_GenericServer & S, uint64_t TransactionId) { | ||||
|         return RESTAPI_Router_I< | ||||
|             RESTAPI_users_handler, | ||||
|             RESTAPI_user_handler, | ||||
|             RESTAPI_subuser_handler, | ||||
|             RESTAPI_subusers_handler, | ||||
|             RESTAPI_system_command, | ||||
|             RESTAPI_action_links, | ||||
|             RESTAPI_validate_token_handler, | ||||
|             RESTAPI_validate_sub_token_handler, | ||||
|             RESTAPI_sms_handler, | ||||
|             RESTAPI_preferences, | ||||
|             RESTAPI_subpreferences, | ||||
|             RESTAPI_suboauth2_handler, | ||||
|             RESTAPI_submfa_handler | ||||
|         >(Path, Bindings, L, S, TransactionId); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										55
									
								
								src/RESTAPI/RESTAPI_sms_handler.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								src/RESTAPI/RESTAPI_sms_handler.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,55 @@ | ||||
| // | ||||
| // Created by stephane bourque on 2021-10-09. | ||||
| // | ||||
|  | ||||
| #include "RESTAPI_sms_handler.h" | ||||
| #include "SMSSender.h" | ||||
| #include "framework/RESTAPI_errors.h" | ||||
| #include "framework/MicroService.h" | ||||
|  | ||||
| namespace OpenWifi { | ||||
|  | ||||
|     void OpenWifi::RESTAPI_sms_handler::DoPost() { | ||||
|         auto Obj = ParseStream(); | ||||
|  | ||||
|         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::InsufficientAccessRights,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); | ||||
|     } | ||||
|  | ||||
| } | ||||
							
								
								
									
										25
									
								
								src/RESTAPI/RESTAPI_sms_handler.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								src/RESTAPI/RESTAPI_sms_handler.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,25 @@ | ||||
| // | ||||
| // 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 const std::list<const char *> PathName() { return std::list<const char *>{"/api/v1/sms"};} | ||||
|         void DoGet() final {}; | ||||
|         void DoPost() final; | ||||
|         void DoDelete() final {}; | ||||
|         void DoPut() final {}; | ||||
|     }; | ||||
| } | ||||
							
								
								
									
										79
									
								
								src/RESTAPI/RESTAPI_subavatar_handler.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								src/RESTAPI/RESTAPI_subavatar_handler.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,79 @@ | ||||
| // | ||||
| // 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/RESTAPI_protocol.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(Poco::format("Uploaded avatar: %s Type: %s", partHandler.Name(), partHandler.ContentType())); | ||||
|             StorageService()->SubAvatarDB().SetAvatar(UserInfo_.userinfo.email, | ||||
|                                  Id, SS.str(), partHandler.ContentType(), partHandler.Name()); | ||||
|             StorageService()->SubDB().SetAvatar(Id,"1"); | ||||
|         } 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(); | ||||
|         } | ||||
|         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::InsufficientAccessRights, ACCESS_DENIED); | ||||
|         } | ||||
|  | ||||
|         if (!StorageService()->SubAvatarDB().DeleteAvatar(UserInfo_.userinfo.email, Id)) { | ||||
|             return NotFound(); | ||||
|         } | ||||
|         StorageService()->SubDB().SetAvatar(Id,""); | ||||
|         OK(); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										51
									
								
								src/RESTAPI/RESTAPI_subavatar_handler.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								src/RESTAPI/RESTAPI_subavatar_handler.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,51 @@ | ||||
| // | ||||
| // 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_; | ||||
|     }; | ||||
|  | ||||
|     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 const std::list<const char *> PathName() { return std::list<const char *>{"/api/v1/subavatar/{id}"}; }; | ||||
|  | ||||
|         void DoGet() final; | ||||
|         void DoPost() final; | ||||
|         void DoDelete() final; | ||||
|         void DoPut() final {}; | ||||
|  | ||||
|     }; | ||||
| } | ||||
							
								
								
									
										129
									
								
								src/RESTAPI/RESTAPI_submfa_handler.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										129
									
								
								src/RESTAPI/RESTAPI_submfa_handler.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,129 @@ | ||||
| // | ||||
| // 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; | ||||
|  | ||||
|         // std::cout << "submfa get " << UserInfo_.userinfo.Id << "   user:" << UserInfo_.userinfo.email << std::endl; | ||||
|         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 { | ||||
|             auto Body = ParseStream(); | ||||
|  | ||||
|             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("Missing phone number"); | ||||
|                     } | ||||
|  | ||||
|                     if (SMSSender()->StartValidation(MFC.sms, UserInfo_.userinfo.email)) { | ||||
|                         return OK(); | ||||
|                     } else { | ||||
|                         return InternalError("SMS could not be sent. Verify the number or try again later."); | ||||
|                     } | ||||
|                 } else if (GetBoolParameter("completeValidation", false)) { | ||||
|                     auto ChallengeCode = GetParameter("challengeCode", ""); | ||||
|                     if (ChallengeCode.empty()) { | ||||
|                         return BadRequest("Missing 'challengeCode'"); | ||||
|                     } | ||||
|                     if (MFC.sms.empty()) { | ||||
|                         return BadRequest("Missing phone number"); | ||||
|                     } | ||||
|                     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("SMS could not be sent. Verify the number or try again later."); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } catch (const Poco::Exception &E) { | ||||
|             Logger_.log(E); | ||||
|         } | ||||
|         return BadRequest(RESTAPI::Errors::MissingOrInvalidParameters); | ||||
|     } | ||||
|  | ||||
| } | ||||
							
								
								
									
										27
									
								
								src/RESTAPI/RESTAPI_submfa_handler.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								src/RESTAPI/RESTAPI_submfa_handler.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,27 @@ | ||||
| // | ||||
| // 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 const std::list<const char *> PathName() { return std::list<const char *>{"/api/v1/submfa"}; }; | ||||
|         void DoGet() final; | ||||
|         void DoPost() final {}; | ||||
|         void DoDelete() final {}; | ||||
|         void DoPut() final ; | ||||
|     }; | ||||
| } | ||||
							
								
								
									
										153
									
								
								src/RESTAPI/RESTAPI_suboauth2_handler.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										153
									
								
								src/RESTAPI/RESTAPI_suboauth2_handler.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,153 @@ | ||||
| // | ||||
| // Created by stephane bourque on 2021-11-30. | ||||
| // | ||||
|  | ||||
| #include "RESTAPI_suboauth2_handler.h" | ||||
| #include "AuthService.h" | ||||
| #include "MFAServer.h" | ||||
| #include "framework/RESTAPI_protocol.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::ExpiredToken,EXPIRED_TOKEN); | ||||
|             return UnAuthorized(RESTAPI::Errors::MissingAuthenticationInformation, INVALID_TOKEN); | ||||
|         } | ||||
|         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; | ||||
|             SecurityObjects::UserInfo   ReturnedUser = UserInfo_.userinfo; | ||||
|             Sanitize(UserInfo_, ReturnedUser); | ||||
|             ReturnedUser.to_json(Me); | ||||
|             return ReturnObject(Me); | ||||
|         } | ||||
|         BadRequest(RESTAPI::Errors::UnrecognizedRequest); | ||||
|     } | ||||
|  | ||||
|     void RESTAPI_suboauth2_handler::DoDelete() { | ||||
|         bool Expired = false, Contacted = false; | ||||
|         if (!IsAuthorized(Expired, Contacted, true)) { | ||||
|             if(Expired) | ||||
|                 return UnAuthorized(RESTAPI::Errors::ExpiredToken,EXPIRED_TOKEN); | ||||
|             return UnAuthorized(RESTAPI::Errors::MissingAuthenticationInformation, INVALID_TOKEN); | ||||
|         } | ||||
|  | ||||
|         auto Token = GetBinding(RESTAPI::Protocol::TOKEN, "..."); | ||||
|         if (Token == SessionToken_) { | ||||
|             AuthService()->SubLogout(Token); | ||||
|             return ReturnStatus(Poco::Net::HTTPResponse::HTTP_NO_CONTENT, true); | ||||
|         } | ||||
|  | ||||
|         Logger_.information(Poco::format("BAD-LOGOUT(%s): Request for %s", Request->clientAddress().toString(), UserInfo_.userinfo.email)); | ||||
|         NotFound(); | ||||
|     } | ||||
|  | ||||
|     void RESTAPI_suboauth2_handler::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()->SubPasswordValidationExpression()); | ||||
|             Answer.set(RESTAPI::Protocol::ACCESSPOLICY, AuthService()->GetSubAccessPolicy()); | ||||
|             Answer.set(RESTAPI::Protocol::PASSWORDPOLICY, AuthService()->GetSubPasswordPolicy()); | ||||
|             return ReturnObject(Answer); | ||||
|         } | ||||
|  | ||||
|         if(GetBoolParameter(RESTAPI::Protocol::FORGOTPASSWORD,false)) { | ||||
|             SecurityObjects::UserInfo UInfo1; | ||||
|             auto UserExists = StorageService()->SubDB().GetUserByEmail(userId,UInfo1); | ||||
|             if(UserExists) { | ||||
|                 Logger_.information(Poco::format("FORGOTTEN-PASSWORD(%s): Request for %s", 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 = std::time(nullptr); | ||||
|                 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,false)) { | ||||
|             Logger_.information(Poco::format("RESEND-MFA-CODE(%s): Request for %s", Request->clientAddress().toString(), userId)); | ||||
|             if(Obj->has("uuid")) { | ||||
|                 auto uuid = Obj->get("uuid").toString(); | ||||
|                 if(MFAServer()->ResendCode(uuid)) | ||||
|                     return OK(); | ||||
|             } | ||||
|             return UnAuthorized(RESTAPI::Errors::InvalidCredentials, BAD_MFA_TRANSACTION); | ||||
|         } | ||||
|  | ||||
|         if(GetBoolParameter(RESTAPI::Protocol::COMPLETEMFACHALLENGE,false)) { | ||||
|             Logger_.information(Poco::format("COMPLETE-MFA-CHALLENGE(%s): Request for %s", 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::InvalidCredentials, 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::InvalidCredentials, Code); | ||||
|                 case PASSWORD_INVALID: | ||||
|                     return UnAuthorized(RESTAPI::Errors::InvalidPassword, Code); | ||||
|                 case PASSWORD_ALREADY_USED: | ||||
|                     return UnAuthorized(RESTAPI::Errors::PasswordRejected, Code); | ||||
|                 case USERNAME_PENDING_VERIFICATION: | ||||
|                     return UnAuthorized(RESTAPI::Errors::UserPendingVerification, Code); | ||||
|                 case PASSWORD_CHANGE_REQUIRED: | ||||
|                     return UnAuthorized(RESTAPI::Errors::PasswordMustBeChanged, Code); | ||||
|                 default: | ||||
|                     return UnAuthorized(RESTAPI::Errors::InvalidCredentials); break; | ||||
|             } | ||||
|             return; | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										27
									
								
								src/RESTAPI/RESTAPI_suboauth2_handler.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								src/RESTAPI/RESTAPI_suboauth2_handler.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,27 @@ | ||||
| // | ||||
| // 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 const std::list<const char *> PathName() { return std::list<const char *>{"/api/v1/suboauth2/{token}","/api/v1/suboauth2"}; }; | ||||
|         void DoGet() final; | ||||
|         void DoPost() final; | ||||
|         void DoDelete() final; | ||||
|         void DoPut() final {}; | ||||
|     }; | ||||
| } | ||||
							
								
								
									
										36
									
								
								src/RESTAPI/RESTAPI_subpreferences.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								src/RESTAPI/RESTAPI_subpreferences.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,36 @@ | ||||
| // | ||||
| // 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; | ||||
|  | ||||
|         auto RawObject = ParseStream(); | ||||
|         if(!P.from_json(RawObject)) { | ||||
|             return BadRequest(RESTAPI::Errors::InvalidJSONDocument); | ||||
|         } | ||||
|  | ||||
|         P.id = UserInfo_.userinfo.id; | ||||
|         P.modified = std::time(nullptr); | ||||
|         StorageService()->SubPreferencesDB().SetPreferences(P); | ||||
|  | ||||
|         Poco::JSON::Object  Answer; | ||||
|         P.to_json(Answer); | ||||
|         ReturnObject(Answer); | ||||
|     } | ||||
|  | ||||
| } | ||||
							
								
								
									
										27
									
								
								src/RESTAPI/RESTAPI_subpreferences.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								src/RESTAPI/RESTAPI_subpreferences.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,27 @@ | ||||
| // | ||||
| // 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 const std::list<const char *> PathName() { return std::list<const char *>{"/api/v1/subpreferences"}; }; | ||||
|         void DoGet() final; | ||||
|         void DoPut() final; | ||||
|         void DoPost() final {}; | ||||
|         void DoDelete() final {}; | ||||
|     }; | ||||
| } | ||||
							
								
								
									
										38
									
								
								src/RESTAPI/RESTAPI_subtotp_handler.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								src/RESTAPI/RESTAPI_subtotp_handler.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,38 @@ | ||||
| // | ||||
| // 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; | ||||
|  | ||||
|         uint64_t ErrorCode = 0; | ||||
|         std::string ErrorText; | ||||
|         if(TotpCache()->ContinueValidation(UserInfo_.userinfo,true,Value,nextIndex,moreCodes, ErrorCode, ErrorText )) { | ||||
|             Poco::JSON::Object Answer; | ||||
|             Answer.set("nextIndex", nextIndex); | ||||
|             Answer.set("moreCodes", moreCodes); | ||||
|             return ReturnObject(Answer); | ||||
|         } | ||||
|         return BadRequest(ErrorCode, ErrorText); | ||||
|     } | ||||
|  | ||||
| } | ||||
							
								
								
									
										29
									
								
								src/RESTAPI/RESTAPI_subtotp_handler.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								src/RESTAPI/RESTAPI_subtotp_handler.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,29 @@ | ||||
| // | ||||
| // 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 const std::list<const char *> PathName() { return std::list<const char *>{"/api/v1/subtotp"}; }; | ||||
|         void DoGet() final; | ||||
|         void DoPost() final {}; | ||||
|         void DoDelete() final {}; | ||||
|         void DoPut() final; | ||||
|     private: | ||||
|  | ||||
|     }; | ||||
| } | ||||
							
								
								
									
										259
									
								
								src/RESTAPI/RESTAPI_subuser_handler.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										259
									
								
								src/RESTAPI/RESTAPI_subuser_handler.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,259 @@ | ||||
| // | ||||
| // Created by stephane bourque on 2021-11-30. | ||||
| // | ||||
|  | ||||
| #include "RESTAPI_subuser_handler.h" | ||||
| #include "StorageService.h" | ||||
| #include "framework/RESTAPI_errors.h" | ||||
| #include "SMSSender.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(!ACLProcessor::Can(UserInfo_.userinfo, TargetUser,ACLProcessor::DELETE)) { | ||||
|             return UnAuthorized(RESTAPI::Errors::InsufficientAccessRights, 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(Poco::format("User '%s' deleted by '%s'.",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; | ||||
|         RESTAPI_utils::from_request(NewUser,*Request); | ||||
|         if(NewUser.userRole == SecurityObjects::UNKNOWN || NewUser.userRole != SecurityObjects::SUBSCRIBER) { | ||||
|             return BadRequest(RESTAPI::Errors::EntityMustExist); | ||||
|         } | ||||
|  | ||||
|         if(!ACLProcessor::Can(UserInfo_.userinfo,NewUser,ACLProcessor::CREATE)) { | ||||
|             return UnAuthorized(RESTAPI::Errors::InsufficientAccessRights, 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(NewUser.email, NewUser)) { | ||||
|             Logger_.information(Poco::format("Could not add user '%s'.",NewUser.email)); | ||||
|             return BadRequest(RESTAPI::Errors::RecordNotCreated); | ||||
|         } | ||||
|  | ||||
|         if(GetParameter("email_verification","false")=="true") { | ||||
|             if(AuthService::VerifySubEmail(NewUser)) | ||||
|                 Logger_.information(Poco::format("Verification e-mail requested for %s",NewUser.email)); | ||||
|             StorageService()->SubDB().UpdateUserInfo(UserInfo_.userinfo.email,NewUser.id,NewUser); | ||||
|         } | ||||
|  | ||||
|         if(!StorageService()->SubDB().GetUserByEmail(NewUser.email, NewUser)) { | ||||
|             Logger_.information(Poco::format("User '%s' but not retrieved.",NewUser.email)); | ||||
|             return NotFound(); | ||||
|         } | ||||
|  | ||||
|         Poco::JSON::Object  UserInfoObject; | ||||
|         Sanitize(UserInfo_, NewUser); | ||||
|         NewUser.to_json(UserInfoObject); | ||||
|         ReturnObject(UserInfoObject); | ||||
|         Logger_.information(Poco::format("User '%s' has been added by '%s')",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(!ACLProcessor::Can(UserInfo_.userinfo,Existing,ACLProcessor::MODIFY)) { | ||||
|             return UnAuthorized("Insufficient access rights.", ACCESS_DENIED); | ||||
|         } | ||||
|  | ||||
|         SecurityObjects::UserInfo   NewUser; | ||||
|         auto RawObject = ParseStream(); | ||||
|         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::InsufficientAccessRights, ACCESS_DENIED); | ||||
|                 } | ||||
|                 if(Id==UserInfo_.userinfo.id) { | ||||
|                     return UnAuthorized(RESTAPI::Errors::InsufficientAccessRights, 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)std::time(nullptr), .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(Poco::format("Verification e-mail requested for %s",Existing.email)); | ||||
|         } | ||||
|  | ||||
|         if(RawObject->has("userTypeProprietaryInfo")) { | ||||
|             if(NewUser.userTypeProprietaryInfo.mfa.enabled) { | ||||
|                 if (!MFAMETHODS::Validate(NewUser.userTypeProprietaryInfo.mfa.method)) { | ||||
|                     return BadRequest(RESTAPI::Errors::BadMFAMethod); | ||||
|                 } | ||||
|  | ||||
|                 bool ChangingMFA = | ||||
|                         NewUser.userTypeProprietaryInfo.mfa.enabled && !Existing.userTypeProprietaryInfo.mfa.enabled; | ||||
|                 Existing.userTypeProprietaryInfo.mfa.enabled = NewUser.userTypeProprietaryInfo.mfa.enabled; | ||||
|  | ||||
|                 auto PropInfo = RawObject->get("userTypeProprietaryInfo"); | ||||
|                 if (ChangingMFA && NewUser.userTypeProprietaryInfo.mfa.method == MFAMETHODS::SMS) { | ||||
|                     auto PInfo = PropInfo.extract<Poco::JSON::Object::Ptr>(); | ||||
|                     if (PInfo->isArray("mobiles")) { | ||||
|                         Existing.userTypeProprietaryInfo.mobiles = NewUser.userTypeProprietaryInfo.mobiles; | ||||
|                     } | ||||
|                     if (NewUser.userTypeProprietaryInfo.mobiles.empty() || | ||||
|                         !SMSSender()->IsNumberValid(NewUser.userTypeProprietaryInfo.mobiles[0].number, | ||||
|                                                     UserInfo_.userinfo.email)) { | ||||
|                         return BadRequest(RESTAPI::Errors::NeedMobileNumber); | ||||
|                     } | ||||
|                     Existing.userTypeProprietaryInfo.authenticatorSecret.clear(); | ||||
|                 } else if (ChangingMFA && 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 (ChangingMFA && NewUser.userTypeProprietaryInfo.mfa.method == MFAMETHODS::EMAIL) { | ||||
|                     // nothing to do for email. | ||||
|                     Existing.userTypeProprietaryInfo.mobiles.clear(); | ||||
|                     Existing.userTypeProprietaryInfo.authenticatorSecret.clear(); | ||||
|                 } | ||||
|                 Existing.userTypeProprietaryInfo.mfa.method = NewUser.userTypeProprietaryInfo.mfa.method; | ||||
|                 Existing.userTypeProprietaryInfo.mfa.enabled = true; | ||||
|             } 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); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										31
									
								
								src/RESTAPI/RESTAPI_subuser_handler.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								src/RESTAPI/RESTAPI_subuser_handler.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,31 @@ | ||||
| // | ||||
| // 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 const std::list<const char *> PathName() { return std::list<const char *>{"/api/v1/subuser/{id}"}; }; | ||||
|         void DoGet() final; | ||||
|         void DoPost() final; | ||||
|         void DoDelete() final; | ||||
|         void DoPut() final; | ||||
|     private: | ||||
|  | ||||
|     }; | ||||
| } | ||||
							
								
								
									
										55
									
								
								src/RESTAPI/RESTAPI_subusers_handler.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								src/RESTAPI/RESTAPI_subusers_handler.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,55 @@ | ||||
| // | ||||
| // Created by stephane bourque on 2021-11-30. | ||||
| // | ||||
|  | ||||
| #include "RESTAPI_subusers_handler.h" | ||||
| #include "StorageService.h" | ||||
| #include "framework/RESTAPI_protocol.h" | ||||
| #include "framework/MicroService.h" | ||||
| #include "RESTAPI/RESTAPI_db_helpers.h" | ||||
|  | ||||
| namespace OpenWifi { | ||||
|  | ||||
|     void RESTAPI_subusers_handler::DoGet() { | ||||
|         std::vector<SecurityObjects::UserInfo> Users; | ||||
|         bool IdOnly = (GetParameter("idOnly","false")=="true"); | ||||
|  | ||||
|         if(QB_.Select.empty()) { | ||||
|             Poco::JSON::Array ArrayObj; | ||||
|             Poco::JSON::Object Answer; | ||||
|             if (StorageService()->SubDB().GetUsers(QB_.Offset, QB_.Limit, Users)) { | ||||
|                 for (auto &i : Users) { | ||||
|                     Poco::JSON::Object Obj; | ||||
|                     if (IdOnly) { | ||||
|                         ArrayObj.add(i.id); | ||||
|                     } else { | ||||
|                         Sanitize(UserInfo_, i); | ||||
|                         i.to_json(Obj); | ||||
|                         ArrayObj.add(Obj); | ||||
|                     } | ||||
|                 } | ||||
|                 Answer.set(RESTAPI::Protocol::USERS, ArrayObj); | ||||
|             } | ||||
|             return ReturnObject(Answer); | ||||
|         } else { | ||||
|             Poco::JSON::Array ArrayObj; | ||||
|             for(auto &i:SelectedRecords()) { | ||||
|                 SecurityObjects::UserInfo   UInfo; | ||||
|                 auto tI{i}; | ||||
|                 if(StorageService()->SubDB().GetUserById(tI,UInfo)) { | ||||
|                     Poco::JSON::Object Obj; | ||||
|                     if (IdOnly) { | ||||
|                         ArrayObj.add(UInfo.id); | ||||
|                     } else { | ||||
|                         Sanitize(UserInfo_, UInfo); | ||||
|                         UInfo.to_json(Obj); | ||||
|                         ArrayObj.add(Obj); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|             Poco::JSON::Object RetObj; | ||||
|             RetObj.set(RESTAPI::Protocol::USERS, ArrayObj); | ||||
|             return ReturnObject(RetObj); | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										26
									
								
								src/RESTAPI/RESTAPI_subusers_handler.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								src/RESTAPI/RESTAPI_subusers_handler.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | ||||
| // | ||||
| // 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 const std::list<const char *> PathName() { return std::list<const char *>{"/api/v1/subusers"}; }; | ||||
|         void DoGet() final; | ||||
|         void DoPost() final {}; | ||||
|         void DoDelete() final {}; | ||||
|         void DoPut() final {}; | ||||
|     }; | ||||
| }; | ||||
| @@ -2,14 +2,13 @@ | ||||
| // Created by stephane bourque on 2021-07-01.
 | ||||
| //
 | ||||
| 
 | ||||
| #include "RESTAPI_systemEndpoints_handler.h" | ||||
| #include "Daemon.h" | ||||
| #include "RESTAPI_SecurityObjects.h" | ||||
| #include "RESTAPI_system_endpoints_handler.h" | ||||
| #include "RESTObjects/RESTAPI_SecurityObjects.h" | ||||
| 
 | ||||
| namespace OpenWifi { | ||||
| 
 | ||||
|     void RESTAPI_systemEndpoints_handler::DoGet() { | ||||
|         auto Services = Daemon()->GetServices(); | ||||
|     void RESTAPI_system_endpoints_handler::DoGet() { | ||||
|         auto Services = MicroService::instance().GetServices(); | ||||
|         SecurityObjects::SystemEndpointList L; | ||||
|         for(const auto &i:Services) { | ||||
|             SecurityObjects::SystemEndpoint S{ | ||||
| @@ -2,18 +2,19 @@ | ||||
| // Created by stephane bourque on 2021-07-01.
 | ||||
| //
 | ||||
| 
 | ||||
| #ifndef UCENTRALSEC_RESTAPI_SYSTEMENDPOINTS_HANDLER_H | ||||
| #define UCENTRALSEC_RESTAPI_SYSTEMENDPOINTS_HANDLER_H | ||||
| #pragma once | ||||
| 
 | ||||
| #include "../framework/MicroService.h" | ||||
| 
 | ||||
| #include "RESTAPI_handler.h" | ||||
| namespace OpenWifi { | ||||
|     class RESTAPI_systemEndpoints_handler : public RESTAPIHandler { | ||||
|     class RESTAPI_system_endpoints_handler : public RESTAPIHandler { | ||||
|     public: | ||||
|         RESTAPI_systemEndpoints_handler(const RESTAPIHandler::BindingMap &bindings, Poco::Logger &L, RESTAPI_GenericServer &Server, bool Internal) | ||||
|         RESTAPI_system_endpoints_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 const std::list<const char *> PathName() { return std::list<const char *>{"/api/v1/systemEndpoints"}; }; | ||||
|         void DoGet() final; | ||||
| @@ -22,5 +23,3 @@ namespace OpenWifi { | ||||
|         void DoPut() final {}; | ||||
|     }; | ||||
| } | ||||
| 
 | ||||
| #endif //UCENTRALSEC_RESTAPI_SYSTEMENDPOINTS_HANDLER_H
 | ||||
							
								
								
									
										37
									
								
								src/RESTAPI/RESTAPI_totp_handler.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								src/RESTAPI/RESTAPI_totp_handler.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,37 @@ | ||||
| // | ||||
| // 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; | ||||
|  | ||||
|         uint64_t ErrorCode = 0; | ||||
|         std::string ErrorText; | ||||
|         if(TotpCache()->ContinueValidation(UserInfo_.userinfo,false,Value,nextIndex,moreCodes, ErrorCode, ErrorText )) { | ||||
|             Poco::JSON::Object Answer; | ||||
|             Answer.set("nextIndex", nextIndex); | ||||
|             Answer.set("moreCodes", moreCodes); | ||||
|             return ReturnObject(Answer); | ||||
|         } | ||||
|         return BadRequest(ErrorCode, ErrorText); | ||||
|     } | ||||
|  | ||||
| } | ||||
							
								
								
									
										31
									
								
								src/RESTAPI/RESTAPI_totp_handler.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								src/RESTAPI/RESTAPI_totp_handler.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,31 @@ | ||||
| // | ||||
| // 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 const std::list<const char *> PathName() { return std::list<const char *>{"/api/v1/totp"}; }; | ||||
|         void DoGet() final; | ||||
|         void DoPost() final {}; | ||||
|         void DoDelete() final {}; | ||||
|         void DoPut() final; | ||||
|     private: | ||||
|  | ||||
|     }; | ||||
| } | ||||
							
								
								
									
										268
									
								
								src/RESTAPI/RESTAPI_user_handler.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										268
									
								
								src/RESTAPI/RESTAPI_user_handler.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,268 @@ | ||||
| // | ||||
| // Created by stephane bourque on 2021-06-21. | ||||
| // | ||||
|  | ||||
| #include "RESTAPI_user_handler.h" | ||||
| #include "StorageService.h" | ||||
| #include "framework/RESTAPI_errors.h" | ||||
| #include "SMSSender.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::InsufficientAccessRights, 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::InsufficientAccessRights, 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(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") { | ||||
|             return BadRequest(RESTAPI::Errors::IdMustBe0); | ||||
|         } | ||||
|  | ||||
|         SecurityObjects::UserInfo   NewUser; | ||||
|         RESTAPI_utils::from_request(NewUser,*Request); | ||||
|         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::InsufficientAccessRights, ACCESS_DENIED); | ||||
|         } | ||||
|  | ||||
|         Poco::toLowerInPlace(NewUser.email); | ||||
|         if(!Utils::ValidEMailAddress(NewUser.email)) { | ||||
|             return BadRequest(RESTAPI::Errors::InvalidEmailAddress); | ||||
|         } | ||||
|  | ||||
|         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(); | ||||
|  | ||||
|         if(!StorageService()->UserDB().CreateUser(NewUser.email,NewUser)) { | ||||
|             Logger_.information(Poco::format("Could not add user '%s'.",NewUser.email)); | ||||
|             return BadRequest(RESTAPI::Errors::RecordNotCreated); | ||||
|         } | ||||
|  | ||||
|         if(GetParameter("email_verification","false")=="true") { | ||||
|             if(AuthService::VerifyEmail(NewUser)) | ||||
|                 Logger_.information(Poco::format("Verification e-mail requested for %s",NewUser.email)); | ||||
|             StorageService()->UserDB().UpdateUserInfo(UserInfo_.userinfo.email,NewUser.id,NewUser); | ||||
|         } | ||||
|  | ||||
|         if(!StorageService()->UserDB().GetUserByEmail(NewUser.email, NewUser)) { | ||||
|             Logger_.information(Poco::format("User '%s' but not retrieved.",NewUser.email)); | ||||
|             return NotFound(); | ||||
|         } | ||||
|  | ||||
|         Poco::JSON::Object  UserInfoObject; | ||||
|         Sanitize(UserInfo_, NewUser); | ||||
|         NewUser.to_json(UserInfoObject); | ||||
|         ReturnObject(UserInfoObject); | ||||
|         Logger_.information(Poco::format("User '%s' has been added by '%s')",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("Insufficient access rights.", ACCESS_DENIED); | ||||
|         } | ||||
|  | ||||
|         SecurityObjects::UserInfo   NewUser; | ||||
|         auto RawObject = ParseStream(); | ||||
|         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::InsufficientAccessRights, ACCESS_DENIED); | ||||
|                 } | ||||
|                 if(Id==UserInfo_.userinfo.id) { | ||||
|                     return UnAuthorized(RESTAPI::Errors::InsufficientAccessRights, 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)std::time(nullptr), .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(GetParameter("email_verification","false")=="true") { | ||||
|             if(AuthService::VerifyEmail(Existing)) | ||||
|                 Logger_.information(Poco::format("Verification e-mail requested for %s",Existing.email)); | ||||
|         } | ||||
|  | ||||
|         if(RawObject->has("userTypeProprietaryInfo")) { | ||||
|             if(NewUser.userTypeProprietaryInfo.mfa.enabled) { | ||||
|                 if (!MFAMETHODS::Validate(NewUser.userTypeProprietaryInfo.mfa.method)) { | ||||
|                     return BadRequest(RESTAPI::Errors::BadMFAMethod); | ||||
|                 } | ||||
|  | ||||
|                 bool ChangingMFA = | ||||
|                         NewUser.userTypeProprietaryInfo.mfa.enabled && !Existing.userTypeProprietaryInfo.mfa.enabled; | ||||
|                 Existing.userTypeProprietaryInfo.mfa.enabled = NewUser.userTypeProprietaryInfo.mfa.enabled; | ||||
|  | ||||
|                 auto PropInfo = RawObject->get("userTypeProprietaryInfo"); | ||||
|                 if (ChangingMFA && NewUser.userTypeProprietaryInfo.mfa.method == MFAMETHODS::SMS) { | ||||
|                     auto PInfo = PropInfo.extract<Poco::JSON::Object::Ptr>(); | ||||
|                     if (PInfo->isArray("mobiles")) { | ||||
|                         Existing.userTypeProprietaryInfo.mobiles = NewUser.userTypeProprietaryInfo.mobiles; | ||||
|                     } | ||||
|                     if (NewUser.userTypeProprietaryInfo.mobiles.empty() || | ||||
|                         !SMSSender()->IsNumberValid(NewUser.userTypeProprietaryInfo.mobiles[0].number, | ||||
|                                                     UserInfo_.userinfo.email)) { | ||||
|                         return BadRequest(RESTAPI::Errors::NeedMobileNumber); | ||||
|                     } | ||||
|                     Existing.userTypeProprietaryInfo.authenticatorSecret.clear(); | ||||
|                 } else if (ChangingMFA && 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 (ChangingMFA && NewUser.userTypeProprietaryInfo.mfa.method == MFAMETHODS::EMAIL) { | ||||
|                     // nothing to do for email. | ||||
|                     Existing.userTypeProprietaryInfo.mobiles.clear(); | ||||
|                     Existing.userTypeProprietaryInfo.authenticatorSecret.clear(); | ||||
|                 } | ||||
|                 Existing.userTypeProprietaryInfo.mfa.method = NewUser.userTypeProprietaryInfo.mfa.method; | ||||
|                 Existing.userTypeProprietaryInfo.mfa.enabled = true; | ||||
|             } else { | ||||
|                 Existing.userTypeProprietaryInfo.authenticatorSecret.clear(); | ||||
|                 Existing.userTypeProprietaryInfo.mobiles.clear(); | ||||
|                 Existing.userTypeProprietaryInfo.mfa.enabled = false; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         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); | ||||
|     } | ||||
| } | ||||
| @@ -2,15 +2,14 @@ | ||||
| // Created by stephane bourque on 2021-06-21.
 | ||||
| //
 | ||||
| 
 | ||||
| #ifndef UCENTRALSEC_RESTAPI_USER_HANDLER_H | ||||
| #define UCENTRALSEC_RESTAPI_USER_HANDLER_H | ||||
| #pragma once | ||||
| 
 | ||||
| #include "RESTAPI_handler.h" | ||||
| #include "framework/MicroService.h" | ||||
| 
 | ||||
| namespace OpenWifi { | ||||
|     class RESTAPI_user_handler : public RESTAPIHandler { | ||||
|     public: | ||||
|         RESTAPI_user_handler(const RESTAPIHandler::BindingMap &bindings, Poco::Logger &L, RESTAPI_GenericServer &Server, bool Internal) | ||||
|         RESTAPI_user_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, | ||||
| @@ -19,6 +18,7 @@ namespace OpenWifi { | ||||
|                                           Poco::Net::HTTPRequest::HTTP_DELETE, | ||||
|                                           Poco::Net::HTTPRequest::HTTP_OPTIONS}, | ||||
|                                           Server, | ||||
|                                           TransactionId, | ||||
|                                           Internal) {} | ||||
|         static const std::list<const char *> PathName() { return std::list<const char *>{"/api/v1/user/{id}"}; }; | ||||
|         void DoGet() final; | ||||
| @@ -29,6 +29,3 @@ namespace OpenWifi { | ||||
| 
 | ||||
|     }; | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| #endif //UCENTRALSEC_RESTAPI_USER_HANDLER_H
 | ||||
| @@ -4,8 +4,9 @@ | ||||
| 
 | ||||
| #include "RESTAPI_users_handler.h" | ||||
| #include "StorageService.h" | ||||
| #include "RESTAPI_protocol.h" | ||||
| #include "Utils.h" | ||||
| #include "framework/RESTAPI_protocol.h" | ||||
| #include "framework/MicroService.h" | ||||
| #include "RESTAPI/RESTAPI_db_helpers.h" | ||||
| 
 | ||||
| namespace OpenWifi { | ||||
|     void RESTAPI_users_handler::DoGet() { | ||||
| @@ -15,30 +16,31 @@ namespace OpenWifi { | ||||
|         if(QB_.Select.empty()) { | ||||
|             Poco::JSON::Array ArrayObj; | ||||
|             Poco::JSON::Object Answer; | ||||
|             if (Storage()->GetUsers(QB_.Offset, QB_.Limit, Users)) { | ||||
|                 for (const auto &i : Users) { | ||||
|             if (StorageService()->UserDB().GetUsers(QB_.Offset, QB_.Limit, Users)) { | ||||
|                 for (auto &i : Users) { | ||||
|                     Poco::JSON::Object Obj; | ||||
|                     if (IdOnly) { | ||||
|                         ArrayObj.add(i.Id); | ||||
|                         ArrayObj.add(i.id); | ||||
|                     } else { | ||||
|                         Sanitize(UserInfo_, i); | ||||
|                         i.to_json(Obj); | ||||
|                         ArrayObj.add(Obj); | ||||
|                     } | ||||
|                 } | ||||
|                 Answer.set(RESTAPI::Protocol::USERS, ArrayObj); | ||||
|             } | ||||
|             ReturnObject(Answer); | ||||
|             return; | ||||
|             return ReturnObject(Answer); | ||||
|         } else { | ||||
|             Types::StringVec IDs = Utils::Split(QB_.Select); | ||||
|             Poco::JSON::Array ArrayObj; | ||||
|             for(auto &i:IDs) { | ||||
|             for(auto &i:SelectedRecords()) { | ||||
|                 SecurityObjects::UserInfo   UInfo; | ||||
|                 if(Storage()->GetUserById(i,UInfo)) { | ||||
|                 auto tI{i}; | ||||
|                 if(StorageService()->UserDB().GetUserById(i,UInfo)) { | ||||
|                     Poco::JSON::Object Obj; | ||||
|                     if (IdOnly) { | ||||
|                         ArrayObj.add(UInfo.Id); | ||||
|                         ArrayObj.add(UInfo.id); | ||||
|                     } else { | ||||
|                         Sanitize(UserInfo_, UInfo); | ||||
|                         UInfo.to_json(Obj); | ||||
|                         ArrayObj.add(Obj); | ||||
|                     } | ||||
| @@ -46,8 +48,7 @@ namespace OpenWifi { | ||||
|             } | ||||
|             Poco::JSON::Object RetObj; | ||||
|             RetObj.set(RESTAPI::Protocol::USERS, ArrayObj); | ||||
|             ReturnObject(RetObj); | ||||
|             return; | ||||
|             return ReturnObject(RetObj); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -2,20 +2,20 @@ | ||||
| // Created by stephane bourque on 2021-06-21.
 | ||||
| //
 | ||||
| 
 | ||||
| #ifndef UCENTRALSEC_RESTAPI_USERS_HANDLER_H | ||||
| #define UCENTRALSEC_RESTAPI_USERS_HANDLER_H | ||||
| #pragma once | ||||
| 
 | ||||
| #include "RESTAPI_handler.h" | ||||
| #include "framework/MicroService.h" | ||||
| 
 | ||||
| namespace OpenWifi { | ||||
|     class RESTAPI_users_handler : public RESTAPIHandler { | ||||
|     public: | ||||
|         RESTAPI_users_handler(const RESTAPIHandler::BindingMap &bindings, Poco::Logger &L, RESTAPI_GenericServer &Server, bool Internal) | ||||
|         RESTAPI_users_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 const std::list<const char *> PathName() { return std::list<const char *>{"/api/v1/users"}; }; | ||||
|         void DoGet() final; | ||||
| @@ -25,5 +25,3 @@ namespace OpenWifi { | ||||
|     }; | ||||
| }; | ||||
| 
 | ||||
| 
 | ||||
| #endif //UCENTRALSEC_RESTAPI_USERS_HANDLER_H
 | ||||
							
								
								
									
										26
									
								
								src/RESTAPI/RESTAPI_validate_sub_token_handler.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								src/RESTAPI/RESTAPI_validate_sub_token_handler.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | ||||
| // | ||||
| // 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(); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										26
									
								
								src/RESTAPI/RESTAPI_validate_sub_token_handler.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								src/RESTAPI/RESTAPI_validate_sub_token_handler.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | ||||
| // | ||||
| // 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 const std::list<const char *> PathName() { return std::list<const char *>{"/api/v1/validateSubToken"}; }; | ||||
|         void DoGet() final; | ||||
|         void DoPost() final {}; | ||||
|         void DoDelete() final {}; | ||||
|         void DoPut() final {}; | ||||
|     }; | ||||
| } | ||||
| @@ -2,27 +2,25 @@ | ||||
| // Created by stephane bourque on 2021-07-01.
 | ||||
| //
 | ||||
| 
 | ||||
| #include "RESTAPI_validateToken_handler.h" | ||||
| #include "Daemon.h" | ||||
| #include "RESTAPI_validate_token_handler.h" | ||||
| #include "AuthService.h" | ||||
| #include "Utils.h" | ||||
| 
 | ||||
| namespace OpenWifi { | ||||
|     void RESTAPI_validateToken_handler::DoGet() { | ||||
|     void RESTAPI_validate_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; | ||||
|                 if (AuthService()->IsValidToken(i.second, SecObj.webtoken, SecObj.userinfo)) { | ||||
|                 bool Expired = false; | ||||
|                 if (AuthService()->IsValidToken(i.second, SecObj.webtoken, SecObj.userinfo, Expired)) { | ||||
|                     Poco::JSON::Object Obj; | ||||
|                     SecObj.to_json(Obj); | ||||
|                     ReturnObject(Obj); | ||||
|                     return; | ||||
|                     return ReturnObject(Obj); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         NotFound(); | ||||
|         return NotFound(); | ||||
|     } | ||||
| } | ||||
| @@ -2,20 +2,20 @@ | ||||
| // Created by stephane bourque on 2021-07-01.
 | ||||
| //
 | ||||
| 
 | ||||
| #ifndef UCENTRALSEC_RESTAPI_VALIDATETOKEN_HANDLER_H | ||||
| #define UCENTRALSEC_RESTAPI_VALIDATETOKEN_HANDLER_H | ||||
| #pragma once | ||||
| 
 | ||||
| #include "RESTAPI_handler.h" | ||||
| #include "framework/MicroService.h" | ||||
| 
 | ||||
| namespace OpenWifi { | ||||
|     class RESTAPI_validateToken_handler : public RESTAPIHandler { | ||||
|     class RESTAPI_validate_token_handler : public RESTAPIHandler { | ||||
|     public: | ||||
|         RESTAPI_validateToken_handler(const RESTAPIHandler::BindingMap &bindings, Poco::Logger &L, RESTAPI_GenericServer &Server, bool Internal) | ||||
|         RESTAPI_validate_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 const std::list<const char *> PathName() { return std::list<const char *>{"/api/v1/validateToken"}; }; | ||||
|         void DoGet() final; | ||||
| @@ -25,4 +25,3 @@ namespace OpenWifi { | ||||
|     }; | ||||
| } | ||||
| 
 | ||||
| #endif //UCENTRALSEC_RESTAPI_VALIDATETOKEN_HANDLER_H
 | ||||
| @@ -1,5 +0,0 @@ | ||||
| // | ||||
| // Created by stephane bourque on 2021-09-15. | ||||
| // | ||||
|  | ||||
| #include "RESTAPI_GenericServer.h" | ||||
| @@ -1,78 +0,0 @@ | ||||
| // | ||||
| // 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 | ||||
| @@ -1,80 +0,0 @@ | ||||
| // | ||||
| // 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_); | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -1,57 +0,0 @@ | ||||
| // | ||||
| // 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 | ||||
| @@ -1,181 +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. | ||||
| // | ||||
|  | ||||
| #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 | ||||
| @@ -1,132 +0,0 @@ | ||||
| // | ||||
| // 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); | ||||
|     } | ||||
|  | ||||
| } | ||||
| @@ -1,479 +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 <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); | ||||
| 	} | ||||
| } | ||||
| @@ -1,233 +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. | ||||
| // | ||||
|  | ||||
| #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 | ||||
| @@ -1,100 +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_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; | ||||
|         } | ||||
| 	} | ||||
| } | ||||
| @@ -1,92 +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 <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 | ||||
| @@ -1,71 +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. | ||||
| // | ||||
|  | ||||
| #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 | ||||
| @@ -1,146 +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 "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); | ||||
| 	} | ||||
| } | ||||
| @@ -1,32 +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. | ||||
| // | ||||
|  | ||||
| #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 | ||||
| @@ -1,186 +0,0 @@ | ||||
| // | ||||
| // 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); | ||||
|     } | ||||
| } | ||||
| @@ -1,17 +0,0 @@ | ||||
| // | ||||
| // Created by stephane bourque on 2021-07-05. | ||||
| // | ||||
|  | ||||
| #include "RESTAPI_utils.h" | ||||
|  | ||||
| namespace OpenWifi::RESTAPI_utils { | ||||
|  | ||||
| 	void EmbedDocument(const std::string & ObjName, Poco::JSON::Object & Obj, const std::string &ObjStr) { | ||||
| 		std::string D = ObjStr.empty() ? "{}" : ObjStr; | ||||
| 		Poco::JSON::Parser P; | ||||
| 		Poco::Dynamic::Var result = P.parse(D); | ||||
| 		const auto &DetailsObj = result.extract<Poco::JSON::Object::Ptr>(); | ||||
| 		Obj.set(ObjName, DetailsObj); | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @@ -1,216 +0,0 @@ | ||||
| // | ||||
| // Created by stephane bourque on 2021-07-05. | ||||
| // | ||||
|  | ||||
| #ifndef UCENTRALGW_RESTAPI_UTILS_H | ||||
| #define UCENTRALGW_RESTAPI_UTILS_H | ||||
| #include <functional> | ||||
|  | ||||
| #include "Poco/JSON/Object.h" | ||||
| #include "Poco/JSON/Parser.h" | ||||
| #include "Poco/Net/HTTPServerRequest.h" | ||||
| #include "OpenWifiTypes.h" | ||||
| #include "Utils.h" | ||||
|  | ||||
| namespace OpenWifi::RESTAPI_utils { | ||||
|  | ||||
| 	void EmbedDocument(const std::string & ObjName, Poco::JSON::Object & Obj, const std::string &ObjStr); | ||||
|  | ||||
| 	inline void field_to_json(Poco::JSON::Object &Obj, const char *Field, bool V) { | ||||
| 		Obj.set(Field,V); | ||||
| 	} | ||||
|  | ||||
| 	inline void field_to_json(Poco::JSON::Object &Obj, const char *Field, const std::string & S) { | ||||
| 		Obj.set(Field,S); | ||||
| 	} | ||||
|  | ||||
| 	inline void field_to_json(Poco::JSON::Object &Obj, const char *Field, const char * S) { | ||||
| 		Obj.set(Field,S); | ||||
| 	} | ||||
|  | ||||
| 	inline void field_to_json(Poco::JSON::Object &Obj, const char *Field, uint64_t V) { | ||||
| 		Obj.set(Field,V); | ||||
| 	} | ||||
|  | ||||
| 	inline void field_to_json(Poco::JSON::Object &Obj, const char *Field, const Types::StringVec &V) { | ||||
| 		Poco::JSON::Array	A; | ||||
| 		for(const auto &i:V) | ||||
| 			A.add(i); | ||||
| 		Obj.set(Field,A); | ||||
| 	} | ||||
|  | ||||
|     inline void field_to_json(Poco::JSON::Object &Obj, const char *Field, const Types::CountedMap &M) { | ||||
|         Poco::JSON::Array	A; | ||||
|         for(const auto &[Key,Value]:M) { | ||||
|             Poco::JSON::Object  O; | ||||
|             O.set("tag",Key); | ||||
|             O.set("value", Value); | ||||
|             A.add(O); | ||||
|         } | ||||
|         Obj.set(Field,A); | ||||
|     } | ||||
|  | ||||
|  | ||||
|     template<typename T> void field_to_json(Poco::JSON::Object &Obj, | ||||
| 					   						const char *Field, | ||||
| 					   						const T &V, | ||||
| 											std::function<std::string(const T &)> F) { | ||||
| 		Obj.set(Field, F(V)); | ||||
| 	} | ||||
|  | ||||
| 	template<typename T> bool field_from_json(Poco::JSON::Object::Ptr Obj, const char *Field, T & V, | ||||
| 											std::function<T(const std::string &)> F) { | ||||
| 		if(Obj->has(Field)) | ||||
| 			V = F(Obj->get(Field).toString()); | ||||
| 		return true; | ||||
| 	} | ||||
|  | ||||
| 	inline void field_from_json(Poco::JSON::Object::Ptr Obj, const char *Field, std::string &S) { | ||||
| 		if(Obj->has(Field)) | ||||
| 			S = Obj->get(Field).toString(); | ||||
| 	} | ||||
|  | ||||
| 	inline void field_from_json(Poco::JSON::Object::Ptr Obj, const char *Field, uint64_t &V) { | ||||
| 		if(Obj->has(Field)) | ||||
| 			V = Obj->get(Field); | ||||
| 	} | ||||
|  | ||||
| 	inline void field_from_json(Poco::JSON::Object::Ptr Obj, const char *Field, bool &V) { | ||||
| 		if(Obj->has(Field)) | ||||
| 			V = (Obj->get(Field).toString() == "true"); | ||||
| 	} | ||||
|  | ||||
| 	inline void field_from_json(Poco::JSON::Object::Ptr Obj, const char *Field, Types::StringVec &V) { | ||||
| 		if(Obj->isArray(Field)) { | ||||
| 			V.clear(); | ||||
| 			Poco::JSON::Array::Ptr A = Obj->getArray(Field); | ||||
| 			for(const auto &i:*A) { | ||||
| 				V.push_back(i.toString()); | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	template<class T> void field_to_json(Poco::JSON::Object &Obj, const char *Field, const std::vector<T> &Value) { | ||||
| 		Poco::JSON::Array Arr; | ||||
| 		for(const auto &i:Value) { | ||||
| 			Poco::JSON::Object	AO; | ||||
| 			i.to_json(AO); | ||||
| 			Arr.add(AO); | ||||
| 		} | ||||
| 		Obj.set(Field, Arr); | ||||
| 	} | ||||
|  | ||||
| 	template<class T> void field_from_json(const Poco::JSON::Object::Ptr &Obj, const char *Field, std::vector<T> &Value) { | ||||
| 		if(Obj->isArray(Field)) { | ||||
| 			Poco::JSON::Array::Ptr	Arr = Obj->getArray(Field); | ||||
| 			for(auto &i:*Arr) { | ||||
| 				auto InnerObj = i.extract<Poco::JSON::Object::Ptr>(); | ||||
| 				T	NewItem; | ||||
| 				NewItem.from_json(InnerObj); | ||||
| 				Value.push_back(NewItem); | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	template<class T> void field_from_json(const Poco::JSON::Object::Ptr &Obj, const char *Field, T &Value) { | ||||
| 		if(Obj->isObject(Field)) { | ||||
| 			Poco::JSON::Object::Ptr	A = Obj->getObject(Field); | ||||
| 			Value.from_json(A); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	inline std::string to_string(const Types::StringVec & ObjectArray) { | ||||
| 		Poco::JSON::Array OutputArr; | ||||
| 		if(ObjectArray.empty()) | ||||
| 			return "[]"; | ||||
| 		for(auto const &i:ObjectArray) { | ||||
| 			OutputArr.add(i); | ||||
| 		} | ||||
| 		std::ostringstream OS; | ||||
| 		Poco::JSON::Stringifier::condense(OutputArr,OS); | ||||
| 		return OS.str(); | ||||
| 	} | ||||
|  | ||||
| 	template<class T> std::string to_string(const std::vector<T> & ObjectArray) { | ||||
| 		Poco::JSON::Array OutputArr; | ||||
| 		if(ObjectArray.empty()) | ||||
| 			return "[]"; | ||||
| 		for(auto const &i:ObjectArray) { | ||||
| 			Poco::JSON::Object O; | ||||
| 			i.to_json(O); | ||||
| 			OutputArr.add(O); | ||||
| 		} | ||||
| 		std::ostringstream OS; | ||||
| 		Poco::JSON::Stringifier::condense(OutputArr,OS); | ||||
| 		return OS.str(); | ||||
| 	} | ||||
|  | ||||
| 	template<class T> std::string to_string(const T & Object) { | ||||
| 		Poco::JSON::Object OutputObj; | ||||
| 		Object.to_json(OutputObj); | ||||
| 		std::ostringstream OS; | ||||
| 		Poco::JSON::Stringifier::condense(OutputObj,OS); | ||||
| 		return OS.str(); | ||||
| 	} | ||||
|  | ||||
|     inline Types::StringVec to_object_array(const std::string & ObjectString) { | ||||
|  | ||||
|         Types::StringVec 	Result; | ||||
|         if(ObjectString.empty()) | ||||
|             return Result; | ||||
|  | ||||
|         try { | ||||
|             Poco::JSON::Parser P; | ||||
|             auto Object = P.parse(ObjectString).template extract<Poco::JSON::Array::Ptr>(); | ||||
|             for (auto const i : *Object) { | ||||
|                 Result.push_back(i.toString()); | ||||
|             } | ||||
|         } catch (...) { | ||||
|  | ||||
|         } | ||||
|         return Result; | ||||
|     } | ||||
|  | ||||
|     template<class T> std::vector<T> to_object_array(const std::string & ObjectString) { | ||||
|  | ||||
| 		std::vector<T>	Result; | ||||
| 		if(ObjectString.empty()) | ||||
| 			return Result; | ||||
|  | ||||
| 		try { | ||||
| 			Poco::JSON::Parser P; | ||||
| 			auto Object = P.parse(ObjectString).template extract<Poco::JSON::Array::Ptr>(); | ||||
| 			for (auto const i : *Object) { | ||||
| 				auto InnerObject = i.template extract<Poco::JSON::Object::Ptr>(); | ||||
| 				T Obj; | ||||
| 				Obj.from_json(InnerObject); | ||||
| 				Result.push_back(Obj); | ||||
| 			} | ||||
| 		} catch (...) { | ||||
|  | ||||
| 		} | ||||
| 		return Result; | ||||
| 	} | ||||
|  | ||||
| 	template<class T> T to_object(const std::string & ObjectString) { | ||||
| 		T	Result; | ||||
|  | ||||
| 		if(ObjectString.empty()) | ||||
| 			return Result; | ||||
|  | ||||
| 		Poco::JSON::Parser	P; | ||||
| 		auto Object = P.parse(ObjectString).template extract<Poco::JSON::Object::Ptr>(); | ||||
| 		Result.from_json(Object); | ||||
|  | ||||
| 		return Result; | ||||
| 	} | ||||
|  | ||||
|     template<class T> bool from_request(T & Obj, Poco::Net::HTTPServerRequest &Request) { | ||||
|         Poco::JSON::Parser IncomingParser; | ||||
|         auto RawObject = IncomingParser.parse(Request.stream()).extract<Poco::JSON::Object::Ptr>(); | ||||
|         Obj.from_json(RawObject); | ||||
|         return true; | ||||
|     } | ||||
| } | ||||
|  | ||||
| #endif // UCENTRALGW_RESTAPI_UTILS_H | ||||
							
								
								
									
										178
									
								
								src/RESTObjects/RESTAPI_CertObjects.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										178
									
								
								src/RESTObjects/RESTAPI_CertObjects.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,178 @@ | ||||
| // | ||||
| // Created by stephane bourque on 2021-12-07. | ||||
| // | ||||
|  | ||||
| #include "RESTAPI_CertObjects.h" | ||||
|  | ||||
| using OpenWifi::RESTAPI_utils::field_to_json; | ||||
| using OpenWifi::RESTAPI_utils::field_from_json; | ||||
|  | ||||
| namespace OpenWifi { | ||||
|     namespace  CertObjects { | ||||
|         void CertificateEntry::to_json(Poco::JSON::Object &Obj) const { | ||||
|             field_to_json(Obj,"id", id); | ||||
|             field_to_json(Obj,"entity", entity); | ||||
|             field_to_json(Obj,"creator", creator); | ||||
|             field_to_json(Obj,"type", type); | ||||
|             field_to_json(Obj,"status", status); | ||||
|             field_to_json(Obj,"certificate", certificate); | ||||
|             field_to_json(Obj,"key", key); | ||||
|             field_to_json(Obj,"devid", devid); | ||||
|             field_to_json(Obj,"cas", cas); | ||||
|             field_to_json(Obj,"manufacturer", manufacturer); | ||||
|             field_to_json(Obj,"model", model); | ||||
|             field_to_json(Obj,"redirector", redirector); | ||||
|             field_to_json(Obj,"commonName", commonName); | ||||
|             field_to_json(Obj,"certificateId", certificateId); | ||||
|             field_to_json(Obj,"batch", batch); | ||||
|             field_to_json(Obj,"created", created); | ||||
|             field_to_json(Obj,"modified", modified); | ||||
|             field_to_json(Obj,"revoked", revoked); | ||||
|             field_to_json(Obj,"revokeCount", revokeCount); | ||||
|         } | ||||
|  | ||||
|         bool CertificateEntry::from_json(const Poco::JSON::Object::Ptr &Obj) { | ||||
|             try { | ||||
|                 field_from_json(Obj,"id", id); | ||||
|                 field_from_json(Obj,"entity", entity); | ||||
|                 field_from_json(Obj,"creator", creator); | ||||
|                 field_from_json(Obj,"type", type); | ||||
|                 field_from_json(Obj,"status", status); | ||||
|                 field_from_json(Obj,"certificate", certificate); | ||||
|                 field_from_json(Obj,"key", key); | ||||
|                 field_from_json(Obj,"devid", devid); | ||||
|                 field_from_json(Obj,"cas", cas); | ||||
|                 field_from_json(Obj,"manufacturer", manufacturer); | ||||
|                 field_from_json(Obj,"model", model); | ||||
|                 field_from_json(Obj,"redirector", redirector); | ||||
|                 field_from_json(Obj,"commonName", commonName); | ||||
|                 field_from_json(Obj,"certificateId", certificateId); | ||||
|                 field_from_json(Obj,"batch", batch); | ||||
|                 field_from_json(Obj,"created", created); | ||||
|                 field_from_json(Obj,"modified", modified); | ||||
|                 field_from_json(Obj,"revoked", revoked); | ||||
|                 field_from_json(Obj,"revokeCount", revokeCount); | ||||
|                 return true; | ||||
|             } catch (...) { | ||||
|             } | ||||
|             return false; | ||||
|         } | ||||
|  | ||||
|         void EntityEntry::to_json(Poco::JSON::Object &Obj) const { | ||||
|             field_to_json(Obj,"id", id); | ||||
|             field_to_json(Obj,"creator", creator); | ||||
|             field_to_json(Obj,"name", name); | ||||
|             field_to_json(Obj,"description", description); | ||||
|             field_to_json(Obj,"defaultRedirector", defaultRedirector); | ||||
|             field_to_json(Obj,"apiKey", apiKey); | ||||
|             field_to_json(Obj,"serverEnrollmentProfile", serverEnrollmentProfile); | ||||
|             field_to_json(Obj,"clientEnrollmentProfile", clientEnrollmentProfile); | ||||
|             field_to_json(Obj,"organization", organization); | ||||
|             field_to_json(Obj,"created", created); | ||||
|             field_to_json(Obj,"modified", modified); | ||||
|             field_to_json(Obj,"suspended", suspended); | ||||
|             field_to_json(Obj,"deleted", deleted); | ||||
|             field_to_json(Obj,"notes", notes); | ||||
|         } | ||||
|  | ||||
|         bool EntityEntry::from_json(const Poco::JSON::Object::Ptr &Obj) { | ||||
|             try { | ||||
|                 field_from_json(Obj,"id", id); | ||||
|                 field_from_json(Obj,"creator", creator); | ||||
|                 field_from_json(Obj,"name", name); | ||||
|                 field_from_json(Obj,"description", description); | ||||
|                 field_from_json(Obj,"defaultRedirector", defaultRedirector); | ||||
|                 field_from_json(Obj,"apiKey", apiKey); | ||||
|                 field_from_json(Obj,"serverEnrollmentProfile", serverEnrollmentProfile); | ||||
|                 field_from_json(Obj,"clientEnrollmentProfile", clientEnrollmentProfile); | ||||
|                 field_from_json(Obj,"organization", organization); | ||||
|                 field_from_json(Obj,"created", created); | ||||
|                 field_from_json(Obj,"modified", modified); | ||||
|                 field_from_json(Obj,"suspended", suspended); | ||||
|                 field_from_json(Obj,"deleted", deleted); | ||||
|                 field_from_json(Obj,"notes", notes); | ||||
|                 return true; | ||||
|             } catch (...) { | ||||
|             } | ||||
|             return false; | ||||
|         } | ||||
|  | ||||
|         void BatchEntry::to_json(Poco::JSON::Object &Obj) const { | ||||
|             field_to_json(Obj,"id", id); | ||||
|             field_to_json(Obj,"entity", entity); | ||||
|             field_to_json(Obj,"creator", creator); | ||||
|             field_to_json(Obj,"name", name); | ||||
|             field_to_json(Obj,"description", description); | ||||
|             field_to_json(Obj,"manufacturer", manufacturer); | ||||
|             field_to_json(Obj,"model", model); | ||||
|             field_to_json(Obj,"redirector", redirector); | ||||
|             field_to_json(Obj,"commonNames", commonNames); | ||||
|             field_to_json(Obj,"jobHistory", jobHistory); | ||||
|             field_to_json(Obj,"notes", notes); | ||||
|             field_to_json(Obj,"submitted", submitted); | ||||
|             field_to_json(Obj,"started", started); | ||||
|             field_to_json(Obj,"completed", completed); | ||||
|             field_to_json(Obj,"modified", modified); | ||||
|         } | ||||
|  | ||||
|         bool BatchEntry::from_json(const Poco::JSON::Object::Ptr &Obj) { | ||||
|             try { | ||||
|                 field_from_json(Obj,"id", id); | ||||
|                 field_from_json(Obj,"entity", entity); | ||||
|                 field_from_json(Obj,"creator", creator); | ||||
|                 field_from_json(Obj,"name", name); | ||||
|                 field_from_json(Obj,"description", description); | ||||
|                 field_from_json(Obj,"manufacturer", manufacturer); | ||||
|                 field_from_json(Obj,"model", model); | ||||
|                 field_from_json(Obj,"redirector", redirector); | ||||
|                 field_from_json(Obj,"commonNames", commonNames); | ||||
|                 field_from_json(Obj,"jobHistory", jobHistory); | ||||
|                 field_from_json(Obj,"notes", notes); | ||||
|                 field_from_json(Obj,"submitted", submitted); | ||||
|                 field_from_json(Obj,"started", started); | ||||
|                 field_from_json(Obj,"completed", completed); | ||||
|                 field_from_json(Obj,"modified", modified); | ||||
|                 return true; | ||||
|             } catch (...) { | ||||
|             } | ||||
|             return false; | ||||
|         } | ||||
|  | ||||
|         void JobEntry::to_json(Poco::JSON::Object &Obj) const { | ||||
|             field_to_json(Obj,"id", id); | ||||
|             field_to_json(Obj,"entity", entity); | ||||
|             field_to_json(Obj,"creator", creator); | ||||
|             field_to_json(Obj,"batch", batch); | ||||
|             field_to_json(Obj,"commonNames", commonNames); | ||||
|             field_to_json(Obj,"completedNames", completedNames); | ||||
|             field_to_json(Obj,"errorNames", errorNames); | ||||
|             field_to_json(Obj,"status", status); | ||||
|             field_to_json(Obj,"command", command); | ||||
|             field_to_json(Obj,"parameters", parameters); | ||||
|             field_to_json(Obj,"submitted", submitted); | ||||
|             field_to_json(Obj,"started", started); | ||||
|             field_to_json(Obj,"completed", completed); | ||||
|         } | ||||
|  | ||||
|         bool JobEntry::from_json(const Poco::JSON::Object::Ptr &Obj) { | ||||
|             try { | ||||
|                 field_from_json(Obj,"id", id); | ||||
|                 field_from_json(Obj,"entity", entity); | ||||
|                 field_from_json(Obj,"creator", creator); | ||||
|                 field_from_json(Obj,"batch", batch); | ||||
|                 field_from_json(Obj,"commonNames", commonNames); | ||||
|                 field_from_json(Obj,"completedNames", completedNames); | ||||
|                 field_from_json(Obj,"errorNames", errorNames); | ||||
|                 field_from_json(Obj,"status", status); | ||||
|                 field_from_json(Obj,"command", command); | ||||
|                 field_from_json(Obj,"parameters", parameters); | ||||
|                 field_from_json(Obj,"submitted", submitted); | ||||
|                 field_from_json(Obj,"started", started); | ||||
|                 field_from_json(Obj,"completed", completed); | ||||
|                 return true; | ||||
|             } catch (...) { | ||||
|             } | ||||
|             return false; | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										101
									
								
								src/RESTObjects/RESTAPI_CertObjects.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										101
									
								
								src/RESTObjects/RESTAPI_CertObjects.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,101 @@ | ||||
| // | ||||
| // Created by stephane bourque on 2021-12-07. | ||||
| // | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include <string> | ||||
| #include "framework/MicroService.h" | ||||
| #include "framework/OpenWifiTypes.h" | ||||
| #include "RESTObjects/RESTAPI_SecurityObjects.h" | ||||
|  | ||||
| namespace OpenWifi { | ||||
|  | ||||
|     namespace CertObjects { | ||||
|  | ||||
|         struct CertificateEntry { | ||||
|             OpenWifi::Types::UUID_t         id; | ||||
|             OpenWifi::Types::UUID_t         entity; | ||||
|             OpenWifi::Types::UUID_t         creator; | ||||
|             std::string                     type; | ||||
|             std::string                     status; | ||||
|             std::string                     certificate; | ||||
|             std::string                     key; | ||||
|             std::string                     devid; | ||||
|             std::string                     cas; | ||||
|             std::string                     manufacturer; | ||||
|             std::string                     model; | ||||
|             std::string                     redirector; | ||||
|             std::string                     commonName; | ||||
|             std::string                     certificateId; | ||||
|             OpenWifi::Types::UUID_t         batch; | ||||
|             uint64_t                        created = 0; | ||||
|             uint64_t                        modified = 0; | ||||
|             uint64_t                        revoked = 0; | ||||
|             uint64_t                        revokeCount = 0; | ||||
|  | ||||
|             void to_json(Poco::JSON::Object &Obj) const; | ||||
|             bool from_json(const Poco::JSON::Object::Ptr &Obj); | ||||
|         }; | ||||
|  | ||||
|         struct EntityEntry { | ||||
|             OpenWifi::Types::UUID_t         id; | ||||
|             OpenWifi::Types::UUID_t         creator; | ||||
|             std::string                     name; | ||||
|             std::string                     description; | ||||
|             std::string                     defaultRedirector; | ||||
|             std::string                     apiKey; | ||||
|             std::string                     serverEnrollmentProfile; | ||||
|             std::string                     clientEnrollmentProfile; | ||||
|             std::string                     organization; | ||||
|             SecurityObjects::NoteInfoVec    notes; | ||||
|             bool                            suspended=false; | ||||
|             bool                            deleted=false; | ||||
|             uint64_t                        created = 0 ; | ||||
|             uint64_t                        modified = 0 ; | ||||
|  | ||||
|             void to_json(Poco::JSON::Object &Obj) const; | ||||
|             bool from_json(const Poco::JSON::Object::Ptr &Obj); | ||||
|         }; | ||||
|  | ||||
|         struct BatchEntry { | ||||
|             OpenWifi::Types::UUID_t         id; | ||||
|             OpenWifi::Types::UUID_t         entity; | ||||
|             OpenWifi::Types::UUID_t         creator; | ||||
|             std::string                     name; | ||||
|             std::string                     description; | ||||
|             std::string                     manufacturer; | ||||
|             std::string                     model; | ||||
|             std::string                     redirector; | ||||
|             std::vector<std::string>        commonNames; | ||||
|             std::vector<std::string>        jobHistory; | ||||
|             SecurityObjects::NoteInfoVec    notes; | ||||
|             uint64_t                        submitted = 0 ; | ||||
|             uint64_t                        started = 0 ; | ||||
|             uint64_t                        completed = 0 ; | ||||
|             uint64_t                        modified = 0 ; | ||||
|  | ||||
|             void to_json(Poco::JSON::Object &Obj) const; | ||||
|             bool from_json(const Poco::JSON::Object::Ptr &Obj); | ||||
|         }; | ||||
|  | ||||
|         struct JobEntry { | ||||
|             OpenWifi::Types::UUID_t         id; | ||||
|             OpenWifi::Types::UUID_t         entity; | ||||
|             OpenWifi::Types::UUID_t         creator; | ||||
|             OpenWifi::Types::UUID_t         batch; | ||||
|             std::string                     command; | ||||
|             OpenWifi::Types::StringVec      commonNames; | ||||
|             OpenWifi::Types::StringVec      completedNames; | ||||
|             OpenWifi::Types::StringVec      errorNames; | ||||
|             Types::StringPairVec            parameters; | ||||
|             std::string                     status; | ||||
|             uint64_t                        submitted=0; | ||||
|             uint64_t                        started=0; | ||||
|             uint64_t                        completed=0; | ||||
|  | ||||
|             void to_json(Poco::JSON::Object &Obj) const; | ||||
|             bool from_json(const Poco::JSON::Object::Ptr &Obj); | ||||
|         }; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										248
									
								
								src/RESTObjects/RESTAPI_FMSObjects.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										248
									
								
								src/RESTObjects/RESTAPI_FMSObjects.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,248 @@ | ||||
| // | ||||
| // Created by stephane bourque on 2021-07-12. | ||||
| // | ||||
|  | ||||
| #include "RESTAPI_FMSObjects.h" | ||||
| #include "framework/MicroService.h" | ||||
|  | ||||
| using OpenWifi::RESTAPI_utils::field_to_json; | ||||
| using OpenWifi::RESTAPI_utils::field_from_json; | ||||
|  | ||||
| namespace OpenWifi::FMSObjects { | ||||
|  | ||||
|     void Firmware::to_json(Poco::JSON::Object &Obj) const { | ||||
|         field_to_json(Obj, "id", id); | ||||
|         field_to_json(Obj, "release", release); | ||||
|         field_to_json(Obj, "deviceType", deviceType); | ||||
|         field_to_json(Obj, "description", description); | ||||
|         field_to_json(Obj, "revision", revision); | ||||
|         field_to_json(Obj, "uri", uri); | ||||
|         field_to_json(Obj, "image", image); | ||||
|         field_to_json(Obj, "imageDate", imageDate); | ||||
|         field_to_json(Obj, "size", size); | ||||
|         field_to_json(Obj, "downloadCount", downloadCount); | ||||
|         field_to_json(Obj, "firmwareHash", firmwareHash); | ||||
|         field_to_json(Obj, "owner", owner); | ||||
|         field_to_json(Obj, "location", location); | ||||
|         field_to_json(Obj, "uploader", uploader); | ||||
|         field_to_json(Obj, "digest", digest); | ||||
|         field_to_json(Obj, "latest", latest); | ||||
|         field_to_json(Obj, "notes", notes); | ||||
|         field_to_json(Obj, "created", created); | ||||
|     }; | ||||
|  | ||||
|     bool Firmware::from_json(const Poco::JSON::Object::Ptr &Obj) { | ||||
|         try { | ||||
|             field_from_json(Obj, "id", id); | ||||
|             field_from_json(Obj, "release", release); | ||||
|             field_from_json(Obj, "deviceType", deviceType); | ||||
|             field_from_json(Obj, "description", description); | ||||
|             field_from_json(Obj, "revision", revision); | ||||
|             field_from_json(Obj, "uri", uri); | ||||
|             field_from_json(Obj, "image", image); | ||||
|             field_from_json(Obj, "imageDate", imageDate); | ||||
|             field_from_json(Obj, "size", size); | ||||
|             field_from_json(Obj, "downloadCount", downloadCount); | ||||
|             field_from_json(Obj, "firmwareHash", firmwareHash); | ||||
|             field_from_json(Obj, "owner", owner); | ||||
|             field_from_json(Obj, "location", location); | ||||
|             field_from_json(Obj, "uploader", uploader); | ||||
|             field_from_json(Obj, "digest", digest); | ||||
|             field_from_json(Obj, "latest", latest); | ||||
|             field_from_json(Obj, "notes", notes); | ||||
|             field_from_json(Obj, "created", created); | ||||
|             return true; | ||||
|         } catch (...) { | ||||
|  | ||||
|         } | ||||
|         return true; | ||||
|     } | ||||
|  | ||||
|     void FirmwareList::to_json(Poco::JSON::Object &Obj) const { | ||||
|         field_to_json(Obj,"firmwares",firmwares); | ||||
|     } | ||||
|  | ||||
|     bool FirmwareList::from_json(const Poco::JSON::Object::Ptr &Obj) { | ||||
|         try { | ||||
|             field_from_json(Obj, "firmwares", firmwares); | ||||
|             return true; | ||||
|         } catch (...) { | ||||
|  | ||||
|         } | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     void DeviceType::to_json(Poco::JSON::Object &Obj) const { | ||||
|         field_to_json(Obj, "id", id); | ||||
|         field_to_json(Obj, "deviceType", deviceType); | ||||
|         field_to_json(Obj, "manufacturer", manufacturer); | ||||
|         field_to_json(Obj, "model", model); | ||||
|         field_to_json(Obj, "policy", policy); | ||||
|         field_to_json(Obj, "notes", notes); | ||||
|         field_to_json(Obj, "lastUpdate", lastUpdate); | ||||
|         field_to_json(Obj, "created", created); | ||||
|         field_to_json(Obj, "id", id); | ||||
|         field_to_json(Obj, "id", id); | ||||
|         field_to_json(Obj, "id", id); | ||||
|     } | ||||
|  | ||||
|     bool DeviceType::from_json(const Poco::JSON::Object::Ptr &Obj) { | ||||
|         try { | ||||
|             field_from_json(Obj, "id", id); | ||||
|             field_from_json(Obj, "deviceType", deviceType); | ||||
|             field_from_json(Obj, "manufacturer", manufacturer); | ||||
|             field_from_json(Obj, "model", model); | ||||
|             field_from_json(Obj, "policy", policy); | ||||
|             field_from_json(Obj, "notes", notes); | ||||
|             field_from_json(Obj, "lastUpdate", lastUpdate); | ||||
|             field_from_json(Obj, "created", created); | ||||
|             field_from_json(Obj, "id", id); | ||||
|             field_from_json(Obj, "id", id); | ||||
|             field_from_json(Obj, "id", id); | ||||
|             return true; | ||||
|         } catch (...) { | ||||
|  | ||||
|         } | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     void DeviceTypeList::to_json(Poco::JSON::Object &Obj) const { | ||||
|         field_to_json(Obj,"deviceTypes", deviceTypes); | ||||
|     } | ||||
|  | ||||
|     bool DeviceTypeList::from_json(const Poco::JSON::Object::Ptr &Obj) { | ||||
|         try { | ||||
|             field_from_json(Obj,"deviceTypes", deviceTypes); | ||||
|             return true; | ||||
|         } catch(...) { | ||||
|  | ||||
|         } | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     void RevisionHistoryEntry::to_json(Poco::JSON::Object &Obj) const { | ||||
|         field_to_json(Obj, "id", id); | ||||
|         field_to_json(Obj, "serialNumber", serialNumber); | ||||
|         field_to_json(Obj, "fromRelease", fromRelease); | ||||
|         field_to_json(Obj, "toRelease", toRelease); | ||||
|         field_to_json(Obj, "commandUUID", commandUUID); | ||||
|         field_to_json(Obj, "revisionId", revisionId); | ||||
|         field_to_json(Obj, "upgraded", upgraded); | ||||
|     } | ||||
|  | ||||
|     bool RevisionHistoryEntry::from_json(const Poco::JSON::Object::Ptr &Obj) { | ||||
|         try { | ||||
|             field_from_json(Obj, "id", id); | ||||
|             field_from_json(Obj, "serialNumber", serialNumber); | ||||
|             field_from_json(Obj, "fromRelease", fromRelease); | ||||
|             field_from_json(Obj, "toRelease", toRelease); | ||||
|             field_from_json(Obj, "commandUUID", commandUUID); | ||||
|             field_from_json(Obj, "revisionId", revisionId); | ||||
|             field_from_json(Obj, "upgraded", upgraded); | ||||
|             return true; | ||||
|         } catch(...) { | ||||
|  | ||||
|         } | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     void RevisionHistoryEntryList::to_json(Poco::JSON::Object &Obj) const { | ||||
|         field_to_json(Obj,"deviceTypes", history); | ||||
|     } | ||||
|  | ||||
|     bool RevisionHistoryEntryList::from_json(const Poco::JSON::Object::Ptr &Obj) { | ||||
|         try { | ||||
|             field_from_json(Obj,"deviceTypes", history); | ||||
|             return true; | ||||
|         } catch(...) { | ||||
|  | ||||
|         } | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     void FirmwareAgeDetails::to_json(Poco::JSON::Object &Obj) const { | ||||
|         field_to_json(Obj,"latestId", latestId); | ||||
|         field_to_json(Obj,"image", image); | ||||
|         field_to_json(Obj,"imageDate", imageDate); | ||||
|         field_to_json(Obj,"revision", revision); | ||||
|         field_to_json(Obj,"uri", uri); | ||||
|         field_to_json(Obj,"age", age); | ||||
|         field_to_json(Obj,"latest",latest); | ||||
|     } | ||||
|  | ||||
|     bool FirmwareAgeDetails::from_json(const Poco::JSON::Object::Ptr &Obj) { | ||||
|         try { | ||||
|             field_from_json(Obj,"latestId", latestId); | ||||
|             field_from_json(Obj,"image", image); | ||||
|             field_from_json(Obj,"imageDate", imageDate); | ||||
|             field_from_json(Obj,"revision", revision); | ||||
|             field_from_json(Obj,"uri", uri); | ||||
|             field_from_json(Obj,"age", age); | ||||
|             field_from_json(Obj,"latest", latest); | ||||
|             return true; | ||||
|         } catch(...) { | ||||
|  | ||||
|         } | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     void DeviceConnectionInformation::to_json(Poco::JSON::Object &Obj) const { | ||||
|         field_to_json(Obj, "serialNumber", serialNumber); | ||||
|         field_to_json(Obj, "revision", revision); | ||||
|         field_to_json(Obj, "deviceType", deviceType); | ||||
|         field_to_json(Obj, "endPoint", endPoint); | ||||
|         field_to_json(Obj, "lastUpdate", lastUpdate); | ||||
|         field_to_json(Obj, "status", status); | ||||
|     } | ||||
|  | ||||
|     bool DeviceConnectionInformation::from_json(const Poco::JSON::Object::Ptr &Obj) { | ||||
|         try { | ||||
|             field_from_json(Obj, "serialNumber", serialNumber); | ||||
|             field_from_json(Obj, "revision", revision); | ||||
|             field_from_json(Obj, "deviceType", deviceType); | ||||
|             field_from_json(Obj, "endPoint", endPoint); | ||||
|             field_from_json(Obj, "lastUpdate", lastUpdate); | ||||
|             field_from_json(Obj, "status", status); | ||||
|             return true; | ||||
|         } catch(...) { | ||||
|  | ||||
|         } | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     void DeviceReport::to_json(Poco::JSON::Object &Obj) const { | ||||
|         field_to_json(Obj, "ouis",OUI_); | ||||
|         field_to_json(Obj, "revisions", Revisions_); | ||||
|         field_to_json(Obj, "deviceTypes", DeviceTypes_); | ||||
|         field_to_json(Obj, "status", Status_); | ||||
|         field_to_json(Obj, "endPoints", EndPoints_); | ||||
|         field_to_json(Obj, "usingLatest", UsingLatest_); | ||||
|         field_to_json(Obj, "unknownFirmwares", UnknownFirmwares_); | ||||
|         field_to_json(Obj,"snapshot",snapshot); | ||||
|         field_to_json(Obj,"numberOfDevices",numberOfDevices); | ||||
|         field_to_json(Obj, "totalSecondsOld", totalSecondsOld_); | ||||
|     } | ||||
|  | ||||
|     void DeviceReport::reset() { | ||||
|         OUI_.clear(); | ||||
|         Revisions_.clear(); | ||||
|         DeviceTypes_.clear(); | ||||
|         Status_.clear(); | ||||
|         EndPoints_.clear(); | ||||
|         UsingLatest_.clear(); | ||||
|         UnknownFirmwares_.clear(); | ||||
|         totalSecondsOld_.clear(); | ||||
|         numberOfDevices = 0 ; | ||||
|         snapshot = std::time(nullptr); | ||||
|     } | ||||
|  | ||||
|     bool DeviceReport::from_json(const Poco::JSON::Object::Ptr &Obj) { | ||||
|         try { | ||||
|  | ||||
|             return true; | ||||
|         } catch (...) { | ||||
|  | ||||
|         } | ||||
|         return false; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										133
									
								
								src/RESTObjects/RESTAPI_FMSObjects.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										133
									
								
								src/RESTObjects/RESTAPI_FMSObjects.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,133 @@ | ||||
| // | ||||
| // Created by stephane bourque on 2021-07-12. | ||||
| // | ||||
|  | ||||
| #include <string> | ||||
|  | ||||
| #ifndef UCENTRALFMS_RESTAPI_FMSOBJECTS_H | ||||
| #define UCENTRALFMS_RESTAPI_FMSOBJECTS_H | ||||
|  | ||||
|  | ||||
| #include "RESTAPI_SecurityObjects.h" | ||||
| #include "framework/OpenWifiTypes.h" | ||||
|  | ||||
| namespace OpenWifi::FMSObjects { | ||||
|  | ||||
|     struct Firmware { | ||||
|         std::string     id; | ||||
|         std::string     release; | ||||
|         std::string     deviceType; | ||||
|         std::string     description; | ||||
|         std::string     revision; | ||||
|         std::string     uri; | ||||
|         std::string     image; | ||||
|         uint64_t        imageDate=0; | ||||
|         uint64_t        size=0; | ||||
|         uint64_t        downloadCount=0; | ||||
|         std::string     firmwareHash; | ||||
|         std::string     owner; | ||||
|         std::string     location; | ||||
|         std::string     uploader; | ||||
|         std::string     digest; | ||||
|         bool            latest=0; | ||||
|         SecurityObjects::NoteInfoVec    notes; | ||||
|         uint64_t        created=0; | ||||
|  | ||||
|         void to_json(Poco::JSON::Object &Obj) const; | ||||
|         bool from_json(const Poco::JSON::Object::Ptr &Obj); | ||||
|     }; | ||||
|     typedef std::vector<Firmware>    FirmwareVec; | ||||
|  | ||||
|     struct FirmwareList { | ||||
|         FirmwareVec  firmwares; | ||||
|  | ||||
|         void to_json(Poco::JSON::Object &Obj) const; | ||||
|         bool from_json(const Poco::JSON::Object::Ptr &Obj); | ||||
|     }; | ||||
|  | ||||
|     struct DeviceType { | ||||
|         std::string id; | ||||
|         std::string deviceType; | ||||
|         std::string manufacturer; | ||||
|         std::string model; | ||||
|         std::string policy; | ||||
|         SecurityObjects::NoteInfoVec notes; | ||||
|         uint64_t lastUpdate=0; | ||||
|         uint64_t created=0; | ||||
|  | ||||
|         void to_json(Poco::JSON::Object &Obj) const; | ||||
|         bool from_json(const Poco::JSON::Object::Ptr &Obj); | ||||
|     }; | ||||
|     typedef std::vector<DeviceType> DeviceTypeVec; | ||||
|  | ||||
|     struct DeviceTypeList { | ||||
|         DeviceTypeVec   deviceTypes; | ||||
|  | ||||
|         void to_json(Poco::JSON::Object &Obj) const; | ||||
|         bool from_json(const Poco::JSON::Object::Ptr &Obj); | ||||
|     }; | ||||
|  | ||||
|     struct RevisionHistoryEntry { | ||||
|         std::string id; | ||||
|         std::string serialNumber; | ||||
|         std::string fromRelease; | ||||
|         std::string toRelease; | ||||
|         std::string commandUUID; | ||||
|         std::string revisionId; | ||||
|         uint64_t    upgraded; | ||||
|  | ||||
|         void to_json(Poco::JSON::Object &Obj) const; | ||||
|         bool from_json(const Poco::JSON::Object::Ptr &Obj); | ||||
|     }; | ||||
|     typedef std::vector<RevisionHistoryEntry>   RevisionHistoryEntryVec; | ||||
|  | ||||
|     struct RevisionHistoryEntryList { | ||||
|         RevisionHistoryEntryVec history; | ||||
|  | ||||
|         void to_json(Poco::JSON::Object &Obj) const; | ||||
|         bool from_json(const Poco::JSON::Object::Ptr &Obj); | ||||
|     }; | ||||
|  | ||||
|     struct FirmwareAgeDetails { | ||||
|         std::string latestId; | ||||
|         std::string image; | ||||
|         uint64_t imageDate; | ||||
|         std::string revision; | ||||
|         std::string uri; | ||||
|         uint64_t age=0; | ||||
|         bool latest=true; | ||||
|  | ||||
|         void to_json(Poco::JSON::Object &Obj) const; | ||||
|         bool from_json(const Poco::JSON::Object::Ptr &Obj); | ||||
|     }; | ||||
|  | ||||
|     struct DeviceConnectionInformation { | ||||
|         std::string serialNumber; | ||||
|         std::string revision; | ||||
|         std::string deviceType; | ||||
|         std::string endPoint; | ||||
|         uint64_t    lastUpdate; | ||||
|         std::string status; | ||||
|         void to_json(Poco::JSON::Object &Obj) const; | ||||
|         bool from_json(const Poco::JSON::Object::Ptr &Obj); | ||||
|     }; | ||||
|  | ||||
|     struct DeviceReport { | ||||
|         uint64_t               snapshot=0; | ||||
|         uint64_t               numberOfDevices=0; | ||||
|         Types::CountedMap      OUI_; | ||||
|         Types::CountedMap      Revisions_; | ||||
|         Types::CountedMap      DeviceTypes_; | ||||
|         Types::CountedMap      Status_; | ||||
|         Types::CountedMap      EndPoints_; | ||||
|         Types::CountedMap      UsingLatest_; | ||||
|         Types::CountedMap      UnknownFirmwares_; | ||||
|         Types::CountedMap      totalSecondsOld_; | ||||
|         void to_json(Poco::JSON::Object &Obj) const; | ||||
|         void reset(); | ||||
|         bool from_json(const Poco::JSON::Object::Ptr &Obj); | ||||
|     }; | ||||
| } | ||||
|  | ||||
|  | ||||
| #endif //UCENTRALFMS_RESTAPI_FMSOBJECTS_H | ||||
							
								
								
									
										268
									
								
								src/RESTObjects/RESTAPI_GWobjects.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										268
									
								
								src/RESTObjects/RESTAPI_GWobjects.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,268 @@ | ||||
| // | ||||
| //	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 "Poco/JSON/Stringifier.h" | ||||
|  | ||||
| #include "Daemon.h" | ||||
| #ifdef	TIP_GATEWAY_SERVICE | ||||
| #include "DeviceRegistry.h" | ||||
| #include "CapabilitiesCache.h" | ||||
| #endif | ||||
|  | ||||
| #include "RESTAPI_GWobjects.h" | ||||
| #include "framework/MicroService.h" | ||||
|  | ||||
| using OpenWifi::RESTAPI_utils::field_to_json; | ||||
| using OpenWifi::RESTAPI_utils::field_from_json; | ||||
| using OpenWifi::RESTAPI_utils::EmbedDocument; | ||||
|  | ||||
| namespace OpenWifi::GWObjects { | ||||
|  | ||||
| 	void Device::to_json(Poco::JSON::Object &Obj) const { | ||||
| 		field_to_json(Obj,"serialNumber", SerialNumber); | ||||
| #ifdef TIP_GATEWAY_SERVICE | ||||
| 		field_to_json(Obj,"deviceType", CapabilitiesCache::instance()->Get(Compatible)); | ||||
| #endif | ||||
| 		field_to_json(Obj,"macAddress", MACAddress); | ||||
| 		field_to_json(Obj,"manufacturer", Manufacturer); | ||||
| 		field_to_json(Obj,"UUID", UUID); | ||||
| 		EmbedDocument("configuration", Obj, Configuration); | ||||
| 		field_to_json(Obj,"notes", Notes); | ||||
| 		field_to_json(Obj,"createdTimestamp", CreationTimestamp); | ||||
| 		field_to_json(Obj,"lastConfigurationChange", LastConfigurationChange); | ||||
| 		field_to_json(Obj,"lastConfigurationDownload", LastConfigurationDownload); | ||||
| 		field_to_json(Obj,"lastFWUpdate", LastFWUpdate); | ||||
| 		field_to_json(Obj,"owner", Owner); | ||||
| 		field_to_json(Obj,"location", Location); | ||||
| 		field_to_json(Obj,"venue", Venue); | ||||
| 		field_to_json(Obj,"firmware", Firmware); | ||||
| 		field_to_json(Obj,"compatible", Compatible); | ||||
| 		field_to_json(Obj,"fwUpdatePolicy", FWUpdatePolicy); | ||||
| 		field_to_json(Obj,"devicePassword", DevicePassword); | ||||
| 	} | ||||
|  | ||||
| 	void Device::to_json_with_status(Poco::JSON::Object &Obj) const { | ||||
| 		to_json(Obj); | ||||
|  | ||||
| #ifdef TIP_GATEWAY_SERVICE | ||||
| 		ConnectionState ConState; | ||||
|  | ||||
| 		if (DeviceRegistry()->GetState(SerialNumber, ConState)) { | ||||
| 			ConState.to_json(Obj); | ||||
| 		} else { | ||||
| 			field_to_json(Obj,"ipAddress", ""); | ||||
| 			field_to_json(Obj,"txBytes", (uint64_t) 0); | ||||
| 			field_to_json(Obj,"rxBytes", (uint64_t )0); | ||||
| 			field_to_json(Obj,"messageCount", (uint64_t )0); | ||||
| 			field_to_json(Obj,"connected", false); | ||||
| 			field_to_json(Obj,"lastContact", ""); | ||||
| 			field_to_json(Obj,"verifiedCertificate", "NO_CERTIFICATE"); | ||||
| 			field_to_json(Obj,"associations_2G", (uint64_t) 0); | ||||
| 			field_to_json(Obj,"associations_5G", (uint64_t) 0); | ||||
| 		} | ||||
| #endif | ||||
| 	} | ||||
|  | ||||
| 	bool Device::from_json(Poco::JSON::Object::Ptr &Obj) { | ||||
| 		try { | ||||
| 			field_from_json(Obj,"serialNumber",SerialNumber); | ||||
| 			field_from_json(Obj,"deviceType",DeviceType); | ||||
| 			field_from_json(Obj,"macAddress",MACAddress); | ||||
| 			field_from_json(Obj,"configuration",Configuration); | ||||
| 			field_from_json(Obj,"notes",Notes); | ||||
| 			field_from_json(Obj,"manufacturer",Manufacturer); | ||||
| 			field_from_json(Obj,"owner",Owner); | ||||
| 			field_from_json(Obj,"location",Location); | ||||
| 			field_from_json(Obj,"venue",Venue); | ||||
| 			field_from_json(Obj,"compatible",Compatible); | ||||
| 			return true; | ||||
| 		} catch (const Poco::Exception &E) { | ||||
| 		} | ||||
| 		return false; | ||||
| 	} | ||||
|  | ||||
| 	void Device::Print() const { | ||||
| 		std::cout << "Device: " << SerialNumber << " DeviceType:" << DeviceType << " MACAddress:" << MACAddress << " Manufacturer:" | ||||
| 				  << Manufacturer << " " << Configuration << std::endl; | ||||
| 	} | ||||
|  | ||||
| 	void Statistics::to_json(Poco::JSON::Object &Obj) const { | ||||
| 		EmbedDocument("data", Obj, Data); | ||||
| 		field_to_json(Obj,"UUID", UUID); | ||||
| 		field_to_json(Obj,"recorded", Recorded); | ||||
| 	} | ||||
|  | ||||
| 	void Capabilities::to_json(Poco::JSON::Object &Obj) const { | ||||
| 		EmbedDocument("capabilities", Obj, Capabilities); | ||||
| 		field_to_json(Obj,"firstUpdate", FirstUpdate); | ||||
| 		field_to_json(Obj,"lastUpdate", LastUpdate); | ||||
| 	} | ||||
|  | ||||
| 	void DeviceLog::to_json(Poco::JSON::Object &Obj) const { | ||||
| 		EmbedDocument("data", Obj, Data); | ||||
| 		field_to_json(Obj,"log", Log); | ||||
| 		field_to_json(Obj,"severity", Severity); | ||||
| 		field_to_json(Obj,"recorded", Recorded); | ||||
| 		field_to_json(Obj,"logType", LogType); | ||||
| 		field_to_json(Obj,"UUID", UUID); | ||||
| 	} | ||||
|  | ||||
| 	void HealthCheck::to_json(Poco::JSON::Object &Obj) const { | ||||
| 		EmbedDocument("values", Obj, Data); | ||||
| 		field_to_json(Obj,"UUID", UUID); | ||||
| 		field_to_json(Obj,"sanity", Sanity); | ||||
| 		field_to_json(Obj,"recorded", Recorded); | ||||
| 	} | ||||
|  | ||||
| 	void DefaultConfiguration::to_json(Poco::JSON::Object &Obj) const { | ||||
| 		EmbedDocument("configuration", Obj, Configuration); | ||||
| 		field_to_json(Obj,"name", Name); | ||||
| 		field_to_json(Obj,"modelIds", Models); | ||||
| 		field_to_json(Obj,"description", Description); | ||||
| 		field_to_json(Obj,"created", Created); | ||||
| 		field_to_json(Obj,"lastModified", LastModified); | ||||
| 	} | ||||
|  | ||||
| 	void CommandDetails::to_json(Poco::JSON::Object &Obj) const { | ||||
| 		EmbedDocument("details", Obj, Details); | ||||
| 		EmbedDocument("results", Obj, Results); | ||||
| 		field_to_json(Obj,"UUID", UUID); | ||||
| 		field_to_json(Obj,"serialNumber", SerialNumber); | ||||
| 		field_to_json(Obj,"command", Command); | ||||
| 		field_to_json(Obj,"errorText", ErrorText); | ||||
| 		field_to_json(Obj,"submittedBy", SubmittedBy); | ||||
| 		field_to_json(Obj,"status", Status); | ||||
| 		field_to_json(Obj,"submitted", Submitted); | ||||
| 		field_to_json(Obj,"executed", Executed); | ||||
| 		field_to_json(Obj,"completed", Completed); | ||||
| 		field_to_json(Obj,"when", RunAt); | ||||
| 		field_to_json(Obj,"errorCode", ErrorCode); | ||||
| 		field_to_json(Obj,"custom", Custom); | ||||
| 		field_to_json(Obj,"waitingForFile", WaitingForFile); | ||||
| 		field_to_json(Obj,"attachFile", AttachDate); | ||||
| 		field_to_json(Obj,"executionTime", executionTime); | ||||
| 	} | ||||
|  | ||||
| 	bool DefaultConfiguration::from_json(Poco::JSON::Object::Ptr &Obj) { | ||||
| 		try { | ||||
| 			field_from_json(Obj,"name",Name); | ||||
| 			field_from_json(Obj,"configuration",Configuration); | ||||
| 			field_from_json(Obj,"modelIds",Models); | ||||
| 			field_from_json(Obj,"description",Description); | ||||
| 			return true; | ||||
| 		} catch (const Poco::Exception &E) { | ||||
| 		} | ||||
| 		return false; | ||||
| 	} | ||||
|  | ||||
| 	void BlackListedDevice::to_json(Poco::JSON::Object &Obj) const { | ||||
| 		field_to_json(Obj,"serialNumber", serialNumber); | ||||
| 		field_to_json(Obj,"author", author); | ||||
| 		field_to_json(Obj,"reason", reason); | ||||
| 		field_to_json(Obj,"created", created); | ||||
| 	} | ||||
|  | ||||
| 	bool BlackListedDevice::from_json(Poco::JSON::Object::Ptr &Obj) { | ||||
| 		try { | ||||
| 			field_from_json(Obj,"serialNumber",serialNumber); | ||||
| 			field_from_json(Obj,"author",author); | ||||
| 			field_from_json(Obj,"reason",reason); | ||||
| 			field_from_json(Obj,"created",created); | ||||
| 			return true; | ||||
| 		} catch (const Poco::Exception &E) { | ||||
| 		} | ||||
| 		return false; | ||||
| 	} | ||||
|  | ||||
| 	void ConnectionState::to_json(Poco::JSON::Object &Obj) const { | ||||
| 		field_to_json(Obj,"ipAddress", Address); | ||||
| 		field_to_json(Obj,"txBytes", TX); | ||||
| 		field_to_json(Obj,"rxBytes", RX); | ||||
| 		field_to_json(Obj,"messageCount", MessageCount); | ||||
| 		field_to_json(Obj,"UUID", UUID); | ||||
| 		field_to_json(Obj,"connected", Connected); | ||||
| 		field_to_json(Obj,"firmware", Firmware); | ||||
| 		field_to_json(Obj,"lastContact", LastContact); | ||||
| 		field_to_json(Obj,"associations_2G", Associations_2G); | ||||
| 		field_to_json(Obj,"associations_5G", Associations_5G); | ||||
| 		field_to_json(Obj,"webSocketClients", webSocketClients); | ||||
| 		field_to_json(Obj,"websocketPackets", websocketPackets); | ||||
| 		field_to_json(Obj,"kafkaClients", kafkaClients); | ||||
| 		field_to_json(Obj,"kafkaPackets", kafkaPackets); | ||||
|  | ||||
| 		switch(VerifiedCertificate) { | ||||
| 			case NO_CERTIFICATE: | ||||
| 				field_to_json(Obj,"verifiedCertificate", "NO_CERTIFICATE"); break; | ||||
| 			case VALID_CERTIFICATE: | ||||
| 				field_to_json(Obj,"verifiedCertificate", "VALID_CERTIFICATE"); break; | ||||
| 			case MISMATCH_SERIAL: | ||||
| 				field_to_json(Obj,"verifiedCertificate", "MISMATCH_SERIAL"); break; | ||||
| 			case VERIFIED: | ||||
| 				field_to_json(Obj,"verifiedCertificate", "VERIFIED"); break; | ||||
| 			default: | ||||
| 				field_to_json(Obj,"verifiedCertificate", "NO_CERTIFICATE"); break; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	void RttySessionDetails::to_json(Poco::JSON::Object &Obj) const { | ||||
| 		field_to_json(Obj,"serialNumber", SerialNumber); | ||||
| 		field_to_json(Obj,"server", Server); | ||||
| 		field_to_json(Obj,"port", Port); | ||||
| 		field_to_json(Obj,"token",Token); | ||||
| 		field_to_json(Obj,"timeout", TimeOut); | ||||
| 		field_to_json(Obj,"connectionId",ConnectionId); | ||||
| 		field_to_json(Obj,"commandUUID",CommandUUID); | ||||
| 		field_to_json(Obj,"started", Started); | ||||
| 		field_to_json(Obj,"viewport",ViewPort); | ||||
| 		field_to_json(Obj,"password",DevicePassword); | ||||
| 	} | ||||
|  | ||||
| 	void Dashboard::to_json(Poco::JSON::Object &Obj) const { | ||||
| 		field_to_json(Obj,"commands",commands); | ||||
| 		field_to_json(Obj,"upTimes",upTimes); | ||||
| 		field_to_json(Obj,"memoryUsed",memoryUsed); | ||||
| 		field_to_json(Obj,"load1",load1); | ||||
| 		field_to_json(Obj,"load5",load5); | ||||
| 		field_to_json(Obj,"load15",load15); | ||||
| 		field_to_json(Obj,"vendors",vendors); | ||||
| 		field_to_json(Obj,"status",status); | ||||
| 		field_to_json(Obj,"deviceType",deviceType); | ||||
| 		field_to_json(Obj,"healths",healths); | ||||
| 		field_to_json(Obj,"certificates",certificates); | ||||
| 		field_to_json(Obj,"lastContact",lastContact); | ||||
| 		field_to_json(Obj,"associations",associations); | ||||
| 		field_to_json(Obj,"snapshot",snapshot); | ||||
| 		field_to_json(Obj,"numberOfDevices",numberOfDevices); | ||||
| 	} | ||||
|  | ||||
| 	void Dashboard::reset()  { | ||||
| 		commands.clear(); | ||||
| 		upTimes.clear(); | ||||
| 		memoryUsed.clear(); | ||||
| 		load1.clear(); | ||||
| 		load5.clear(); | ||||
| 		load15.clear(); | ||||
| 		vendors.clear(); | ||||
| 		status.clear(); | ||||
| 		deviceType.clear(); | ||||
| 		healths.clear(); | ||||
| 		certificates.clear(); | ||||
| 		lastContact.clear(); | ||||
| 		associations.clear(); | ||||
| 		numberOfDevices = 0 ; | ||||
| 		snapshot = std::time(nullptr); | ||||
| 	} | ||||
|  | ||||
| 	void CapabilitiesModel::to_json(Poco::JSON::Object &Obj) const{ | ||||
| 		field_to_json(Obj,"deviceType", deviceType); | ||||
| 		field_to_json(Obj,"capabilities", capabilities); | ||||
| 	}; | ||||
|  | ||||
| } | ||||
|  | ||||
							
								
								
									
										196
									
								
								src/RESTObjects/RESTAPI_GWobjects.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										196
									
								
								src/RESTObjects/RESTAPI_GWobjects.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,196 @@ | ||||
| // | ||||
| //	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. | ||||
| // | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include "Poco/JSON/Object.h" | ||||
| #include "RESTAPI_SecurityObjects.h" | ||||
|  | ||||
| namespace OpenWifi::GWObjects { | ||||
|  | ||||
| 	enum CertificateValidation { | ||||
| 		NO_CERTIFICATE, | ||||
| 		VALID_CERTIFICATE, | ||||
| 		MISMATCH_SERIAL, | ||||
| 		VERIFIED | ||||
| 	}; | ||||
|  | ||||
| 	struct ConnectionState { | ||||
| 		uint64_t MessageCount = 0 ; | ||||
| 		std::string Address; | ||||
| 		uint64_t UUID = 0 ; | ||||
| 		uint64_t PendingUUID = 0 ; | ||||
| 		uint64_t TX = 0, RX = 0; | ||||
| 		uint64_t Associations_2G=0; | ||||
| 		uint64_t Associations_5G=0; | ||||
| 		bool Connected = false; | ||||
| 		uint64_t LastContact=0; | ||||
| 		std::string Firmware; | ||||
| 		CertificateValidation VerifiedCertificate = NO_CERTIFICATE; | ||||
| 		std::string Compatible; | ||||
| 		uint64_t 	kafkaClients=0; | ||||
| 		uint64_t 	webSocketClients=0; | ||||
| 		uint64_t 	kafkaPackets=0; | ||||
| 		uint64_t 	websocketPackets=0; | ||||
| 		void to_json(Poco::JSON::Object &Obj) const; | ||||
| 	}; | ||||
|  | ||||
| 	struct Device { | ||||
| 		std::string SerialNumber; | ||||
| 		std::string DeviceType; | ||||
| 		std::string MACAddress; | ||||
| 		std::string Manufacturer; | ||||
| 		std::string Configuration; | ||||
| 		SecurityObjects::NoteInfoVec 	Notes; | ||||
| 		std::string Owner; | ||||
| 		std::string Location; | ||||
| 		std::string Firmware; | ||||
| 		std::string Compatible; | ||||
| 		std::string FWUpdatePolicy; | ||||
| 		uint64_t UUID = 0 ; | ||||
| 		uint64_t CreationTimestamp = 0 ; | ||||
| 		uint64_t LastConfigurationChange = 0 ; | ||||
| 		uint64_t LastConfigurationDownload = 0 ; | ||||
| 		uint64_t LastFWUpdate = 0 ; | ||||
| 		std::string Venue; | ||||
| 		std::string DevicePassword; | ||||
| 		void to_json(Poco::JSON::Object &Obj) const; | ||||
| 		void to_json_with_status(Poco::JSON::Object &Obj) const; | ||||
| 		bool from_json(Poco::JSON::Object::Ptr &Obj); | ||||
| 		void Print() const; | ||||
| 	}; | ||||
|  | ||||
| 	struct Statistics { | ||||
| 		std::string SerialNumber; | ||||
| 		uint64_t 	UUID = 0 ; | ||||
| 		std::string Data; | ||||
| 		uint64_t 	Recorded = 0; | ||||
| 		void to_json(Poco::JSON::Object &Obj) const; | ||||
| 	}; | ||||
|  | ||||
| 	struct HealthCheck { | ||||
| 		std::string SerialNumber; | ||||
| 		uint64_t 	UUID = 0 ; | ||||
| 		std::string Data; | ||||
| 		uint64_t 	Recorded = 0 ; | ||||
| 		uint64_t 	Sanity = 0 ; | ||||
| 		void to_json(Poco::JSON::Object &Obj) const; | ||||
| 	}; | ||||
|  | ||||
| 	struct Capabilities { | ||||
| 		std::string Capabilities; | ||||
| 		uint64_t 	FirstUpdate = 0 ; | ||||
| 		uint64_t 	LastUpdate = 0 ; | ||||
| 		void 		to_json(Poco::JSON::Object &Obj) const; | ||||
| 	}; | ||||
|  | ||||
| 	struct DeviceLog { | ||||
| 		enum Level { | ||||
| 			LOG_EMERG = 0,	 /* system is unusable */ | ||||
| 			LOG_ALERT = 1,	 /* action must be taken immediately */ | ||||
| 			LOG_CRIT = 2,	 /* critical conditions */ | ||||
| 			LOG_ERR = 3,	 /* error conditions */ | ||||
| 			LOG_WARNING = 4, /* warning conditions */ | ||||
| 			LOG_NOTICE = 5,	 /* normal but significant condition */ | ||||
| 			LOG_INFO = 6,	 /* informational */ | ||||
| 			LOG_DEBUG = 7	 /* debug-level messages */ | ||||
| 		}; | ||||
| 		std::string SerialNumber; | ||||
| 		std::string Log; | ||||
| 		std::string Data; | ||||
| 		uint64_t 	Severity = 0 ; | ||||
| 		uint64_t 	Recorded = 0 ; | ||||
| 		uint64_t 	LogType = 0 ; | ||||
| 		uint64_t 	UUID = 0 ; | ||||
| 		void 		to_json(Poco::JSON::Object &Obj) const; | ||||
| 	}; | ||||
|  | ||||
| 	struct DefaultConfiguration { | ||||
| 		std::string Name; | ||||
| 		std::string Configuration; | ||||
| 		Types::StringVec Models; | ||||
| 		std::string Description; | ||||
| 		uint64_t 	Created; | ||||
| 		uint64_t 	LastModified; | ||||
| 		void 		to_json(Poco::JSON::Object &Obj) const; | ||||
| 		bool 		from_json(Poco::JSON::Object::Ptr &Obj); | ||||
| 	}; | ||||
|  | ||||
| 	struct CommandDetails { | ||||
| 		std::string UUID; | ||||
| 		std::string SerialNumber; | ||||
| 		std::string Command; | ||||
| 		std::string Status; | ||||
| 		std::string SubmittedBy; | ||||
| 		std::string Results; | ||||
| 		std::string Details; | ||||
| 		std::string ErrorText; | ||||
| 		uint64_t Submitted = time(nullptr); | ||||
| 		uint64_t Executed = 0; | ||||
| 		uint64_t Completed = 0 ; | ||||
| 		uint64_t RunAt = 0 ; | ||||
| 		uint64_t ErrorCode = 0 ; | ||||
| 		uint64_t Custom = 0 ; | ||||
| 		uint64_t WaitingForFile = 0 ; | ||||
| 		uint64_t AttachDate = 0 ; | ||||
| 		uint64_t AttachSize = 0 ; | ||||
| 		std::string AttachType; | ||||
| 		double 		executionTime = 0.0; | ||||
| 		void to_json(Poco::JSON::Object &Obj) const; | ||||
| 	}; | ||||
|  | ||||
| 	struct BlackListedDevice { | ||||
| 		std::string serialNumber; | ||||
| 		std::string reason; | ||||
| 		std::string author; | ||||
| 		uint64_t created; | ||||
| 		void to_json(Poco::JSON::Object &Obj) const; | ||||
| 		bool from_json(Poco::JSON::Object::Ptr &Obj); | ||||
| 	}; | ||||
|  | ||||
| 	struct RttySessionDetails { | ||||
| 		std::string SerialNumber; | ||||
| 		std::string Server; | ||||
| 		uint64_t 	Port = 0 ; | ||||
| 		std::string Token; | ||||
| 		uint64_t 	TimeOut = 0 ; | ||||
| 		std::string ConnectionId; | ||||
| 		uint64_t 	Started = 0 ; | ||||
| 		std::string CommandUUID; | ||||
| 		uint64_t 	ViewPort = 0 ; | ||||
| 		std::string DevicePassword; | ||||
| 		void to_json(Poco::JSON::Object &Obj) const; | ||||
| 	}; | ||||
|  | ||||
| 	struct Dashboard { | ||||
| 		uint64_t 		  snapshot = 0 ; | ||||
| 		uint64_t 		  numberOfDevices = 0 ; | ||||
| 		Types::CountedMap commands; | ||||
| 		Types::CountedMap upTimes; | ||||
| 		Types::CountedMap memoryUsed; | ||||
| 		Types::CountedMap load1; | ||||
| 		Types::CountedMap load5; | ||||
| 		Types::CountedMap load15; | ||||
| 		Types::CountedMap vendors; | ||||
| 		Types::CountedMap status; | ||||
| 		Types::CountedMap deviceType; | ||||
| 		Types::CountedMap healths; | ||||
| 		Types::CountedMap certificates; | ||||
| 		Types::CountedMap lastContact; | ||||
| 		Types::CountedMap associations; | ||||
| 		void to_json(Poco::JSON::Object &Obj) const; | ||||
| 		void reset(); | ||||
| 	}; | ||||
|  | ||||
| 	struct CapabilitiesModel { | ||||
| 		std::string deviceType; | ||||
| 		std::string capabilities; | ||||
|  | ||||
| 		void to_json(Poco::JSON::Object &Obj) const; | ||||
| 	}; | ||||
| } | ||||
							
								
								
									
										649
									
								
								src/RESTObjects/RESTAPI_ProvObjects.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										649
									
								
								src/RESTObjects/RESTAPI_ProvObjects.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,649 @@ | ||||
| // | ||||
| //	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_ProvObjects.h" | ||||
| #include "framework/MicroService.h" | ||||
|  | ||||
| using OpenWifi::RESTAPI_utils::field_to_json; | ||||
| using OpenWifi::RESTAPI_utils::field_from_json; | ||||
|  | ||||
| namespace OpenWifi::ProvObjects { | ||||
|  | ||||
|     void ObjectInfo::to_json(Poco::JSON::Object &Obj) const { | ||||
|         field_to_json(Obj,"id",id); | ||||
|         field_to_json(Obj,"name",name); | ||||
|         field_to_json(Obj,"description",description); | ||||
|         field_to_json(Obj,"created",created); | ||||
|         field_to_json(Obj,"modified",modified); | ||||
|         field_to_json(Obj,"notes",notes); | ||||
|         field_to_json(Obj,"tags",tags); | ||||
|     } | ||||
|  | ||||
|     bool ObjectInfo::from_json(const Poco::JSON::Object::Ptr &Obj) { | ||||
|         try { | ||||
|             field_from_json(Obj,"id",id); | ||||
|             field_from_json(Obj,"name",name); | ||||
|             field_from_json(Obj,"description",description); | ||||
|             field_from_json(Obj,"created",created); | ||||
|             field_from_json(Obj,"modified",modified); | ||||
|             field_from_json(Obj,"notes",notes); | ||||
|             field_from_json(Obj,"tags",tags); | ||||
|             return true; | ||||
|         } catch(...) { | ||||
|  | ||||
|         } | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     void ManagementPolicyEntry::to_json(Poco::JSON::Object &Obj) const { | ||||
|         field_to_json( Obj,"users",users); | ||||
|         field_to_json( Obj,"resources",resources); | ||||
|         field_to_json( Obj,"access",access); | ||||
|         field_to_json( Obj,"policy",policy); | ||||
|     } | ||||
|  | ||||
|     bool ManagementPolicyEntry::from_json(const Poco::JSON::Object::Ptr &Obj) { | ||||
|         try { | ||||
|             field_from_json( Obj,"users",users); | ||||
|             field_from_json( Obj,"resources",resources); | ||||
|             field_from_json( Obj,"access",access); | ||||
|             field_from_json( Obj,"policy",policy); | ||||
|             return true; | ||||
|         } catch(...) { | ||||
|  | ||||
|         } | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     void ManagementPolicy::to_json(Poco::JSON::Object &Obj) const { | ||||
|         info.to_json(Obj); | ||||
|         field_to_json(Obj, "entries", entries); | ||||
|         field_to_json(Obj, "inUse", inUse); | ||||
|         field_to_json(Obj, "entity", entity); | ||||
|     } | ||||
|  | ||||
|     bool ManagementPolicy::from_json(const Poco::JSON::Object::Ptr &Obj) { | ||||
|         try { | ||||
|             info.from_json(Obj); | ||||
|             field_from_json(Obj, "entries", entries); | ||||
|             field_from_json(Obj, "inUse", inUse); | ||||
|             field_from_json(Obj, "entity", entity); | ||||
|             return true; | ||||
|         } catch(...) { | ||||
|  | ||||
|         } | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     void Entity::to_json(Poco::JSON::Object &Obj) const { | ||||
|         info.to_json(Obj); | ||||
|         field_to_json( Obj,"parent",parent); | ||||
|         field_to_json( Obj,"venues",venues); | ||||
|         field_to_json( Obj,"children",children); | ||||
|         field_to_json( Obj,"contacts",contacts); | ||||
|         field_to_json( Obj,"locations",locations); | ||||
|         field_to_json( Obj,"managementPolicy",managementPolicy); | ||||
|         field_to_json( Obj,"deviceConfiguration",deviceConfiguration); | ||||
|         field_to_json( Obj,"devices",devices); | ||||
|         field_to_json( Obj,"rrm",rrm); | ||||
|         field_to_json( Obj,"sourceIP",sourceIP); | ||||
|     } | ||||
|  | ||||
|     bool Entity::from_json(const Poco::JSON::Object::Ptr &Obj) { | ||||
|         try { | ||||
|             info.from_json(Obj); | ||||
|             field_from_json( Obj,"parent",parent); | ||||
|             field_from_json( Obj,"venues",venues); | ||||
|             field_from_json( Obj,"children",children); | ||||
|             field_from_json( Obj,"contacts",contacts); | ||||
|             field_from_json( Obj,"locations",locations); | ||||
|             field_from_json( Obj,"managementPolicy",managementPolicy); | ||||
|             field_from_json( Obj,"deviceConfiguration",deviceConfiguration); | ||||
|             field_from_json( Obj,"devices",devices); | ||||
|             field_from_json( Obj,"rrm",rrm); | ||||
|             field_from_json( Obj,"sourceIP",sourceIP); | ||||
|             return true; | ||||
|         } catch(...) { | ||||
|  | ||||
|         } | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     void DiGraphEntry::to_json(Poco::JSON::Object &Obj) const { | ||||
|         field_to_json( Obj,"parent",parent); | ||||
|         field_to_json( Obj,"child",child); | ||||
|     } | ||||
|  | ||||
|     bool DiGraphEntry::from_json(const Poco::JSON::Object::Ptr &Obj) { | ||||
|         try { | ||||
|             field_from_json( Obj,"parent",parent); | ||||
|             field_from_json( Obj,"child",child); | ||||
|             return true; | ||||
|         } catch (...) { | ||||
|  | ||||
|         } | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     void Venue::to_json(Poco::JSON::Object &Obj) const { | ||||
|         info.to_json(Obj); | ||||
|         field_to_json( Obj,"parent",parent); | ||||
|         field_to_json( Obj,"entity",entity); | ||||
|         field_to_json( Obj,"children",children); | ||||
|         field_to_json( Obj,"devices",devices); | ||||
|         field_to_json( Obj,"topology",topology); | ||||
|         field_to_json( Obj,"parent",parent); | ||||
|         field_to_json( Obj,"design",design); | ||||
|         field_to_json( Obj,"managementPolicy",managementPolicy); | ||||
|         field_to_json( Obj,"deviceConfiguration",deviceConfiguration); | ||||
|         field_to_json( Obj,"contact",contact); | ||||
|         field_to_json( Obj,"location",location); | ||||
|         field_to_json( Obj,"rrm",rrm); | ||||
|         field_to_json( Obj,"sourceIP",sourceIP); | ||||
|     } | ||||
|  | ||||
|     bool Venue::from_json(const Poco::JSON::Object::Ptr &Obj) { | ||||
|         try { | ||||
|             info.from_json(Obj); | ||||
|             field_from_json( Obj,"parent",parent); | ||||
|             field_from_json( Obj,"entity",entity); | ||||
|             field_from_json( Obj,"children",children); | ||||
|             field_from_json( Obj,"devices",devices); | ||||
|             field_from_json( Obj,"topology",topology); | ||||
|             field_from_json( Obj,"parent",parent); | ||||
|             field_from_json( Obj,"design",design); | ||||
|             field_from_json( Obj,"managementPolicy",managementPolicy); | ||||
|             field_from_json( Obj,"deviceConfiguration",deviceConfiguration); | ||||
|             field_from_json( Obj,"contact",contact); | ||||
|             field_from_json( Obj,"location",location); | ||||
|             field_from_json( Obj,"rrm",rrm); | ||||
|             field_from_json( Obj,"sourceIP",sourceIP); | ||||
|             return true; | ||||
|         } catch (...) { | ||||
|  | ||||
|         } | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     void UserInfoDigest::to_json(Poco::JSON::Object &Obj) const { | ||||
|         field_to_json( Obj,"id",id); | ||||
|         field_to_json( Obj,"entity",loginId); | ||||
|         field_to_json( Obj,"children",userType); | ||||
|     } | ||||
|  | ||||
|     bool UserInfoDigest::from_json(const Poco::JSON::Object::Ptr &Obj) { | ||||
|         try { | ||||
|             field_from_json( Obj,"id",id); | ||||
|             field_from_json( Obj,"entity",loginId); | ||||
|             field_from_json( Obj,"children",userType); | ||||
|             return true; | ||||
|         } catch(...) { | ||||
|         } | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     void ManagementRole::to_json(Poco::JSON::Object &Obj) const { | ||||
|         info.to_json(Obj); | ||||
|         field_to_json( Obj,"managementPolicy",managementPolicy); | ||||
|         field_to_json( Obj,"users",users); | ||||
|         field_to_json( Obj,"entity",entity); | ||||
|     } | ||||
|  | ||||
|     bool ManagementRole::from_json(const Poco::JSON::Object::Ptr &Obj) { | ||||
|         try { | ||||
|             info.from_json(Obj); | ||||
|             field_from_json( Obj,"managementPolicy",managementPolicy); | ||||
|             field_from_json( Obj,"users",users); | ||||
|             field_from_json( Obj,"entity",entity); | ||||
|             return true; | ||||
|         } catch(...) { | ||||
|         } | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     void Location::to_json(Poco::JSON::Object &Obj) const { | ||||
|         info.to_json(Obj); | ||||
|         field_to_json( Obj,"type",OpenWifi::ProvObjects::to_string(type)); | ||||
|         field_to_json( Obj,"buildingName",buildingName); | ||||
|         field_to_json( Obj,"addressLines",addressLines); | ||||
|         field_to_json( Obj,"city",city); | ||||
|         field_to_json( Obj,"state",state); | ||||
|         field_to_json( Obj,"postal",postal); | ||||
|         field_to_json( Obj,"country",country); | ||||
|         field_to_json( Obj,"phones",phones); | ||||
|         field_to_json( Obj,"mobiles",mobiles); | ||||
|         field_to_json( Obj,"geoCode",geoCode); | ||||
|         field_to_json( Obj,"inUse",inUse); | ||||
|         field_to_json( Obj,"entity",entity); | ||||
|         field_to_json( Obj,"managementPolicy",managementPolicy); | ||||
|     } | ||||
|  | ||||
|     bool Location::from_json(const Poco::JSON::Object::Ptr &Obj) { | ||||
|         try { | ||||
|             info.from_json(Obj); | ||||
|             std::string tmp_type; | ||||
|             field_from_json( Obj,"type", tmp_type); | ||||
|             type = location_from_string(tmp_type); | ||||
|             field_from_json( Obj,"buildingName",buildingName); | ||||
|             field_from_json( Obj,"addressLines",addressLines); | ||||
|             field_from_json( Obj,"city",city); | ||||
|             field_from_json( Obj,"state",state); | ||||
|             field_from_json( Obj,"postal",postal); | ||||
|             field_from_json( Obj,"country",country); | ||||
|             field_from_json( Obj,"phones",phones); | ||||
|             field_from_json( Obj,"mobiles",mobiles); | ||||
|             field_from_json( Obj,"geoCode",geoCode); | ||||
|             field_from_json( Obj,"inUse",inUse); | ||||
|             field_from_json( Obj,"entity",entity); | ||||
|             field_from_json( Obj,"managementPolicy",managementPolicy); | ||||
|             return true; | ||||
|         } catch (...) { | ||||
|  | ||||
|         } | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     void Contact::to_json(Poco::JSON::Object &Obj) const { | ||||
|         info.to_json(Obj); | ||||
|         field_to_json( Obj,"type", to_string(type)); | ||||
|         field_to_json( Obj,"title",title); | ||||
|         field_to_json( Obj,"salutation",salutation); | ||||
|         field_to_json( Obj,"firstname",firstname); | ||||
|         field_to_json( Obj,"lastname",lastname); | ||||
|         field_to_json( Obj,"initials",initials); | ||||
|         field_to_json( Obj,"visual",visual); | ||||
|         field_to_json( Obj,"mobiles",mobiles); | ||||
|         field_to_json( Obj,"phones",phones); | ||||
|         field_to_json( Obj,"primaryEmail",primaryEmail); | ||||
|         field_to_json( Obj,"secondaryEmail",secondaryEmail); | ||||
|         field_to_json( Obj,"accessPIN",accessPIN); | ||||
|         field_to_json( Obj,"inUse",inUse); | ||||
|         field_to_json( Obj,"entity",entity); | ||||
|         field_to_json( Obj,"managementPolicy",managementPolicy); | ||||
|     } | ||||
|  | ||||
|     bool Contact::from_json(const Poco::JSON::Object::Ptr &Obj) { | ||||
|         try { | ||||
|             info.from_json(Obj); | ||||
|             std::string tmp_type; | ||||
|             field_from_json( Obj,"type", tmp_type); | ||||
|             type = contact_from_string(tmp_type); | ||||
|             field_from_json( Obj,"title",title); | ||||
|             field_from_json( Obj,"salutation",salutation); | ||||
|             field_from_json( Obj,"firstname",firstname); | ||||
|             field_from_json( Obj,"lastname",lastname); | ||||
|             field_from_json( Obj,"initials",initials); | ||||
|             field_from_json( Obj,"visual",visual); | ||||
|             field_from_json( Obj,"mobiles",mobiles); | ||||
|             field_from_json( Obj,"phones",phones); | ||||
|             field_from_json( Obj,"primaryEmail",primaryEmail); | ||||
|             field_from_json( Obj,"secondaryEmail",secondaryEmail); | ||||
|             field_from_json( Obj,"accessPIN",accessPIN); | ||||
|             field_from_json( Obj,"inUse",inUse); | ||||
|             field_from_json( Obj,"entity",entity); | ||||
|             field_from_json( Obj,"managementPolicy",managementPolicy); | ||||
|             return true; | ||||
|         } catch (...) { | ||||
|  | ||||
|         } | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     void InventoryTag::to_json(Poco::JSON::Object &Obj) const { | ||||
|         info.to_json(Obj); | ||||
|         field_to_json(Obj, "serialNumber", serialNumber); | ||||
|         field_to_json(Obj, "venue", venue); | ||||
|         field_to_json(Obj, "entity", entity); | ||||
|         field_to_json(Obj, "subscriber", subscriber); | ||||
|         field_to_json(Obj, "deviceType", deviceType); | ||||
|         field_to_json(Obj, "qrCode", qrCode); | ||||
|         field_to_json(Obj, "geoCode", geoCode); | ||||
|         field_to_json(Obj, "location", location); | ||||
|         field_to_json(Obj, "contact", contact); | ||||
|         field_to_json( Obj,"deviceConfiguration",deviceConfiguration); | ||||
|         field_to_json( Obj,"rrm",rrm); | ||||
|         field_to_json( Obj,"managementPolicy",managementPolicy); | ||||
|         field_to_json( Obj,"state",state); | ||||
|     } | ||||
|  | ||||
|     bool InventoryTag::from_json(const Poco::JSON::Object::Ptr &Obj) { | ||||
|         try { | ||||
|             info.from_json(Obj); | ||||
|             field_from_json( Obj,"serialNumber",serialNumber); | ||||
|             field_from_json( Obj,"venue",venue); | ||||
|             field_from_json( Obj,"entity",entity); | ||||
|             field_from_json( Obj,"subscriber",subscriber); | ||||
|             field_from_json( Obj,"deviceType",deviceType); | ||||
|             field_from_json(Obj, "qrCode", qrCode); | ||||
|             field_from_json( Obj,"geoCode",geoCode); | ||||
|             field_from_json( Obj,"location",location); | ||||
|             field_from_json( Obj,"contact",contact); | ||||
|             field_from_json( Obj,"deviceConfiguration",deviceConfiguration); | ||||
|             field_from_json( Obj,"rrm",rrm); | ||||
|             field_from_json( Obj,"managementPolicy",managementPolicy); | ||||
|             field_from_json( Obj,"state",state); | ||||
|             return true; | ||||
|         } catch(...) { | ||||
|  | ||||
|         } | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     void InventoryTagList::to_json(Poco::JSON::Object &Obj) const { | ||||
|         field_to_json( Obj,"taglist",taglist); | ||||
|     } | ||||
|  | ||||
|     bool InventoryTagList::from_json(const Poco::JSON::Object::Ptr &Obj) { | ||||
|         try { | ||||
|             field_from_json( Obj,"taglist",taglist); | ||||
|             return false; | ||||
|         } catch (...) { | ||||
|  | ||||
|         } | ||||
|         return false; | ||||
|     }; | ||||
|  | ||||
|     void DeviceConfigurationElement::to_json(Poco::JSON::Object &Obj) const { | ||||
|         field_to_json( Obj,"name", name); | ||||
|         field_to_json( Obj,"description", description); | ||||
|         field_to_json( Obj,"weight", weight); | ||||
|         field_to_json( Obj,"configuration", configuration); | ||||
|     } | ||||
|  | ||||
|     bool DeviceConfigurationElement::from_json(const Poco::JSON::Object::Ptr &Obj) { | ||||
|         try { | ||||
|             field_from_json( Obj,"name",name); | ||||
|             field_from_json( Obj,"description",description); | ||||
|             field_from_json( Obj,"weight",weight); | ||||
|             field_from_json( Obj,"configuration",configuration); | ||||
|             return true; | ||||
|         } catch(...) { | ||||
|  | ||||
|         } | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     void DeviceConfiguration::to_json(Poco::JSON::Object &Obj) const { | ||||
|         info.to_json(Obj); | ||||
|         field_to_json( Obj,"managementPolicy",managementPolicy); | ||||
|         field_to_json( Obj,"deviceTypes",deviceTypes); | ||||
|         field_to_json( Obj,"configuration",configuration); | ||||
|         field_to_json( Obj,"inUse",inUse); | ||||
|         field_to_json( Obj,"variables",variables); | ||||
|         field_to_json( Obj,"rrm",rrm); | ||||
|         field_to_json( Obj,"firmwareUpgrade",firmwareUpgrade); | ||||
|         field_to_json( Obj,"firmwareRCOnly",firmwareRCOnly); | ||||
|     } | ||||
|  | ||||
|     bool DeviceConfiguration::from_json(const Poco::JSON::Object::Ptr &Obj) { | ||||
|         try { | ||||
|             info.from_json(Obj); | ||||
|             field_from_json( Obj,"managementPolicy",managementPolicy); | ||||
|             field_from_json( Obj,"deviceTypes",deviceTypes); | ||||
|             field_from_json( Obj,"configuration",configuration); | ||||
|             field_from_json( Obj,"inUse",inUse); | ||||
|             field_from_json( Obj,"variables",variables); | ||||
|             field_from_json( Obj,"rrm",rrm); | ||||
|             field_from_json( Obj,"firmwareUpgrade",firmwareUpgrade); | ||||
|             field_from_json( Obj,"firmwareRCOnly",firmwareRCOnly); | ||||
|             return true; | ||||
|         } catch(...) { | ||||
|  | ||||
|         } | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     void Report::to_json(Poco::JSON::Object &Obj) const { | ||||
|         field_to_json(Obj, "snapshot", snapShot); | ||||
|         field_to_json(Obj, "devices", tenants); | ||||
|     }; | ||||
|  | ||||
|     void Report::reset() { | ||||
|         tenants.clear(); | ||||
|     } | ||||
|  | ||||
|     void ExpandedUseEntry::to_json(Poco::JSON::Object &Obj) const { | ||||
|         field_to_json(Obj, "uuid", uuid); | ||||
|         field_to_json(Obj, "name", name); | ||||
|         field_to_json(Obj, "description", description); | ||||
|     } | ||||
|  | ||||
|     bool ExpandedUseEntry::from_json(const Poco::JSON::Object::Ptr &Obj) { | ||||
|         try { | ||||
|             field_from_json( Obj,"uuid",uuid); | ||||
|             field_from_json( Obj,"name",name); | ||||
|             field_from_json( Obj,"description",description); | ||||
|             return true; | ||||
|         } catch(...) { | ||||
|  | ||||
|         } | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     void ExpandedUseEntryList::to_json(Poco::JSON::Object &Obj) const { | ||||
|         field_to_json(Obj, "type", type); | ||||
|         field_to_json(Obj, "entries", entries); | ||||
|     } | ||||
|  | ||||
|     bool ExpandedUseEntryList::from_json(const Poco::JSON::Object::Ptr &Obj) { | ||||
|         try { | ||||
|             field_from_json( Obj,"type",type); | ||||
|             field_from_json( Obj,"entries",entries); | ||||
|             return true; | ||||
|         } catch(...) { | ||||
|  | ||||
|         } | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     void ExpandedUseEntryMapList::to_json(Poco::JSON::Object &Obj) const { | ||||
|         field_to_json(Obj, "entries", entries); | ||||
|     } | ||||
|  | ||||
|     bool ExpandedUseEntryMapList::from_json(const Poco::JSON::Object::Ptr &Obj) { | ||||
|         try { | ||||
|             field_from_json( Obj,"entries",entries); | ||||
|             return true; | ||||
|         } catch(...) { | ||||
|  | ||||
|         } | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     void UuidList::to_json(Poco::JSON::Object &Obj) const { | ||||
|         field_to_json(Obj, "list", list); | ||||
|     } | ||||
|  | ||||
|     bool UuidList::from_json(const Poco::JSON::Object::Ptr &Obj) { | ||||
|         try { | ||||
|             field_from_json(Obj, "list", list); | ||||
|             return true; | ||||
|         } catch(...) { | ||||
|  | ||||
|         } | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     void field_to_json(Poco::JSON::Object &Obj, const char * FieldName, ACLACCESS A) { | ||||
|         switch(A) { | ||||
|             case READ: Obj.set(FieldName,"read"); break; | ||||
|             case MODIFY: Obj.set(FieldName,"modify"); break; | ||||
|             case CREATE: Obj.set(FieldName,"create"); break; | ||||
|             case DELETE: Obj.set(FieldName,"delete"); break; | ||||
|             case NONE: | ||||
|                 default: | ||||
|                     Obj.set(FieldName,"none"); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     void field_from_json(const Poco::JSON::Object::Ptr &Obj, const char * FieldName, ACLACCESS &A) { | ||||
|         if(Obj->has(FieldName)) { | ||||
|             auto V = Obj->getValue<std::string>(FieldName); | ||||
|             if(V=="read") | ||||
|                 A = READ; | ||||
|             else if(V=="modify") | ||||
|                 A = MODIFY; | ||||
|             else if(V=="create") | ||||
|                 A = CREATE; | ||||
|             else if(V=="delete") | ||||
|                 A = DELETE; | ||||
|             else if(V=="none") | ||||
|                 A = NONE; | ||||
|             else | ||||
|                 throw Poco::Exception("invalid JSON"); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     void ObjectACL::to_json(Poco::JSON::Object &Obj) const { | ||||
|         RESTAPI_utils::field_to_json(Obj, "users", users); | ||||
|         RESTAPI_utils::field_to_json(Obj, "roles", roles); | ||||
|         field_to_json(Obj, "access", access); | ||||
|     } | ||||
|  | ||||
|     bool ObjectACL::from_json(const Poco::JSON::Object::Ptr &Obj) { | ||||
|         try { | ||||
|             RESTAPI_utils::field_from_json(Obj, "users", users); | ||||
|             RESTAPI_utils::field_from_json(Obj, "roles", roles); | ||||
|             field_from_json(Obj, "access", access); | ||||
|             return true; | ||||
|         } catch(...) { | ||||
|  | ||||
|         } | ||||
|         return false; | ||||
|     } | ||||
|      | ||||
|     void ObjectACLList::to_json(Poco::JSON::Object &Obj) const { | ||||
|         RESTAPI_utils::field_to_json(Obj, "list", list); | ||||
|     } | ||||
|  | ||||
|     bool ObjectACLList::from_json(const Poco::JSON::Object::Ptr &Obj) { | ||||
|         try { | ||||
|             RESTAPI_utils::field_from_json(Obj, "list", list); | ||||
|             return true; | ||||
|         } catch(...) { | ||||
|  | ||||
|         } | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     std::string to_string(VISIBILITY A) { | ||||
|         switch(A) { | ||||
|             case PUBLIC: return "public"; | ||||
|             case SELECT: return "select"; | ||||
|             case PRIVATE: | ||||
|             default: | ||||
|                 return "private"; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     void field_to_json(Poco::JSON::Object &Obj, const char * FieldName, VISIBILITY A) { | ||||
|         Obj.set(FieldName,to_string(A)); | ||||
|     } | ||||
|  | ||||
|     VISIBILITY visibility_from_string(const std::string &V) { | ||||
|         if(V=="public") | ||||
|             return PUBLIC; | ||||
|         else if(V=="select") | ||||
|             return SELECT; | ||||
|         else if(V=="private") | ||||
|             return PRIVATE; | ||||
|         throw Poco::Exception("invalid json"); | ||||
|     } | ||||
|  | ||||
|     void field_from_json(const Poco::JSON::Object::Ptr &Obj, const char * FieldName, VISIBILITY &A) { | ||||
|         if(Obj->has(FieldName)) { | ||||
|             auto V = Obj->getValue<std::string>(FieldName); | ||||
|             A = visibility_from_string(V); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     void Map::to_json(Poco::JSON::Object &Obj) const { | ||||
|         info.to_json(Obj); | ||||
|         RESTAPI_utils::field_to_json( Obj,"data",data); | ||||
|         RESTAPI_utils::field_to_json( Obj,"entity",entity); | ||||
|         RESTAPI_utils::field_to_json( Obj,"creator",creator); | ||||
|         field_to_json( Obj,"visibility",visibility); | ||||
|         RESTAPI_utils::field_to_json( Obj,"access",access); | ||||
|     } | ||||
|  | ||||
|     bool Map::from_json(const Poco::JSON::Object::Ptr &Obj) { | ||||
|         try { | ||||
|             info.from_json(Obj); | ||||
|             RESTAPI_utils::field_from_json( Obj,"data",data); | ||||
|             RESTAPI_utils::field_from_json( Obj,"entity",entity); | ||||
|             RESTAPI_utils::field_from_json( Obj,"creator",creator); | ||||
|             field_from_json( Obj,"visibility",visibility); | ||||
|             RESTAPI_utils::field_from_json( Obj,"access",access); | ||||
|             return true; | ||||
|         } catch(...) { | ||||
|  | ||||
|         } | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     void MapList::to_json(Poco::JSON::Object &Obj) const { | ||||
|         RESTAPI_utils::field_to_json( Obj,"list",list); | ||||
|     } | ||||
|  | ||||
|     bool MapList::from_json(const Poco::JSON::Object::Ptr &Obj) { | ||||
|         try { | ||||
|             RESTAPI_utils::field_from_json( Obj,"list",list); | ||||
|             return true; | ||||
|         } catch(...) { | ||||
|  | ||||
|         } | ||||
|         return false; | ||||
|     } | ||||
|  | ||||
|     bool UpdateObjectInfo(const Poco::JSON::Object::Ptr &O, const SecurityObjects::UserInfo &U, ObjectInfo &I) { | ||||
|         uint64_t Now = std::time(nullptr); | ||||
|         if(O->has("name")) | ||||
|             I.name = O->get("name").toString(); | ||||
|  | ||||
|         if(I.name.empty()) | ||||
|             return false; | ||||
|  | ||||
|         if(O->has("description")) | ||||
|             I.description = O->get("description").toString(); | ||||
|         SecurityObjects::MergeNotes(O,U,I.notes); | ||||
|         SecurityObjects::NoteInfoVec N; | ||||
|         for(auto &i:I.notes) { | ||||
|             if(i.note.empty()) | ||||
|                 continue; | ||||
|             N.push_back(SecurityObjects::NoteInfo{.created=Now,.createdBy=U.email,.note=i.note}); | ||||
|         } | ||||
|         I.modified = Now; | ||||
|         return true; | ||||
|     } | ||||
|  | ||||
|     bool CreateObjectInfo(const Poco::JSON::Object::Ptr &O, const SecurityObjects::UserInfo &U, ObjectInfo &I) { | ||||
|         uint64_t Now = std::time(nullptr); | ||||
|         if(O->has("name")) | ||||
|             I.name = O->get("name").toString(); | ||||
|  | ||||
|         if(I.name.empty()) | ||||
|             return false; | ||||
|  | ||||
|         if(O->has("description")) | ||||
|             I.description = O->get("description").toString(); | ||||
|  | ||||
|         SecurityObjects::NoteInfoVec N; | ||||
|         for(auto &i:I.notes) { | ||||
|             if(i.note.empty()) | ||||
|                 continue; | ||||
|             N.push_back(SecurityObjects::NoteInfo{.created=Now,.createdBy=U.email,.note=i.note}); | ||||
|         } | ||||
|         I.notes = N; | ||||
|         I.modified = I.created = Now; | ||||
|         I.id = MicroService::CreateUUID(); | ||||
|  | ||||
|         return true; | ||||
|     } | ||||
| } | ||||
|  | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user