Compare commits

..

65 Commits

Author SHA1 Message Date
Ivan Chvets
3b7a24ea30 feat: Added reenroll to openapi.
Signed-off-by: Ivan Chvets <ivan.chvets@kinarasystems.com>
2025-08-14 11:45:36 -04:00
Carsten Schafer
438309714f Merge pull request #418 from Telecominfraproject/WIFI-14953-add-entire-trust-chain-for-rtty
WIFI-14953 Add entire trust chain for rtty use
2025-08-06 11:00:04 -04:00
Carsten Schafer
a9130eeb75 Read from proper client cas file
Signed-off-by: Carsten Schafer <Carsten.Schafer@kinarasystems.com>
2025-08-06 09:13:04 -04:00
Carsten Schafer
33068fca9e Declare the variable
Signed-off-by: Carsten Schafer <Carsten.Schafer@kinarasystems.com>
2025-08-05 11:15:51 -04:00
Carsten Schafer
d329151f6c Fix typo
Signed-off-by: Carsten Schafer <Carsten.Schafer@kinarasystems.com>
2025-08-05 10:46:45 -04:00
Carsten Schafer
ec846006bb Add entire trust chain for rtty use
Signed-off-by: Carsten Schafer <Carsten.Schafer@kinarasystems.com>
2025-08-05 10:24:33 -04:00
i-chvets
242261de0a Merge pull request #413 from Telecominfraproject/WIFI-14820-fix_wifi_frames_schema_update
WIFI-14820: fix: Added new parameters to wifi-frames
2025-07-14 15:53:41 -04:00
Ivan Chvets
31a4edead5 fix: Added new parameters to wifi-frames
https://telecominfraproject.atlassian.net/browse/WIFI-14820

Signed-off-by: Ivan Chvets <ivan.chvets@kinarasystems.com>
2025-07-14 14:00:47 -04:00
Carsten Schafer
f7b697f219 Merge pull request #412 from Telecominfraproject/PKI2-132_feat_ap_reenroll_command-fix
fix: ucentral reenroll command is reenroll not re-enroll
2025-07-10 10:14:49 -04:00
Carsten Schafer
e020da75fc fix: ucentral reenroll command is reenroll not re-enroll
Signed-off-by: Carsten Schafer <Carsten.Schafer@kinarasystems.com>
2025-07-10 09:50:45 -04:00
i-chvets
89702f56e0 Merge pull request #411 from Telecominfraproject/PKI2-132_feat_ap_reenroll_command
feat: Added reneroll command handler.
2025-07-03 08:24:14 -04:00
Ivan Chvets
0ac97442c0 feat: Added reneroll command handler.
https://telecominfraproject.atlassian.net/browse/PKI2-132

Signed-off-by: Ivan Chvets <ivan.chvets@kinarasystems.com>
2025-07-02 17:53:54 -04:00
i-chvets
e38b4c8a13 Merge pull request #410 from Telecominfraproject/PKI2-131_feat_add_issuer_name
PKI2-131: feat: Added issuer name to certificate data.
2025-07-02 16:55:40 -04:00
Ivan Chvets
9c5bbee834 feat: Added issuer name to certificate data.
https://telecominfraproject.atlassian.net/browse/PKI2-131

Signed-off-by: Ivan Chvets <ivan.chvets@kinarasystems.com>
2025-07-02 16:32:46 -04:00
i-chvets
a5d1eebe6d Merge pull request #405 from Telecominfraproject/version_update
WIFI-14521: fix: Version update - release 4.0.0
2025-04-24 16:56:09 -04:00
Ivan Chvets
ee14f064c8 Merge branch 'master' of github.com:Telecominfraproject/wlan-cloud-ucentralgw into version_update 2025-04-24 16:36:14 -04:00
i-chvets
dbf52c1f23 Merge pull request #406 from Telecominfraproject/WIFI-14521-ci-changes
WIFI-14521 Update to ubuntu-latest for GH runner
2025-04-24 16:18:31 -04:00
Carsten Schafer
9dc6a6bf97 Update to ubuntu-latest for GH runner
Signed-off-by: Carsten Schafer <Carsten.Schafer@kinarasystems.com>
2025-04-24 15:30:49 -04:00
Ivan Chvets
1c0556f8bf fix: Version update - release 4.0.0
Signed-off-by: Ivan Chvets <ivan.chvets@kinarasystems.com>
2025-04-24 14:09:12 -04:00
i-chvets
d298139525 Merge pull request #403 from Telecominfraproject/wifi-14521_feat_use_clientcas_for_validation
WIFI-14521: feat: Added processing of clientcas
2025-04-09 11:50:04 -04:00
Ivan Chvets
a37c961f5b feat: Added processing of clientcas
https://telecominfraproject.atlassian.net/browse/WIFI-14521

Summary of changes:
- Updated code to add certificates from clientcas to trust chain and
  validate client certificates against it.

Signed-off-by: Ivan Chvets <ivan.chvets@kinarasystems.com>
2025-04-09 10:30:52 -04:00
Nicolas Alfonso De Pineda Gutierrez
75bcbd748c Merge pull request #398 from Telecominfraproject/WIFI-14349-plat_cache.json-isnt-being-read-correctly-and-can-result-in-huge-memory-ussages-under-very-specific-situations
WIFI-14349-plat_cache.json-isnt-being-read-correctly-and-can-result-in-huge-memory-ussages-under-very-specific-situations
2025-01-21 16:00:14 +01:00
Nicolas de Pineda
b6eba2a96d fix: plat_cache.json not being read correctly
https://telecominfraproject.atlassian.net/browse/WIFI-14349

Summary of changes:
- Resolved an issue where a string field was being read as JSON, causing the output to be incorrectly surrounded by quotation marks.

Signed-off-by: Nicolas de Pineda <nicolas.depineda@galgus.ai>
2025-01-21 09:42:31 +01:00
i-chvets
17082803d4 Merge pull request #397 from Telecominfraproject/OLS-433-fix-switch-schema-sync
OLS-433: fix: updated internal switch schema
2024-12-10 10:23:27 -05:00
Ivan Chvets
26b9a96506 fix: updated internal switch schema
https://telecominfraproject.atlassian.net/browse/OLS-433

Summary of changes:
- Updated internal switch schema.

Signed-off-by: Ivan Chvets <ivan.chvets@kinarasystems.com>
2024-12-10 08:53:01 -05:00
i-chvets
5ce8dae9ec Merge pull request #393 from Telecominfraproject/OLS-433-fix-switch-schema-sync
OLS-433: fix: updated internal switch schema
2024-12-04 15:49:07 -05:00
i-chvets
7da135c1e5 Merge pull request #394 from Telecominfraproject/OLS-380-fix-file-upload
OSL-380: fix: modified code for file upload transactions
2024-12-04 15:48:47 -05:00
Carsten Schafer
50ee4ba5cb Merge pull request #395 from Telecominfraproject/version_update
fix: release 3.2.1 version update
2024-12-04 12:17:39 -05:00
Ivan Chvets
3a8109d7ad fix: release 3.2.1 version update
https://telecominfraproject.atlassian.net/browse/WIFI-14165

Signed-off-by: Ivan Chvets <ivan.chvets@kinarasystems.com>
2024-12-04 12:10:44 -05:00
Ivan Chvets
56232966ec fix: modified code for file upload transactions
https://telecominfraproject.atlassian.net/browse/OLS-380

Summary of changes:
- Modified code to have two distinct transaction in storage file upload.

Signed-off-by: Ivan Chvets <ivan.chvets@kinarasystems.com>
2024-11-29 13:09:34 -05:00
Ivan Chvets
1ecf98d712 fix: updated internal switch schema
https://telecominfraproject.atlassian.net/browse/OLS-433

Summary of changes:
- Updated internal switch schema.

Signed-off-by: Ivan Chvets <ivan.chvets@kinarasystems.com>
2024-11-29 11:56:05 -05:00
Gopi Raga
f5b60ced61 Merge pull request #392 from Telecominfraproject/WIFI-14292-fix-json-parse-error
WIFI-14292: fix: json parsing error
2024-11-17 11:20:00 +05:30
Ivan Chvets
e4d141bb8e fix: json parsing error
https://telecominfraproject.atlassian.net/browse/WIFI-14292

Summary of changes:
- Removed code that was incorrectly added to JSON string.

Signed-off-by: Ivan Chvets <ivan.chvets@kinarasystems.com>
2024-11-15 13:17:12 -05:00
i-chvets
25b4288050 Merge pull request #391 from Telecominfraproject/WIFI-14254-doc-update
WIFI-14254: doc: update protocol.md
2024-10-31 10:39:24 -04:00
Ivan Chvets
82430c2d5d doc: update protocol.md
https://telecominfraproject.atlassian.net/browse/WIFI-14254

Summary of changes:
- Updated PROTOCOL.md with compressed configuration message
  specification.

Signed-off-by: Ivan Chvets <ivan.chvets@kinarasystems.com>
2024-10-31 10:14:21 -04:00
i-chvets
7b68ec0536 Merge pull request #390 from Telecominfraproject/WIFI-14227-feat-compressed-config
WIFI-14229: feat: compressed config
2024-10-30 12:08:45 -04:00
Ivan Chvets
839f4fec44 feat: compressed config
https://telecominfraproject.atlassian.net/browse/WIFI-14229

Summary of changes:
- Modified code to compress configuration data, if capabilities object
  has compress command indicator enabled.
- Added encode and compress function to utilities.

Signed-off-by: Ivan Chvets <ivan.chvets@kinarasystems.com>
2024-10-30 11:44:02 -04:00
i-chvets
c4178209bb Merge pull request #388 from Telecominfraproject/version_update
WIFI-14165: Release 3.2 version update
2024-09-30 10:12:32 -04:00
Ivan Chvets
79ab67db50 fix: release 3.2 version update
https://telecominfraproject.atlassian.net/browse/WIFI-14165

Signed-off-by: Ivan Chvets <ivan.chvets@kinarasystems.com>
2024-09-30 09:39:44 -04:00
i-chvets
00bc77feea Merge pull request #385 from Telecominfraproject/WIFI-14134-fix_code_style_change
WIFI-14134: fix: file upload status - code style change
2024-09-27 10:59:14 -04:00
Ivan Chvets
4f00d77d2b fix: file upload status - code style change
https://telecominfraproject.atlassian.net/browse/WIFI-14134

Summary of changes:
- Code style change.

Signed-off-by: Ivan Chvets <ivan.chvets@kinarasystems.com>
2024-09-27 10:34:45 -04:00
i-chvets
c679d4ac40 Merge pull request #383 from Telecominfraproject/WIFI-14134-fix_file_upload_status
WIP: WIFI-14134: fix: file upload status
2024-09-26 21:33:56 -04:00
Ivan Chvets
4a150a9fcb fix: file upload status
https://telecominfraproject.atlassian.net/browse/WIFI-14134

Summary of changes:
- Return 202 in case of file pening upload.

Signed-off-by: Ivan Chvets <ivan.chvets@kinarasystems.com>
2024-09-26 16:07:19 -04:00
i-chvets
83eb603f0a Merge pull request #382 from Telecominfraproject/openapi-fix
WIFI-14165: Fix wrong port in openapi.yaml
2024-09-25 15:10:28 -04:00
Adam Capparelli
38bc0f0d69 Fix wrong port in openapi.yaml
Signed-off-by: Adam Capparelli <adam.capparelli@alumni.utoronto.ca>
2024-09-25 11:08:11 -04:00
i-chvets
e7362c2020 Merge pull request #381 from Telecominfraproject/ols-246-feat-cable-diag
OLS-246: feat: add cable diagnostics command
2024-09-25 10:42:52 -04:00
Ivan Chvets
9c9987e190 feat: add cable diagnostics command
https://telecominfraproject.atlassian.net/browse/OLS-246

Summary of changes:
- Modified code to match spec, ie. command is `cable-diagnostics`.

Signed-off-by: Ivan Chvets <ivan.chvets@kinarasystems.com>
2024-09-25 10:17:52 -04:00
i-chvets
4ac7b6ba0b Merge pull request #379 from Telecominfraproject/WIFI-14140-fix-update-schema-multi-psk
WIFI-14140: fix: sync-ed up ucentral schema with code - added missing code
2024-09-25 09:54:12 -04:00
Ivan Chvets
f9ee19af91 fix: sync-ed up ucentral schema with code - added missing code
https://telecominfraproject.atlassian.net/browse/WIFI-14140

Summary of changes:
- Synchronized built-in schema in configuration validation code with
ucentral schema. Added missing code.

Signed-off-by: Ivan Chvets <ivan.chvets@kinarasystems.com>
2024-09-25 08:30:58 -04:00
i-chvets
cd2ab8660f Merge pull request #376 from Telecominfraproject/WIFI-14140-fix-update-schema-multi-psk
WIFI-14140 fix: sync-ed up ucentral schema with code
2024-09-24 10:05:56 -04:00
Ivan Chvets
b9f00f6603 fix: sync-ed up ucentral schema with code
https://telecominfraproject.atlassian.net/browse/WIFI-14140

Summary of changes:
- Synchronized built-in schema in configuration validation code with
  ucentral schema.

Signed-off-by: Ivan Chvets <ivan.chvets@kinarasystems.com>
2024-09-19 10:26:51 -04:00
i-chvets
596cfd49e1 Merge pull request #375 from Telecominfraproject/ols-246-feat-cable-diag
OLS-246: feat: add cable diagnostics command
2024-09-18 11:45:14 -04:00
Ivan Chvets
b3deba5606 feat: add cable diagnostics command
https://telecominfraproject.atlassian.net/browse/OLS-246

Summary of changes:
- Added `cablediagnostics` command.

Signed-off-by: Ivan Chvets <ivan.chvets@kinarasystems.com>
2024-09-18 11:04:57 -04:00
i-chvets
a97d49a06b Merge pull request #373 from Telecominfraproject/WIFI-13126-feat-fixed-config-doc-update
WIFI-13126: feat: fixedconfig doc update
2024-09-16 10:05:55 -04:00
Ivan Chvets
b1be0604d6 feat: fixedconfig doc update
https://telecominfraproject.atlassian.net/browse/WIFI-13126

Summary of changes:
- Updated documentation.

Signed-off-by: Ivan Chvets <ivan.chvets@kinarasystems.com>
2024-09-13 11:30:40 -04:00
i-chvets
b29f7f7dc4 Merge pull request #372 from Telecominfraproject/WIFI-13126-feat-fixed-config
WIFI-13126: feat: add fixedconfig command
2024-09-11 16:59:16 -04:00
Ivan Chvets
132b31b06b feat: add fixedconfig command
https://telecominfraproject.atlassian.net/browse/WIFI-13126

Summary of changes:
- Added `fixedconfig` command.

Signed-off-by: Ivan Chvets <ivan.chvets@kinarasystems.com>
2024-09-11 16:20:02 -04:00
i-chvets
3114ff8a32 Merge pull request #371 from Telecominfraproject/WIFI-14038-fix-exception-handling-for-zero-queue
WIFI-14038: fix: update code to improve exception handling
2024-09-10 13:45:48 -04:00
Ivan Chvets
9c5aeda5dd fix: update code to improve exception handling
https://telecominfraproject.atlassian.net/browse/WIFI-14038

Summary of changes:
- Modified code to cover zero sized queues under exception handling.

Signed-off-by: Ivan Chvets <ivan.chvets@kinarasystems.com>
2024-08-29 17:57:05 -04:00
i-chvets
783ec99930 Merge pull request #370 from Telecominfraproject/WIFI-13875-fix-use-dns
WIFI-13875: fix: updated valijson version
2024-08-08 10:05:40 -04:00
Ivan Chvets
0c661b8b93 fix: updated valijson version
https://telecominfraproject.atlassian.net/browse/WIFI-13875

Summary of changes:
- Updated valijson version in Docker file to bring in fix for https://github.com/tristanpenman/valijson/issues/181

Signed-off-by: Ivan Chvets <ivan.chvets@kinarasystems.com>
2024-08-08 09:23:30 -04:00
i-chvets
9d7f4da504 Merge pull request #369 from Telecominfraproject/WIFI-14027-fix-ping-crash
fix: fix crash for non-configure commands
2024-08-01 19:03:54 -04:00
Ivan Chvets
a3b6e7c315 fix: fix crash for non-configure commands
https://telecominfraproject.atlassian.net/browse/WIFI-14027

Summary of changes:
- Modified code to relay errors only in case of configure command and
  strict mode.

Signed-off-by: Ivan Chvets <ivan.chvets@kinarasystems.com>
2024-08-01 17:09:09 -04:00
i-chvets
451680cd5a Merge pull request #368 from Telecominfraproject/WIFI-14019-fix-report-errors-in-strict-only
WIFI-14019: fix: relay errors from ap nos configuration only when strict mode is enabled
2024-07-30 12:50:25 -04:00
Ivan Chvets
7be48c3cfc fix: relay errors from ap nos configuration only when strict mode is
enabled
https://telecominfraproject.atlassian.net/browse/WIFI-14019

Summary of changes:
- Modified code to only relay errors from AP NOS configuration update
  only when strict mode is enabled.

NOTE: AP NOS is capable of modifying config thus fixing invalid configs
(in some cases) and applying resulting configuration. Warning messages
are produced, but error code is being sent back as error/failed.

Signed-off-by: Ivan Chvets <ivan.chvets@kinarasystems.com>
2024-07-30 12:21:19 -04:00
47 changed files with 813 additions and 688 deletions

View File

@@ -1,19 +1,19 @@
name: Build Docker image
name: CI
on:
push:
paths-ignore:
- 'openapi/**'
- '**.md'
- 'version'
- 'package*.json'
- 'helm/*.yaml'
- 'CMakeLists.txt'
branches:
- kinara
- master
- 'release/*'
tags:
- 'v*'
pull_request:
branches:
- kinara
- master
- 'release/*'
defaults:
run:
@@ -23,100 +23,79 @@ jobs:
docker:
runs-on: ubuntu-latest
env:
ECR_REGISTRY: 471112855615.dkr.ecr.us-east-1.amazonaws.com
ECR_REPOSITORY: owgw
AWS_REGION: us-east-1
DOCKER_REGISTRY_URL: tip-tip-wlan-cloud-ucentral.jfrog.io
DOCKER_REGISTRY_USERNAME: ucentral
steps:
- name: Checkout source
uses: actions/checkout@v4
with:
path: build
token: ${{ secrets.GIT_PUSH_PAT }}
persist-credentials: true
- name: Checkout actions repo
uses: actions/checkout@v3
with:
repository: Telecominfraproject/.github
path: github
- name: Checkout dot github repo
uses: actions/checkout@v4
with:
repository: kinarasystems/.github
ref: main
path: tools
token: ${{ secrets.GIT_PUSH_PAT }}
- name: Build and push Docker image
uses: ./github/composite-actions/docker-image-build
with:
image_name: owgw
registry: tip-tip-wlan-cloud-ucentral.jfrog.io
registry_user: ucentral
registry_password: ${{ secrets.DOCKER_REGISTRY_PASSWORD }}
- name: Bump version and checkin
if: github.ref == 'refs/heads/kinara'
id: bump-version
run: |
cd build
../tools/utils/setup-git-credentials "${{ secrets.GIT_PUSH_PAT}}"
../tools/utils/ver-bump -b -a -p -V kv -y helm/Chart.yaml -Y helm/values.yaml -M CMakeLists.txt
- name: Notify on failure via Slack
if: failure() && github.ref == 'refs/heads/master'
uses: rtCamp/action-slack-notify@v2
env:
SLACK_USERNAME: GitHub Actions failure notifier
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}
SLACK_COLOR: "${{ job.status }}"
SLACK_ICON: https://raw.githubusercontent.com/quintessence/slack-icons/master/images/github-logo-slack-icon.png
SLACK_TITLE: Docker build failed for OWGW service
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ${{ env.AWS_REGION }}
- name: Login to Amazon ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@v2
- name: Build, tag, and push image to Amazon ECR
id: build-image
env:
ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
IMAGE_TAG: ${{ github.sha }}
GITHUB_REF: ${{ github.ref }}
run: |
cd build
version=$(cat version)
../tools/utils/docker_build \
-m kinara \
-b "$GITHUB_REF" \
-t "$IMAGE_TAG" \
-r "$ECR_REGISTRY/$ECR_REPOSITORY" \
-v "kv${version}"
- name: Notify via Teams
#if: failure() && github.ref == 'refs/heads/kinara'
if: always()
uses: skitionek/notify-microsoft-teams@master
with:
webhook_url: ${{ secrets.MS_TEAMS_WEBHOOK }}
needs: ${{ toJson(needs) }}
job: ${{ toJson(job) }}
steps: ${{ toJson(steps) }}
dry_run: False
deploy-to-dev:
trigger-testing:
if: startsWith(github.ref, 'refs/pull/')
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/kinara'
env:
DEPLOY_NAME: owgw
AWS_DEFAULT_REGION: us-east-1
AWS_NAMESPACE: kic-dev1
AWS_EKS_NAME: kinara-dev
KUBECTL_VERSION: "v1.27.14"
needs: docker
steps:
- name: Get base branch name and set as output
id: get_base_branch
run: |
echo "branch=$(echo ${GITHUB_BASE_REF##*/} | sed 's/master/main/g')" >> $GITHUB_OUTPUT
- name: Checkout actions repo
uses: actions/checkout@v3
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 }}
with:
owner: Telecominfraproject
repo: wlan-testing
workflow: ow_docker-compose.yml
token: ${{ secrets.WLAN_TESTING_PAT }}
ref: master
inputs: '{"deployment_version": "${{ env.BASE_BRANCH }}", "owgw_version": "${{ github.sha }}", "owsec_version": "${{ env.BASE_BRANCH }}", "owfms_version": "${{ env.BASE_BRANCH }}", "owprov_version": "${{ env.BASE_BRANCH }}", "owanalytics_version": "${{ env.BASE_BRANCH }}", "owsub_version": "${{ env.BASE_BRANCH }}", "microservice": "owgw"}'
trigger-deploy-to-dev:
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/master'
needs:
- docker
steps:
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ${{ env.AWS_DEFAULT_REGION }}
- name: Checkout actions repo
uses: actions/checkout@v3
with:
repository: Telecominfraproject/.github
path: github
- name: Fetch kubeconfig
run: |
aws eks update-kubeconfig --name ${{ env.AWS_EKS_NAME }} --region ${{ env.AWS_DEFAULT_REGION }}
- name: Install kubectl
run: |
curl -s -LO "https://dl.k8s.io/release/${{ env.KUBECTL_VERSION }}/bin/linux/amd64/kubectl"
sudo install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl
- name: Rolling update of deployment
run: |
kubectl rollout restart deployment/${{ env.DEPLOY_NAME }} -n ${{ env.AWS_NAMESPACE }}
- name: Trigger deployment of the latest version to dev instance and wait for result
uses: ./github/composite-actions/trigger-workflow-and-wait
with:
owner: Telecominfraproject
repo: wlan-testing
workflow: ucentralgw-dev-deployment.yaml
token: ${{ secrets.WLAN_TESTING_PAT }}
ref: master
inputs: '{"force_latest": "true"}'

26
.github/workflows/cleanup.yml vendored Normal file
View File

@@ -0,0 +1,26 @@
name: Clean up PR Docker images
on:
pull_request:
branches:
- master
types: [ closed ]
defaults:
run:
shell: bash
jobs:
cleanup:
runs-on: ubuntu-latest
steps:
- name: Cleanup Docker image with PR branch tag
run: |
export PR_BRANCH_TAG=$(echo ${GITHUB_HEAD_REF#refs/heads/} | tr '/' '-')
if [[ ! $PR_BRANCH_TAG =~ (main|master|release-*) ]]; then
echo "PR branch is $PR_BRANCH_TAG, deleting Docker image"
curl -s -uucentral:${{ secrets.DOCKER_REGISTRY_PASSWORD }} -X DELETE "https://tip.jfrog.io/artifactory/tip-wlan-cloud-ucentral/owgw/$PR_BRANCH_TAG"
else
echo "PR branch is $PR_BRANCH_TAG, not deleting Docker image"
fi

View 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@v3
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 }}

41
.github/workflows/openapi-pages.yml vendored Normal file
View File

@@ -0,0 +1,41 @@
name: Update OpenAPI docs on GitHub Pages
on:
push:
paths:
- 'openapi/**'
branches:
- master
workflow_dispatch:
defaults:
run:
shell: bash
jobs:
docsgen:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Generate static HTML page with docs from OpenAPI definition
run: |
docker run --rm -v "${PWD}:/local" openapitools/openapi-generator-cli:v6.2.1 generate -i https://raw.githubusercontent.com/Telecominfraproject/wlan-cloud-ucentralgw/master/openapi/owgw.yaml -g html2 --skip-validate-spec -o /local/
- name: Update OpenAPI docs
run: |
mkdir tmp-docs
mv index.html tmp-docs/index.html
mkdir -p ~/.ssh
ssh-keyscan -H github.com >> ~/.ssh/known_hosts
echo https://tip-automation:${{ secrets.GIT_PUSH_PAT }}@github.com > ~/.git-credentials
git config --global credential.helper store
git config --global user.email "tip-automation@telecominfraproject.com"
git config --global user.name "TIP Automation User"
git pull
git checkout gh-pages || git checkout -b gh-pages
rm -rf docs
mv tmp-docs docs
git add docs
git commit -m'Update OpenAPI docs for GitHub pages'
git push --set-upstream origin gh-pages

46
.github/workflows/release.yml vendored Normal file
View File

@@ -0,0 +1,46 @@
name: Release chart package
on:
push:
tags:
- 'v*'
defaults:
run:
shell: bash
jobs:
helm-package:
runs-on: ubuntu-latest
env:
HELM_REPO_URL: https://tip.jfrog.io/artifactory/tip-wlan-cloud-ucentral-helm/
HELM_REPO_USERNAME: ucentral
steps:
- name: Checkout uCentral assembly chart repo
uses: actions/checkout@v3
with:
path: wlan-cloud-ucentralgw
- name: Build package
working-directory: wlan-cloud-ucentralgw/helm
run: |
helm plugin install https://github.com/aslafy-z/helm-git --version 0.10.0
helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo update
helm dependency update
mkdir dist
helm package . -d dist
- name: Generate GitHub release body
working-directory: wlan-cloud-ucentralgw/helm
run: |
pip3 install yq -q
echo "Docker image - tip-tip-wlan-cloud-ucentral.jfrog.io/owgw:$GITHUB_REF_NAME" > release.txt
echo "Helm charted may be attached to this release" >> release.txt
echo "Deployment artifacts may be found in https://github.com/Telecominfraproject/wlan-cloud-ucentral-deploy/tree/$GITHUB_REF_NAME" >> release.txt
- name: Create GitHub release
uses: softprops/action-gh-release@v1
with:
body_path: wlan-cloud-ucentralgw/helm/release.txt
files: wlan-cloud-ucentralgw/helm/dist/*

1
.gitignore vendored
View File

@@ -29,3 +29,4 @@ helm/charts/*
!helm/charts/.gitkeep
/portal-test/
/src/ow_version.h

View File

@@ -1,16 +0,0 @@
## 3.0.6 (July 30, 2024)
- chore: updated package.json, updated helm/Chart.yaml, updated helm/values.yaml, updated CMakeLists.txt, updated version, updated CHANGELOG.md, bumped 3.0.5 -> 3.0.6
- chore: updated package.json, updated helm/Chart.yaml, updated helm/values.yaml, updated CMakeLists.txt, updated version, updated CHANGELOG.md, bumped 3.0.4 -> 3.0.5
## 3.0.5 (July 22, 2024)
- chore: updated package.json, updated helm/Chart.yaml, updated helm/values.yaml, updated CMakeLists.txt, updated version, updated CHANGELOG.md, bumped 3.0.4 -> 3.0.5
- Merge pull request #8 from kinarasystems/command
## 3.0.4 (July 17, 2024)
- chore: updated package.json, updated helm/Chart.yaml, updated helm/values.yaml, updated CMakeLists.txt, updated version, updated CHANGELOG.md, bumped 3.0.3 -> 3.0.4
- Merge pull request #7 from kinarasystems/fix_uptime_update_after_reboot
## 3.0.3 (June 19, 2024)
- chore: updated package.json, updated helm/Chart.yaml, updated helm/values.yaml, updated CMakeLists.txt, updated version, created CHANGELOG.md, bumped 3.0.2 -> 3.0.3
- Merge pull request #6 from kinarasystems/devices

View File

@@ -1,5 +1,5 @@
cmake_minimum_required(VERSION 3.13)
project(owgw VERSION 3.0.6)
project(owgw VERSION 4.1.0)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED True)

View File

@@ -1,7 +1,7 @@
ARG DEBIAN_VERSION=11.5-slim
ARG POCO_VERSION=poco-tip-v2
ARG CPPKAFKA_VERSION=tip-v1
ARG VALIJASON_VERSION=tip-v1
ARG VALIJASON_VERSION=tip-v1.0.2
ARG APP_NAME=owgw
ARG APP_HOME_DIR=/openwifi

View File

@@ -306,8 +306,54 @@ The device should answer:
},
"id" : <same number>
}
```
#### Controller wants the device to apply a given fixed configuration
Controller sends this command when it requires the device to apply fixed configuration, eg. country code. The device
should respond with message indicating failure or success.
```json
{ "jsonrpc" : "2.0",
"method" : "fixedconfig",
"params" : {
"serial" : <serial number>,
"when" : Optional - <UTC time when to apply this config, 0 means immediate, this is a suggestion>
"country" : "<country-code>"
},
}
```
If AP supports compressed configuration feature by inidcating `compress_cmd=true` in its capabilities, controller
will send a compressed configuration message where configuration payload (i.e. contents of `params`) is compressed
and encoded in base64 format:
```json
{ "jsonrpc" : "2.0",
"method" : "configure",
"params" : {
"compress_64" : "<b64 encoded zlib compressed payload>",
"compress_sz" : "<size of uncompressed data in bytes>"
},
"id" : <some number>
}
```
The device should answer:
```json
{ "jsonrpc" : "2.0",
"result" : {
"serial": <serial number>,
"status": {
"error": 0 or an error number,
"text": <description of the error or success, eg. "Applied fixed config, rebooting">
},
"uuid": <UUID>
}
}
```
##### The Answer
The device can answer and tell the controller it has rejected certain parts of the config and potentially replaced them with
appropriate values. This could be used to allow a device to replace frequencies for the regions it is located in. The device
@@ -834,6 +880,32 @@ The device should answer:
}
```
#### Controller wants the device to perform re-enrollment
Controller sends this command to trigger re-enrollment, i.e. update of operational certificate. Extreme care must be taken.
```json
{ "jsonrpc" : "2.0" ,
"method" : "reenroll" ,
"params" : {
"serial" : <serial number>,
"when" : Optional - <UTC time when to apply this config, 0 mean immediate, this is a suggestion>
},
"id" : <some number>
}
```
The device should answer:
```json
{ "jsonrpc" : "2.0" ,
"result" : {
"serial" : <serial number> ,
"status" : {
"error" : <0 or the value of $? from the shell running the command, 255 signifies a timeout>,
"txt" : <text describing the error or success>
},
"id" : <same number as request>
}
```
#### Controller wants the device to switch to another controller
Controller sends this when the device should change the controller it connects to without looking up a new redirector.

View File

@@ -1,21 +0,0 @@
#!/bin/bash
set -e
[ -z "$AWS_PROFILE" ] && echo "Please set AWS_PROFILE" && exit 1
registry="471112855615.dkr.ecr.us-east-1.amazonaws.com"
repo="owgw"
aws ecr get-login-password --region us-east-1 | docker login --username AWS --password-stdin "$registry"
img="$registry/$repo"
if [ -n "$1" ] ; then
version="$1"
else
version="latest"
fi
#date > about.txt
#id=$(git rev-parse HEAD)
#br=$(git branch --show-current)
#echo "$br" >> about.txt
#echo "$id" >> about.txt
#echo "Built manually via $0" >> about.txt
#docker build --no-cache -t $img:$version .
docker build -t $img:$version .
docker push $img:$version

20
buildit
View File

@@ -1,20 +0,0 @@
#!/bin/bash
set -e
repo="owgw"
[ -z "$REMOTE_DOCKER_HOST" ] && echo "Please set DOCKER_HOST" && exit 1
[ -z "$REMOTE_DOCKER_PASSWORD" ] && echo "Please set DOCKER_PASSWORD" && exit 1
img="$REMOTE_DOCKER_HOST/kinara/$repo"
if [ -n "$1" ] ; then
version="$1"
else
version="latest"
fi
#date > about.txt
#id=$(git rev-parse HEAD)
#br=$(git branch --show-current)
#echo "$br" >> about.txt
#echo "$id" >> about.txt
#echo "Built manually via $0" >> about.txt
#docker build --no-cache -t $img:$version .
docker build -t $img:$version .
docker push $img:$version

View File

@@ -1,18 +1,18 @@
apiVersion: v2
appVersion: "3.0.6"
appVersion: "1.0"
description: A Helm chart for Kubernetes
name: owgw
version: 0.1.0
dependencies:
- name: postgresql
repository: https://tip.jfrog.io/artifactory/tip-wlan-cloud-ucentral-helm/
version: 10.9.2
condition: postgresql.enabled
- name: mysql
repository: https://tip.jfrog.io/artifactory/tip-wlan-cloud-ucentral-helm/
version: 8.8.3
condition: mysql.enabled
- name: mariadb
repository: https://tip.jfrog.io/artifactory/tip-wlan-cloud-ucentral-helm/
version: 9.4.2
condition: mariadb.enabled
- name: postgresql
repository: https://tip.jfrog.io/artifactory/tip-wlan-cloud-ucentral-helm/
version: 10.9.2
condition: postgresql.enabled
- name: mysql
repository: https://tip.jfrog.io/artifactory/tip-wlan-cloud-ucentral-helm/
version: 8.8.3
condition: mysql.enabled
- name: mariadb
repository: https://tip.jfrog.io/artifactory/tip-wlan-cloud-ucentral-helm/
version: 9.4.2
condition: mariadb.enabled

View File

@@ -2,21 +2,24 @@
replicaCount: 1
strategyType: Recreate
revisionHistoryLimit: 2
nameOverride: ""
fullnameOverride: ""
images:
owgw:
repository: 471112855615.dkr.ecr.us-east-1.amazonaws.com/owgw
tag: kv3.0.6
repository: tip-tip-wlan-cloud-ucentral.jfrog.io/owgw
tag: master
pullPolicy: Always
# regcred:
# registry: tip-tip-wlan-cloud-ucentral.jfrog.io
# username: username
# password: password
# regcred:
# registry: tip-tip-wlan-cloud-ucentral.jfrog.io
# username: username
# password: password
dockerize:
repository: 471112855615.dkr.ecr.us-east-1.amazonaws.com/wait-ready
tag: latest
repository: tip-tip-wlan-cloud-ucentral.jfrog.io/dockerize
tag: 0.16.0
pullPolicy: IfNotPresent
services:
owgw:
type: ClusterIP
@@ -59,6 +62,7 @@ services:
servicePort: 3799
targetPort: 3799
protocol: UDP
checks:
owgw:
liveness:
@@ -69,31 +73,33 @@ checks:
exec:
command:
- /readiness_check
ingresses:
restapi:
enabled: false
annotations: {}
# kubernetes.io/ingress.class: nginx
# kubernetes.io/tls-acme: "true"
# kubernetes.io/ingress.class: nginx
# kubernetes.io/tls-acme: "true"
hosts:
- restapi.chart-example.local
- restapi.chart-example.local
paths:
- path: /
pathType: ImplementationSpecific
serviceName: owgw
servicePort: restapi
- path: /
pathType: ImplementationSpecific
serviceName: owgw
servicePort: restapi
fileuploader:
enabled: false
annotations: {}
# kubernetes.io/ingress.class: nginx
# kubernetes.io/tls-acme: "true"
# kubernetes.io/ingress.class: nginx
# kubernetes.io/tls-acme: "true"
hosts:
- fileuploader.chart-example.local
- fileuploader.chart-example.local
paths:
- path: /
pathType: ImplementationSpecific
serviceName: owgw
servicePort: fileuploader
- path: /
pathType: ImplementationSpecific
serviceName: owgw
servicePort: fileuploader
volumes:
owgw:
- name: config
@@ -119,17 +125,18 @@ volumes:
volumeDefinition: |
persistentVolumeClaim:
claimName: {{ template "owgw.fullname" . }}-pvc
resources: {}
# We usually recommend not to specify default resources and to leave this as a conscious
# choice for the user. This also increases chances charts run on environments with little
# resources, such as Minikube. If you do want to specify resources, uncomment the following
# lines, adjust them as necessary, and remove the curly braces after 'resources:'.
# requests:
# cpu: 100m
# memory: 128Mi
# limits:
# cpu: 100m
# memory: 128Mi
# We usually recommend not to specify default resources and to leave this as a conscious
# choice for the user. This also increases chances charts run on environments with little
# resources, such as Minikube. If you do want to specify resources, uncomment the following
# lines, adjust them as necessary, and remove the curly braces after 'resources:'.
# requests:
# cpu: 100m
# memory: 128Mi
# limits:
# cpu: 100m
# memory: 128Mi
securityContext:
fsGroup: 1000
@@ -144,12 +151,18 @@ securityContext:
# value: "2"
#- name: net.ipv4.tcp_keepalive_time
# value: "45"
nodeSelector: {}
tolerations: []
affinity: {}
podAnnotations: {}
podSecurityPolicy:
enabled: false
persistence:
enabled: true
# storageClassName: "-"
@@ -157,6 +170,7 @@ persistence:
- ReadWriteOnce
size: 10Gi
annotations: {}
# Application
public_env_variables:
OWGW_ROOT: /owgw-data
@@ -166,10 +180,12 @@ public_env_variables:
# 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
#OWSEC: gw-qa01.cicd.lab.wlan.tip.build:16001
secret_env_variables:
# NOTE in order for readiness check to use system info method you need to override these values to the real OWSEC credentials
OWSEC_USERNAME: tip@ucentral.com
OWSEC_PASSWORD: openwifi
configProperties:
# -> Public part
# Websocket
@@ -294,6 +310,7 @@ configProperties:
archiver.db.2.keep: 7
archiver.db.3.name: commandlist
archiver.db.3.keep: 7
# -> Secret part
# Websocket
ucentral.websocket.host.0.key.password: mypassword
@@ -315,8 +332,10 @@ configProperties:
## MySQL
storage.type.mysql.username: stephb
storage.type.mysql.password: snoopy99
# NOTE: List of required certificates may be found in "certs" key. Alternative way to pass required certificates is to create external secret with all required certificates and set secret name in "existingCertsSecret" key. Details may be found in https://github.com/Telecominfraproject/wlan-cloud-ucentral-deploy/tree/main/chart#tldr
existingCertsSecret: ""
certs:
clientcas.pem: ""
issuer.pem: ""
@@ -326,53 +345,66 @@ certs:
root.pem: ""
websocket-cert.pem: ""
websocket-key.pem: ""
certsCAs:
issuer.pem: ""
root.pem: ""
# PostgreSQL (https://github.com/bitnami/charts/tree/master/bitnami/postgresql)
postgresql:
enabled: false
image:
registry: docker.io
repository: bitnami/postgresql
tag: 11.13.0-debian-10-r0
postgresqlPostgresPassword: "rootPassword"
postgresqlUsername: stephb
postgresqlPassword: snoopy99
postgresqlDatabase: owgw
persistence:
enabled: true
storageClass: ""
size: 8Gi
# MySQL (https://github.com/bitnami/charts/tree/master/bitnami/mysql)
mysql:
enabled: false
image:
registry: docker.io
repository: bitnami/mysql
tag: 8.0.26-debian-10-r10
auth:
rootPassword: rootPassword
database: owgw
username: stephb
password: snoopy99
primary:
persistence:
enabled: true
storageClass: ""
size: 8Gi
# MariaDB (https://github.com/bitnami/charts/tree/master/bitnami/mariadb)
mariadb:
enabled: false
image:
registry: docker.io
repository: bitnami/mariadb
tag: 10.5.12-debian-10-r0
auth:
rootPassword: rootPassword
database: owgw
username: stephb
password: snoopy99
primary:
persistence:
enabled: true

View File

@@ -12,7 +12,7 @@ info:
url: https://www.ucentral.info/support
servers:
- url: 'https://localhost:16001/api/v1'
- url: 'https://localhost:16002/api/v1'
security:
- bearerAuth: []
@@ -1576,6 +1576,15 @@ components:
format: base64
description: This is a base64 encoded string of the certificate bundle (the current bundle .tar.gz file from the PKI portal)
ReenrollRequest:
type: object
properties:
serialNumber:
type: string
when:
type: integer
format: int64
PowerCycleRequest:
type: object
properties:
@@ -3056,6 +3065,32 @@ paths:
404:
$ref: '#/components/responses/NotFound'
/device/{serialNumber}/reenroll:
post:
tags:
- Commands
summary: Reenroll operational certificate for the device.
operationId: reenrollCertificate
parameters:
- in: path
name: serialNumber
schema:
type: string
required: true
requestBody:
description: Reenroll operational certificate for the device
content:
application/json:
schema:
$ref: '#/components/schemas/ReenrollRequest'
responses:
200:
$ref: '#/components/responses/Success'
403:
$ref: '#/components/responses/Unauthorized'
404:
$ref: '#/components/responses/NotFound'
/device/{serialNumber}/powercycle:
post:
tags:

View File

@@ -1,15 +0,0 @@
{
"name": "owgw",
"version": "3.0.6",
"description": "This is the Kinara version of OpenWifi OWGW",
"author": "Kinara Systems",
"homepage": "https://kinarasystems.com",
"repository": {
"type": "git",
"url": "https://github.com/kinarasystems/wlan-cloud-ucentralgw"
},
"keywords": [
"owgw",
"gateway"
]
}

View File

@@ -213,6 +213,7 @@ namespace OpenWifi {
}
State_.certificateExpiryDate = PeerCert.expiresOn().timestamp().epochTime();
State_.certificateIssuerName = PeerCert.issuerName();
poco_trace(Logger_,
fmt::format("TLS-CONNECTION({}): Session={} CN={} Completed. (t={})", CId_,

View File

@@ -71,14 +71,18 @@ namespace OpenWifi {
bool AP_WS_Server::ValidateCertificate(const std::string &ConnectionId,
const Poco::Crypto::X509Certificate &Certificate) {
if (IsCertOk()) {
if (!Certificate.issuedBy(*IssuerCert_)) {
poco_warning(
Logger(),
fmt::format("CERTIFICATE({}): issuer mismatch. Local='{}' Incoming='{}'",
ConnectionId, IssuerCert_->issuerName(), Certificate.issuerName()));
return false;
// validate certificate agains trusted chain
for (const auto &cert : ClientCasCerts_) {
if (Certificate.issuedBy(cert)) {
return true;
}
}
return true;
poco_warning(
Logger(),
fmt::format(
"CERTIFICATE({}): issuer mismatch. Certificate not issued by any trusted CA",
ConnectionId)
);
}
return false;
}
@@ -133,6 +137,13 @@ namespace OpenWifi {
Context->addChainCertificate(Issuing);
Context->addCertificateAuthority(Issuing);
// add certificates from clientcas to trust chain
ClientCasCerts_ = Poco::Net::X509Certificate::readPEM(Svr.ClientCas());
for (const auto &cert : ClientCasCerts_) {
Context->addChainCertificate(cert);
Context->addCertificateAuthority(cert);
}
Poco::Crypto::RSAKey Key("", Svr.KeyFile(), Svr.KeyFilePassword());
Context->usePrivateKey(Key);
@@ -784,4 +795,4 @@ namespace OpenWifi {
return false;
}
} // namespace OpenWifi
} // namespace OpenWifi

View File

@@ -223,6 +223,7 @@ namespace OpenWifi {
mutable std::array<std::mutex,MACHashMax> SerialNumbersMutex_;
std::unique_ptr<Poco::Crypto::X509Certificate> IssuerCert_;
std::vector<Poco::Crypto::X509Certificate> ClientCasCerts_;
std::list<std::unique_ptr<Poco::Net::HTTPServer>> WebServers_;
Poco::ThreadPool DeviceConnectionPool_{"ws:dev-pool", 4, 256};
Poco::Net::SocketReactor Reactor_;

View File

@@ -111,7 +111,7 @@ namespace OpenWifi {
i >> cache;
for (const auto &[Type, Platform] : cache.items()) {
Platforms_[Type] = Poco::toLower(to_string(Platform));
Platforms_[Type] = Poco::toLower(Platform.get<std::string>());
}
} catch (...) {
}

View File

@@ -25,12 +25,23 @@ namespace OpenWifi::RESTAPI_RPC {
if (StorageService()->AddCommand(Cmd.SerialNumber, Cmd, Status)) {
Poco::JSON::Object RetObj;
Cmd.to_json(RetObj);
if (Handler != nullptr)
if (Cmd.ErrorCode){
return Handler->ReturnObject(RetObj, Poco::Net::HTTPResponse::HTTP_BAD_REQUEST);
if (Handler == nullptr) {
// nothing to process/return
return;
}
Poco::Net::HTTPResponse::HTTPStatus cmd_status = Poco::Net::HTTPResponse::HTTP_OK;
if (Cmd.ErrorCode > 0) {
// command returned error
cmd_status = Poco::Net::HTTPResponse::HTTP_BAD_REQUEST;
if (Cmd.Command == uCentralProtocol::CONFIGURE) {
// special handling for configure command
if (!Handler->GetBoolParameter("strict", false)) {
// in non-strict mode return success for failed configure command
cmd_status = Poco::Net::HTTPResponse::HTTP_OK;
}
}
return Handler->ReturnObject(RetObj);
return;
}
return Handler->ReturnObject(RetObj, cmd_status);
}
if (Handler != nullptr)
return Handler->ReturnStatus(Poco::Net::HTTPResponse::HTTP_INTERNAL_SERVER_ERROR);
@@ -43,8 +54,8 @@ namespace OpenWifi::RESTAPI_RPC {
std::chrono::milliseconds WaitTimeInMs, Poco::JSON::Object *ObjectToReturn,
RESTAPIHandler *Handler, Poco::Logger &Logger, bool Deferred) {
Logger.information(fmt::format("{},{}: New {} command. User={} Serial={}. ", Cmd.UUID,
RPCID, Cmd.Command, Cmd.SubmittedBy, Cmd.SerialNumber));
Logger.information(fmt::format("{},{}: New {} command. User={} Serial={} Details={}. ", Cmd.UUID,
RPCID, Cmd.Command, Cmd.SubmittedBy, Cmd.SerialNumber, Cmd.Details));
Cmd.Submitted = Utils::Now();
Cmd.Executed = 0;
@@ -171,7 +182,12 @@ namespace OpenWifi::RESTAPI_RPC {
}
// If the command fails on the device we should show it as failed and not return 200 OK
if (Cmd.ErrorCode) {
// exception is configure command which only reported failed in strict validation mode
if (Cmd.ErrorCode &&
(Cmd.Command != uCentralProtocol::CONFIGURE ||
(Cmd.Command == uCentralProtocol::CONFIGURE && Handler->GetBoolParameter("strict", false))
))
{
Logger.information(fmt::format(
"Command failed with error on device: {} Reason: {}.",
Cmd.ErrorCode, Cmd.ErrorText));

View File

@@ -167,7 +167,11 @@ namespace OpenWifi {
{APCommands::Commands::certupdate, false, true, &RESTAPI_device_commandHandler::CertUpdate, 60000ms},
{APCommands::Commands::transfer, false, true, &RESTAPI_device_commandHandler::Transfer, 60000ms},
{APCommands::Commands::script, false, true, &RESTAPI_device_commandHandler::Script, 60000ms},
{APCommands::Commands::powercycle, false, true, &RESTAPI_device_commandHandler::PowerCycle, 60000ms}
{APCommands::Commands::powercycle, false, true, &RESTAPI_device_commandHandler::PowerCycle, 60000ms},
{APCommands::Commands::fixedconfig, false, true, &RESTAPI_device_commandHandler::FixedConfig, 120000ms},
{APCommands::Commands::cablediagnostics, false, true, &RESTAPI_device_commandHandler::CableDiagnostics, 120000ms},
{APCommands::Commands::reenroll, false, true, &RESTAPI_device_commandHandler::ReEnroll, 120000ms},
};
void RESTAPI_device_commandHandler::DoPost() {
@@ -691,9 +695,31 @@ namespace OpenWifi {
Params.stringify(ParamStream);
Cmd.Details = ParamStream.str();
// retrieve capabilities and encode/compress parameters, if required
Poco::JSON::Object ConfigParams = Params;
GWObjects::Capabilities Caps;
if (StorageService()->GetDeviceCapabilities(SerialNumber_, Caps)) {
Poco::JSON::Object CapsJson;
Caps.to_json(CapsJson);
auto DeviceCaps = CapsJson.getObject(uCentralProtocol::CAPABILITIES);
if (DeviceCaps->has("compress_cmd") && DeviceCaps->get("compress_cmd")) {
// compressed command capability present and it is set, compress parameters
Poco::JSON::Object CompressedParams;
std::string CompressedBase64Data;
std::uint64_t UncompressedDataLen = ParamStream.str().length();
if (Utils::CompressAndEncodeBase64(ParamStream.str(), CompressedBase64Data)) {
// set compressed, base 64 encoded data and length of uncompressed data
CompressedParams.set(uCentralProtocol::COMPRESS_64, CompressedBase64Data);
CompressedParams.set(uCentralProtocol::COMPRESS_SZ, UncompressedDataLen);
ConfigParams = CompressedParams;
}
}
}
// AP_WS_Server()->SetPendingUUID(SerialNumber_, NewUUID);
RESTAPI_RPC::WaitForCommand(CMD_RPC, APCommands::Commands::configure, true,
Cmd, Params, *Request, *Response, timeout,
Cmd, ConfigParams, *Request, *Response, timeout,
nullptr, this, Logger_);
if(!Cmd.Executed) {
@@ -1548,4 +1574,123 @@ namespace OpenWifi {
Logger_);
}
// `fixedconfig` command is used set country propery on AP
// This handler uses `fixedconfig` command definitions
void RESTAPI_device_commandHandler::FixedConfig(
const std::string &CMD_UUID, uint64_t CMD_RPC, std::chrono::milliseconds timeout,
[[maybe_unused]] const GWObjects::DeviceRestrictions &Restrictions) {
poco_debug(Logger_, fmt::format("FIXEDCONFIG({},{}): TID={} user={} serial={}", CMD_UUID, CMD_RPC,
TransactionId_, Requester(), SerialNumber_));
// do not allow `fixedconfig` command for simulated devices
if(IsDeviceSimulated(SerialNumber_)) {
CallCanceled("FIXEDCONFIG", CMD_UUID, CMD_RPC, RESTAPI::Errors::SimulatedDeviceNotSupported);
return BadRequest(RESTAPI::Errors::SimulatedDeviceNotSupported);
}
// setup and validate fixedconfig object
GWObjects::FixedConfig fixed_config;
if(!fixed_config.from_json(ParsedBody_)) {
return BadRequest(RESTAPI::Errors::MissingOrInvalidParameters);
}
// setup command message
GWObjects::CommandDetails Cmd;
Cmd.SerialNumber = SerialNumber_;
Cmd.SubmittedBy = Requester();
Cmd.UUID = CMD_UUID;
Cmd.Command = uCentralProtocol::FIXEDCONFIG;
std::ostringstream os;
ParsedBody_->stringify(os);
Cmd.Details = os.str();
Cmd.RunAt = 0;
Cmd.ErrorCode = 0;
Cmd.WaitingForFile = 0;
// send fixedconfig command to device and return status
return RESTAPI_RPC::WaitForCommand(CMD_RPC, APCommands::Commands::fixedconfig, false, Cmd,
*ParsedBody_, *Request, *Response, timeout, nullptr, this,
Logger_);
}
void RESTAPI_device_commandHandler::CableDiagnostics(
const std::string &CMD_UUID, uint64_t CMD_RPC,
[[maybe_unused]] std::chrono::milliseconds timeout,
[[maybe_unused]] const GWObjects::DeviceRestrictions &Restrictions) {
if(UserInfo_.userinfo.userRole != SecurityObjects::ROOT &&
UserInfo_.userinfo.userRole != SecurityObjects::ADMIN) {
CallCanceled("CABLEDIAGNOSTICS", CMD_UUID, CMD_RPC, RESTAPI::Errors::ACCESS_DENIED);
return UnAuthorized(RESTAPI::Errors::ACCESS_DENIED);
}
poco_debug(Logger_, fmt::format("CABLEDIAGNOSTICS({},{}): TID={} user={} serial={}", CMD_UUID,
CMD_RPC, TransactionId_, Requester(), SerialNumber_));
if(IsDeviceSimulated(SerialNumber_)) {
CallCanceled("CABLEDIAGNOSTICS", CMD_UUID, CMD_RPC, RESTAPI::Errors::SimulatedDeviceNotSupported);
return BadRequest(RESTAPI::Errors::SimulatedDeviceNotSupported);
}
GWObjects::CableDiagnostics PR;
if(!PR.from_json(ParsedBody_)) {
return BadRequest(RESTAPI::Errors::MissingOrInvalidParameters);
}
GWObjects::CommandDetails Cmd;
Cmd.SerialNumber = SerialNumber_;
Cmd.SubmittedBy = Requester();
Cmd.UUID = CMD_UUID;
Cmd.Command = uCentralProtocol::CABLEDIAGNOSTICS;
std::ostringstream os;
ParsedBody_->stringify(os);
Cmd.Details = os.str();
Cmd.RunAt = PR.when;
Cmd.ErrorCode = 0;
Cmd.WaitingForFile = 0;
return RESTAPI_RPC::WaitForCommand(CMD_RPC, APCommands::Commands::cablediagnostics, false, Cmd,
*ParsedBody_, *Request, *Response, timeout, nullptr, this,
Logger_);
}
void RESTAPI_device_commandHandler::ReEnroll(
const std::string &CMD_UUID, uint64_t CMD_RPC,
[[maybe_unused]] std::chrono::milliseconds timeout,
[[maybe_unused]] const GWObjects::DeviceRestrictions &Restrictions) {
if(UserInfo_.userinfo.userRole != SecurityObjects::ROOT &&
UserInfo_.userinfo.userRole != SecurityObjects::ADMIN) {
CallCanceled("REENROLL", CMD_UUID, CMD_RPC, RESTAPI::Errors::ACCESS_DENIED);
return UnAuthorized(RESTAPI::Errors::ACCESS_DENIED);
}
poco_debug(Logger_, fmt::format("REENROLL({},{}): TID={} user={} serial={}", CMD_UUID,
CMD_RPC, TransactionId_, Requester(), SerialNumber_));
if(IsDeviceSimulated(SerialNumber_)) {
CallCanceled("REENROLL", CMD_UUID, CMD_RPC, RESTAPI::Errors::SimulatedDeviceNotSupported);
return BadRequest(RESTAPI::Errors::SimulatedDeviceNotSupported);
}
GWObjects::ReEnroll PR;
if(!PR.from_json(ParsedBody_)) {
return BadRequest(RESTAPI::Errors::MissingOrInvalidParameters);
}
GWObjects::CommandDetails Cmd;
Cmd.SerialNumber = SerialNumber_;
Cmd.SubmittedBy = Requester();
Cmd.UUID = CMD_UUID;
Cmd.Command = uCentralProtocol::REENROLL;
std::ostringstream os;
ParsedBody_->stringify(os);
Cmd.Details = os.str();
Cmd.RunAt = PR.when;
Cmd.ErrorCode = 0;
Cmd.WaitingForFile = 0;
return RESTAPI_RPC::WaitForCommand(CMD_RPC, APCommands::Commands::reenroll, false, Cmd,
*ParsedBody_, *Request, *Response, timeout, nullptr, this,
Logger_);
}
} // namespace OpenWifi

View File

@@ -70,6 +70,12 @@ namespace OpenWifi {
const GWObjects::DeviceRestrictions &R);
void PowerCycle(const std::string &UUID, uint64_t RPC, std::chrono::milliseconds timeout,
const GWObjects::DeviceRestrictions &R);
void FixedConfig(const std::string &UUID, uint64_t RPC, std::chrono::milliseconds timeout,
const GWObjects::DeviceRestrictions &R);
void CableDiagnostics(const std::string &UUID, uint64_t RPC, std::chrono::milliseconds timeout,
const GWObjects::DeviceRestrictions &R);
void ReEnroll(const std::string &UUID, uint64_t RPC, std::chrono::milliseconds timeout,
const GWObjects::DeviceRestrictions &R);
static auto PathName() {
return std::list<std::string>{"/api/v1/device/{serialNumber}/{command}"};

View File

@@ -127,7 +127,7 @@ namespace OpenWifi {
} else if (QB_.CountOnly) {
uint64_t Count = 0;
if (StorageService()->GetDeviceCount(Count, platform, includeProvisioned)) {
if (StorageService()->GetDeviceCount(Count, platform)) {
return ReturnCountOnly(Count);
}
} else if (serialOnly) {

View File

@@ -22,9 +22,15 @@ namespace OpenWifi {
std::string FileType;
std::string FileContent;
if (!StorageService()->GetAttachedFileContent(UUID, SerialNumber, FileContent, FileType) || FileContent.empty()) {
int WaitingForFile = 0;
if (!StorageService()->GetAttachedFileContent(UUID, SerialNumber, FileContent, FileType, WaitingForFile) && !WaitingForFile) {
return NotFound();
}
else if (WaitingForFile) {
// waiting for file to be uploaded, return Accepted
return Accepted();
}
if (FileType == "pcap") {
SendFileContent(FileContent, "application/vnd.tcpdump.pcap", UUID + ".pcap");
}

View File

@@ -23,8 +23,8 @@ namespace OpenWifi {
void RESTAPI_script_handler::DoDelete() {
std::string UUID = GetBinding("uuid", "");
if (!UserInfo_.userinfo.userPermissions[SecurityObjects::PM_SCRIPTS_GW][SecurityObjects::PT_DELETE]) {
return UnAuthorized(RESTAPI::Errors::ACCESS_DENIED);
if (UserInfo_.userinfo.userRole != SecurityObjects::ROOT) {
return BadRequest(RESTAPI::Errors::ACCESS_DENIED);
}
if (UUID.empty()) {
@@ -40,8 +40,8 @@ namespace OpenWifi {
void RESTAPI_script_handler::DoPost() {
std::string UUID = GetBinding("uuid", "");
if (!UserInfo_.userinfo.userPermissions[SecurityObjects::PM_SCRIPTS_GW][SecurityObjects::PT_CREATE]) {
return UnAuthorized(RESTAPI::Errors::ACCESS_DENIED);
if (UserInfo_.userinfo.userRole != SecurityObjects::ROOT) {
return BadRequest(RESTAPI::Errors::ACCESS_DENIED);
}
if (UUID.empty()) {
@@ -86,8 +86,8 @@ namespace OpenWifi {
void RESTAPI_script_handler::DoPut() {
std::string UUID = GetBinding("uuid", "");
if (!UserInfo_.userinfo.userPermissions[SecurityObjects::PM_SCRIPTS_GW][SecurityObjects::PT_UPDATE]) {
return UnAuthorized(RESTAPI::Errors::ACCESS_DENIED);
if (UserInfo_.userinfo.userRole != SecurityObjects::ROOT) {
return BadRequest(RESTAPI::Errors::ACCESS_DENIED);
}
if (UUID.empty()) {

View File

@@ -295,8 +295,9 @@ namespace OpenWifi::GWObjects {
field_to_json(Obj, "started", started);
field_to_json(Obj, "sessionId", sessionId);
field_to_json(Obj, "connectionCompletionTime", connectionCompletionTime);
field_to_json(Obj, "totalConnectionTime", started ? Utils::Now() - started : 0);
field_to_json(Obj, "totalConnectionTime", Utils::Now() - started);
field_to_json(Obj, "certificateExpiryDate", certificateExpiryDate);
field_to_json(Obj, "certificateIssuerName", certificateIssuerName);
field_to_json(Obj, "connectReason", connectReason);
field_to_json(Obj, "uptime", uptime);
field_to_json(Obj, "compatible", Compatible);
@@ -358,6 +359,7 @@ namespace OpenWifi::GWObjects {
field_from_json(Obj, "connectionCompletionTime", connectionCompletionTime);
field_from_json(Obj, "totalConnectionTime", totalConnectionTime);
field_from_json(Obj, "certificateExpiryDate", certificateExpiryDate);
field_from_json(Obj, "certificateIssuerName", certificateIssuerName);
field_from_json(Obj, "connectReason", connectReason);
field_from_json(Obj, "uptime", uptime);
field_from_json(Obj, "hasRADIUSSessions", hasRADIUSSessions );
@@ -799,4 +801,34 @@ namespace OpenWifi::GWObjects {
return false;
}
bool FixedConfig::from_json(const Poco::JSON::Object::Ptr &Obj) {
try {
field_from_json(Obj, "serial", serialNumber);
field_from_json(Obj, "country", country);
return true;
} catch (const Poco::Exception &E) {
}
return false;
}
bool CableDiagnostics::from_json(const Poco::JSON::Object::Ptr &Obj) {
try {
field_from_json(Obj, "serial", serialNumber);
field_from_json(Obj, "when", when);
field_from_json(Obj, "ports", ports);
return true;
} catch (const Poco::Exception &E) {
}
return false;
}
bool ReEnroll::from_json(const Poco::JSON::Object::Ptr &Obj) {
try {
field_from_json(Obj, "serial", serialNumber);
field_from_json(Obj, "when", when);
return true;
} catch (const Poco::Exception &E) {
}
return false;
}
} // namespace OpenWifi::GWObjects

View File

@@ -42,6 +42,7 @@ namespace OpenWifi::GWObjects {
uint64_t sessionId = 0;
double connectionCompletionTime = 0.0;
std::uint64_t certificateExpiryDate = 0;
std::string certificateIssuerName;
std::uint64_t hasRADIUSSessions = 0;
bool hasGPS = false;
std::uint64_t sanity=0;
@@ -532,6 +533,25 @@ namespace OpenWifi::GWObjects {
std::uint64_t when;
std::vector<PowerCyclePort> ports;
bool from_json(const Poco::JSON::Object::Ptr &Obj);
};
struct FixedConfig {
std::string serialNumber;
std::string country;
bool from_json(const Poco::JSON::Object::Ptr &Obj);
};
struct CableDiagnostics {
std::string serialNumber;
std::uint64_t when;
std::vector<std::string> ports;
bool from_json(const Poco::JSON::Object::Ptr &Obj);
};
struct ReEnroll {
std::string serialNumber;
std::uint64_t when;
bool from_json(const Poco::JSON::Object::Ptr &Obj);
};
} // namespace OpenWifi::GWObjects

View File

@@ -12,8 +12,6 @@
#include "RESTAPI_SecurityObjects.h"
#include "framework/RESTAPI_utils.h"
#include <stdlib.h>
using OpenWifi::RESTAPI_utils::field_from_json;
using OpenWifi::RESTAPI_utils::field_to_json;
@@ -284,7 +282,6 @@ namespace OpenWifi::SecurityObjects {
field_to_json(Obj, "oauthUserInfo", oauthUserInfo);
field_to_json(Obj, "modified", modified);
field_to_json(Obj, "signingUp", signingUp);
Obj.set("userPermissions", permissions_to_json(userPermissions));
};
bool UserInfo::from_json(const Poco::JSON::Object::Ptr &Obj) {
@@ -321,7 +318,6 @@ namespace OpenWifi::SecurityObjects {
field_from_json(Obj, "oauthUserInfo", oauthUserInfo);
field_from_json(Obj, "modified", modified);
field_from_json(Obj, "signingUp", signingUp);
userPermissions = permissions_from_json(Obj->getObject("userPermissions"));
return true;
} catch (const Poco::Exception &E) {
std::cout << "Cannot parse: UserInfo" << std::endl;
@@ -741,218 +737,4 @@ namespace OpenWifi::SecurityObjects {
return false;
}
PERMISSION_TYPE PermTypeFromString(const std::string &U) {
if (!Poco::icompare(U, "create"))
return PT_CREATE;
else if (!Poco::icompare(U, "update"))
return PT_UPDATE;
else if (!Poco::icompare(U, "delete"))
return PT_DELETE;
else if (!Poco::icompare(U, "readonly"))
return PT_READ_ONLY;
return PT_UNKNOWN;
}
std::string PermTypeToString(PERMISSION_TYPE U) {
switch (U) {
case PT_CREATE:
return "create";
case PT_UPDATE:
return "update";
case PT_DELETE:
return "delete";
case PT_READ_ONLY:
return "readonly";
case PT_UNKNOWN:
default:
return "unknown";
}
}
PERMISSION_MODEL PermModelFromString(const std::string &U) {
if (!Poco::icompare(U, "permissions"))
return PM_PERMISSIONS;
else if (!Poco::icompare(U, "venues"))
return PM_VENUES_PROV;
else if (!Poco::icompare(U, "venues_list"))
return PM_VENUES_LIST_PROV;
else if (!Poco::icompare(U, "entities"))
return PM_ENTITIES_PROV;
else if (!Poco::icompare(U, "entities_list"))
return PM_ENTITIES_LIST_PROV;
else if (!Poco::icompare(U, "inventory"))
return PM_INVENTORY_PROV;
else if (!Poco::icompare(U, "inventory_list"))
return PM_INVENTORY_LIST_PROV;
else if (!Poco::icompare(U, "managementpolicy"))
return PM_MANAGEMENTPOLICY_PROV;
else if (!Poco::icompare(U, "managementpolicy_list"))
return PM_MANAGEMENTPOLICY_LIST_PROV;
else if (!Poco::icompare(U, "managementrole"))
return PM_MANAGEMENTROLE_PROV;
else if (!Poco::icompare(U, "managementrole_list"))
return PM_MANAGEMENTROLE_LIST_PROV;
//GW
else if (!Poco::icompare(U, "scripts"))
return PM_SCRIPTS_GW;
else if (!Poco::icompare(U, "configure"))
return PM_DEVICE_CONFIGURE_GW;
else if (!Poco::icompare(U, "upgrade"))
return PM_DEVICE_UPGRADE_GW;
else if (!Poco::icompare(U, "factoryreset"))
return PM_DEVICE_FACTORY_GW;
else if (!Poco::icompare(U, "leds"))
return PM_DEVICE_LEDS_GW;
else if (!Poco::icompare(U, "trace"))
return PM_DEVICE_TRACE_GW;
else if (!Poco::icompare(U, "request"))
return PM_DEVICE_REQUEST_GW;
else if (!Poco::icompare(U, "wifiscan"))
return PM_DEVICE_WIFISCAN_GW;
else if (!Poco::icompare(U, "eventqueue"))
return PM_DEVICE_EVENTQUEUE_GW;
else if (!Poco::icompare(U, "telemetry"))
return PM_DEVICE_TELEMETRY_GW;
else if (!Poco::icompare(U, "ping"))
return PM_DEVICE_PING_GW;
else if (!Poco::icompare(U, "ap_script"))
return PM_DEVICE_SCRIPT_GW;
else if (!Poco::icompare(U, "rrm"))
return PM_DEVICE_RRM_GW;
else if (!Poco::icompare(U, "transfer"))
return PM_DEVICE_TRANSFER_GW;
else if (!Poco::icompare(U, "certupdate"))
return PM_DEVICE_CERTUPDATE_GW;
else if (!Poco::icompare(U, "powercycle"))
return PM_DEVICE_POWERCYCLE_GW;
else if (!Poco::icompare(U, "ap_logs"))
return PM_DEVICE_LOGS_GW;
else if (!Poco::icompare(U, "healthchecks"))
return PM_DEVICE_HEALTHCHECKS_GW;
else if (!Poco::icompare(U, "ap_capabilities"))
return PM_DEVICE_CAPABILITIES_GW;
else if (!Poco::icompare(U, "ap_statistics"))
return PM_DEVICE_STATISTICS_GW;
else if (!Poco::icompare(U, "ap_status"))
return PM_DEVICE_STATUS_GW;
else if (!Poco::icompare(U, "ap_rtty"))
return PM_DEVICE_RTTY_GW;
return PM_UNKNOWN;
}
std::string PermModelToString(PERMISSION_MODEL U) {
switch (U) {
case PM_PERMISSIONS:
return "permissions";
case PM_VENUES_PROV:
return "venues";
case PM_VENUES_LIST_PROV:
return "venues_list";
case PM_ENTITIES_PROV:
return "entities";
case PM_ENTITIES_LIST_PROV:
return "entities_list";
case PM_INVENTORY_PROV:
return "inventory";
case PM_INVENTORY_LIST_PROV:
return "inventory_list";
case PM_MANAGEMENTPOLICY_PROV:
return "managementpolicy";
case PM_MANAGEMENTPOLICY_LIST_PROV:
return "managementpolicy_list";
case PM_MANAGEMENTROLE_PROV:
return "managementrole";
case PM_MANAGEMENTROLE_LIST_PROV:
return "managementrole_list";
//Gateway
case PM_SCRIPTS_GW:
return "scripts";
case PM_DEVICE_CONFIGURE_GW:
return "configure";
case PM_DEVICE_UPGRADE_GW:
return "upgrade";
case PM_DEVICE_FACTORY_GW:
return "factoryreset";
case PM_DEVICE_LEDS_GW:
return "leds";
case PM_DEVICE_TRACE_GW:
return "trace";
case PM_DEVICE_REQUEST_GW:
return "request";
case PM_DEVICE_WIFISCAN_GW:
return "wifiscan";
case PM_DEVICE_EVENTQUEUE_GW:
return "eventqueue";
case PM_DEVICE_TELEMETRY_GW:
return "telemetry";
case PM_DEVICE_PING_GW:
return "ping";
case PM_DEVICE_SCRIPT_GW:
return "ap_script";
case PM_DEVICE_RRM_GW:
return "rrm";
case PM_DEVICE_TRANSFER_GW:
return "transfer";
case PM_DEVICE_CERTUPDATE_GW:
return "certupdate";
case PM_DEVICE_POWERCYCLE_GW:
return "powercycle";
case PM_DEVICE_LOGS_GW:
return "ap_logs";
case PM_DEVICE_HEALTHCHECKS_GW:
return "healthchecks";
case PM_DEVICE_CAPABILITIES_GW:
return "ap_capabilities";
case PM_DEVICE_STATISTICS_GW:
return "ap_statistics";
case PM_DEVICE_STATUS_GW:
return "ap_status";
case PM_DEVICE_RTTY_GW:
return "ap_rtty";
case PM_UNKNOWN:
default:
return "unknown";
}
}
/**
* Convert PermissionMap into a JSON object and return it
*/
Poco::JSON::Object permissions_to_json(const PermissionMap &Map) {
Poco::JSON::Object MapObj;
for (auto &[Model, Permissions] : Map) {
Poco::JSON::Object ModelObject;
for (auto &[Permission, Allowed] : Permissions) {
ModelObject.set(PermTypeToString(Permission), Allowed);
}
MapObj.set(PermModelToString(Model), ModelObject);
}
return MapObj;
}
/**
* Convert JSON object into a PermissionMap and return it
*/
PermissionMap permissions_from_json(const Poco::JSON::Object::Ptr &Obj) {
PermissionMap permissions;
if (Obj == nullptr) {
return permissions;
}
Poco::JSON::Object::ConstIterator it1;
for(it1 = Obj->begin(); it1 != Obj->end(); it1++) {
std::string model = it1->first;
Poco::JSON::Object::Ptr modelObj = it1->second.extract<Poco::JSON::Object::Ptr>();
Poco::JSON::Object::ConstIterator it2;
for(it2 = modelObj->begin(); it2 != modelObj->end(); it2++) {
std::string permission = it2->first;
bool allowed = it2->second;
permissions[PermModelFromString(model)]
[PermTypeFromString(permission)] = allowed;
}
}
return permissions;
}
} // namespace OpenWifi::SecurityObjects

View File

@@ -11,15 +11,10 @@
#include "Poco/Data/LOB.h"
#include "Poco/Data/LOBStream.h"
#include "Poco/JSON/Object.h"
#include "Poco/Net/HTTPRequest.h"
#include "framework/OpenWifiTypes.h"
#include "framework/utils.h"
#include <string>
#include <type_traits>
#include <iostream>
#include <fstream>
#include <map>
#include <set>
namespace OpenWifi {
uint64_t Now();
@@ -60,10 +55,6 @@ namespace OpenWifi {
bool from_json(const Poco::JSON::Object::Ptr &Obj);
};
// example entry {"/api/v1/device", {Poco::Net::HTTPRequest::HTTP_POST, Poco::Net::HTTPRequest::HTTP_PUT, Poco::Net::HTTPRequest::HTTP_DELETE}}
const std::map<std::string, std::set<std::string>> API_WHITELIST = {
};
enum USER_ROLE {
UNKNOWN,
ROOT,
@@ -134,72 +125,6 @@ namespace OpenWifi {
bool from_json(const Poco::JSON::Object::Ptr &Obj);
};
// Represents particular permissions, i.e. what are you doing do the model
enum PERMISSION_TYPE {
PT_CREATE,
PT_DELETE,
PT_UPDATE,
PT_READ_ONLY,
PT_UNKNOWN
};
PERMISSION_TYPE PermTypeFromString(const std::string &U);
std::string PermTypeToString(PERMISSION_TYPE U);
// Represents a model that can be operated on
enum PERMISSION_MODEL {
//Security
PM_PERMISSIONS,
//Provisioning
PM_VENUES_PROV,
PM_VENUES_LIST_PROV,
PM_ENTITIES_PROV,
PM_ENTITIES_LIST_PROV,
PM_INVENTORY_PROV,
PM_INVENTORY_LIST_PROV,
PM_MANAGEMENTPOLICY_PROV,
PM_MANAGEMENTPOLICY_LIST_PROV,
PM_MANAGEMENTROLE_PROV,
PM_MANAGEMENTROLE_LIST_PROV,
//Gateway
PM_DEVICE_CONFIGURE_GW,
PM_DEVICE_UPGRADE_GW,
PM_DEVICE_REBOOT_GW,
PM_DEVICE_FACTORY_GW,
PM_DEVICE_LEDS_GW,
PM_DEVICE_TRACE_GW,
PM_DEVICE_REQUEST_GW,
PM_DEVICE_WIFISCAN_GW,
PM_DEVICE_EVENTQUEUE_GW,
PM_DEVICE_TELEMETRY_GW,
PM_DEVICE_PING_GW,
PM_DEVICE_SCRIPT_GW,
PM_DEVICE_RRM_GW,
PM_DEVICE_TRANSFER_GW,
PM_DEVICE_CERTUPDATE_GW,
PM_DEVICE_POWERCYCLE_GW,
PM_DEVICE_LOGS_GW,
PM_DEVICE_HEALTHCHECKS_GW,
PM_DEVICE_CAPABILITIES_GW,
PM_DEVICE_STATISTICS_GW,
PM_DEVICE_STATUS_GW,
PM_DEVICE_RTTY_GW,
PM_SCRIPTS_GW,
PM_UNKNOWN
};
PERMISSION_MODEL PermModelFromString(const std::string &U);
std::string PermModelToString(PERMISSION_MODEL U);
// Map a permission (e.g. create, delete) to true/false
typedef std::map<PERMISSION_TYPE, bool> ModelPermissionMap;
// Map a model (e.g. venues, devices) to permissions
typedef std::map<PERMISSION_MODEL, ModelPermissionMap> PermissionMap;
Poco::JSON::Object permissions_to_json(const SecurityObjects::PermissionMap &Map);
PermissionMap permissions_from_json(const Poco::JSON::Object::Ptr &Obj);
struct UserInfo {
std::string id;
std::string name;
@@ -224,7 +149,6 @@ namespace OpenWifi {
bool suspended = false;
bool blackListed = false;
USER_ROLE userRole;
PermissionMap userPermissions;
UserLoginLoginExtensions userTypeProprietaryInfo;
std::string securityPolicy;
uint64_t securityPolicyChange = 0;

View File

@@ -162,7 +162,7 @@ namespace OpenWifi {
bool UpdateDevice(Poco::Data::Session &Sess, GWObjects::Device &NewDeviceDetails);
bool DeviceExists(std::string &SerialNumber);
bool SetConnectInfo(std::string &SerialNumber, std::string &Firmware);
bool GetDeviceCount(uint64_t &Count, const std::string &platform = "", bool includeProvisioned = true);
bool GetDeviceCount(uint64_t &Count, const std::string &platform = "");
bool GetDeviceSerialNumbers(uint64_t From, uint64_t HowMany,
std::vector<std::string> &SerialNumbers,
const std::string &orderBy = "",
@@ -243,7 +243,7 @@ namespace OpenWifi {
const std::string &Type);
bool CancelWaitFile(std::string &UUID, std::string &ErrorText);
bool GetAttachedFileContent(std::string &UUID, const std::string &SerialNumber,
std::string &FileContent, std::string &Type);
std::string &FileContent, std::string &Type, int& WaitingForFile);
bool RemoveAttachedFile(std::string &UUID);
bool SetCommandResult(std::string &UUID, std::string &Result);
bool GetNewestCommands(std::string &SerialNumber, uint64_t HowMany,

View File

@@ -129,26 +129,4 @@ namespace OpenWifi {
return RetrieveApiKeyInformation(SessionToken, UInfo, TID, Expired, Contacted, Suspended);
}
/**
* Given a role, remove the cached user info for any user with that role
*/
void AuthClient::EmptyCacheForRole(const std::string &role) {
SecurityObjects::USER_ROLE roleEnum = SecurityObjects::UserTypeFromString(role);
Poco::JSON::Object::ConstIterator it;
std::set<std::string> tokens = Cache_.getAllKeys();
for(const std::string &token : tokens) {
auto UInfo = Cache_.get(token);
if (UInfo->userinfo.userRole == roleEnum) {
Cache_.remove(token);
}
}
tokens = ApiKeyCache_.getAllKeys();
for(const std::string &token : tokens) {
auto UInfo = ApiKeyCache_.get(token);
if (UInfo->UserInfo.userinfo.userRole == roleEnum) {
ApiKeyCache_.remove(token);
}
}
}
} // namespace OpenWifi

View File

@@ -61,8 +61,6 @@ namespace OpenWifi {
SecurityObjects::UserInfoAndPolicy &UInfo, std::uint64_t TID,
bool &Expired, bool &Contacted, bool &Suspended);
void EmptyCacheForRole(const std::string &role);
private:
Poco::ExpireLRUCache<std::string, OpenWifi::SecurityObjects::UserInfoAndPolicy> Cache_{
512, 1200000};

View File

@@ -376,18 +376,21 @@ static std::string DefaultAPSchema = R"foo(
"properties": {
"port-mirror": {
"description": "Enable mirror of traffic from multiple minotor ports to a single analysis port.",
"type": "object",
"properties": {
"monitor-ports": {
"description": "The list of ports that we want to mirror.",
"type": "array",
"items": {
"type": "array",
"items": {
"type": "object",
"properties": {
"monitor-ports": {
"description": "The list of ports that we want to mirror.",
"type": "array",
"items": {
"type": "string"
}
},
"analysis-port": {
"description": "The port that mirror'ed packets should be sent to.",
"type": "string"
}
},
"analysis-port": {
"description": "The port that mirror'ed packets should be sent to.",
"type": "string"
}
}
},
@@ -2411,11 +2414,18 @@ static std::string DefaultAPSchema = R"foo(
"$ref": "#/$defs/interface.ssid.encryption"
},
"multi-psk": {
"type": "array",
"items": {
"$ref": "#/$defs/interface.ssid.multi-psk"
}
},
"anyOf": [
{
"type": "array",
"items": {
"$ref": "#/$defs/interface.ssid.multi-psk"
}
},
{
"type": "boolean"
}
]
},
"rrm": {
"$ref": "#/$defs/interface.ssid.rrm"
},
@@ -3942,8 +3952,10 @@ static std::string DefaultAPSchema = R"foo(
"inactive-deauth",
"key-mismatch",
"beacon-report",
"radar-detected"
]
"radar-detected",
"ft-finish",
"sta-authorized"
]
}
}
}
@@ -4645,16 +4657,22 @@ static std::string DefaultSWITCHSchema = R"foo(
"type": "object",
"properties": {
"port-mirror": {
"type": "object",
"properties": {
"monitor-ports": {
"type": "array",
"items": {
"description": "Enable mirror of traffic from multiple minotor ports to a single analysis port.",
"type": "array",
"items": {
"type": "object",
"properties": {
"monitor-ports": {
"description": "The list of ports that we want to mirror.",
"type": "array",
"items": {
"type": "string"
}
},
"analysis-port": {
"description": "The port that mirror'ed packets should be sent to.",
"type": "string"
}
},
"analysis-port": {
"type": "string"
}
}
},
@@ -6603,10 +6621,17 @@ static std::string DefaultSWITCHSchema = R"foo(
"$ref": "#/$defs/interface.ssid.encryption"
},
"multi-psk": {
"type": "array",
"items": {
"$ref": "#/$defs/interface.ssid.multi-psk"
}
"anyOf": [
{
"type": "array",
"items": {
"$ref": "#/$defs/interface.ssid.multi-psk"
}
},
{
"type": "boolean"
}
]
},
"rrm": {
"$ref": "#/$defs/interface.ssid.rrm"
@@ -7897,7 +7922,9 @@ static std::string DefaultSWITCHSchema = R"foo(
"inactive-deauth",
"key-mismatch",
"beacon-report",
"radar-detected"
"radar-detected",
"ft-finish",
"sta-authorized"
]
}
}

View File

@@ -118,6 +118,10 @@ namespace OpenWifi {
Producer.poll((std::chrono::milliseconds) 0);
}
}
if (Queue_.size() == 0) {
// message queue is empty, flush all previously sent messages
Producer.flush();
}
} catch (const cppkafka::HandleException &E) {
poco_warning(Logger_,
fmt::format("Caught a Kafka exception (producer): {}", E.what()));
@@ -126,10 +130,6 @@ namespace OpenWifi {
} catch (...) {
poco_error(Logger_, "std::exception");
}
if (Queue_.size() == 0) {
// message queue is empty, flush all previously sent messages
Producer.flush();
}
Note = Queue_.waitDequeueNotification();
}
Producer.flush();

View File

@@ -27,7 +27,6 @@ namespace OpenWifi::KafkaTopics {
inline const char * EVENT_LEAVE = "leave";
inline const char * EVENT_KEEP_ALIVE = "keep-alive";
inline const char * EVENT_REMOVE_TOKEN = "remove-token";
inline const char * EVENT_PERMISSIONS_UPDATE = "permissions-update";
namespace Fields {
inline const char * EVENT = "event";
@@ -38,7 +37,6 @@ namespace OpenWifi::KafkaTopics {
inline const char * KEY = "key";
inline const char * VRSN = "version";
inline const char * TOKEN = "token";
inline const char * ROLE = "role";
} // namespace Fields
} // namespace ServiceEvents
} // namespace OpenWifi::KafkaTopics

View File

@@ -155,16 +155,6 @@ namespace OpenWifi {
BusLogger,
fmt::format("KAFKA-MSG: invalid event '{}', missing token", Event));
}
} else if (Event == KafkaTopics::ServiceEvents::EVENT_PERMISSIONS_UPDATE) {
if (Object->has(KafkaTopics::ServiceEvents::Fields::ROLE)) {
// Permissions of this role have updated, cached user info is now invalid
AuthClient()->EmptyCacheForRole(
Object->get(KafkaTopics::ServiceEvents::Fields::ROLE).toString());
} else {
poco_information(
logger(),
fmt::format("KAFKA-MSG: invalid event '{}', missing role", Event));
}
} else {
poco_information(BusLogger,
fmt::format("Unknown Event: {} Source: {}", Event, ID));

View File

@@ -60,52 +60,9 @@ namespace OpenWifi {
AlwaysAuthorize_(AlwaysAuthorize), Server_(Server), MyRates_(Profile),
TransactionId_(TransactionId) {}
inline int nthOccurrence(const std::string& str, const std::string& findMe, int nth) {
/*
Helper function to get the index of the nth occurence of string findMe in string str.
if there are not n occurrences of findMe in str, returns -1.
*/
size_t pos = 0;
int count = 0;
while(count != nth)
{
pos+=1;
pos = str.find(findMe, pos);
if (pos == std::string::npos)
return -1;
count++;
}
return pos;
}
inline bool RoleIsAuthorized([[maybe_unused]] const std::string &Path,
[[maybe_unused]] const std::string &Method,
[[maybe_unused]] std::string &Reason) {
// If user role is admin or root, authorized is true
if (UserInfo_.userinfo.userRole == SecurityObjects::USER_ROLE::ADMIN || UserInfo_.userinfo.userRole == SecurityObjects::USER_ROLE::ROOT) {
return true;
}
// We just want the /api/v1/x part of the path so we need to account for
// extra path variables as well as query variables.
std::string pathstubtmp = Path.substr(0, nthOccurrence(Path, "/", 3));
std::string pathstub = pathstubtmp.substr(0, nthOccurrence(pathstubtmp, "?", 1));
// Next check the pathstub against the whitelist
if (SecurityObjects::API_WHITELIST.find(pathstub) != SecurityObjects::API_WHITELIST.end()) {
std::set<std::string> allowed_methods = SecurityObjects::API_WHITELIST.at(pathstub);
// The API stub is in the whitelist, but we also need to check that this method is whitelisted for this stub.
if (allowed_methods.find(Method) != allowed_methods.end()) {
return true;
}
}
// At this point, the user is not root/admin and the API + method is not whitelisted, so we disallow any method that is not a GET.
if (Method != Poco::Net::HTTPRequest::HTTP_GET) {
return false;
}
return true;
}
@@ -474,6 +431,11 @@ namespace OpenWifi {
}
}
inline void Accepted() {
PrepareResponse(Poco::Net::HTTPResponse::HTTP_ACCEPTED);
Response->send();
}
inline void SendCompressedTarFile(const std::string &FileName, const std::string &Content) {
Response->setStatus(Poco::Net::HTTPResponse::HTTPStatus::HTTP_OK);
SetCommonHeaders();

View File

@@ -68,6 +68,16 @@ namespace OpenWifi {
Context->addCertificateAuthority(Issuing);
}
if (!client_cas_.empty()) {
// add certificates specified in clientcas
std::vector<Poco::Crypto::X509Certificate> Certs =
Poco::Net::X509Certificate::readPEM(client_cas_);
for (const auto &cert : Certs) {
Context->addChainCertificate(cert);
Context->addCertificateAuthority(cert);
}
}
Poco::Crypto::RSAKey Key("", key_file_, key_file_password_);
Context->usePrivateKey(Key);

View File

@@ -45,6 +45,7 @@ namespace OpenWifi {
[[nodiscard]] inline auto KeyFile() const { return key_file_; };
[[nodiscard]] inline auto CertFile() const { return cert_file_; };
[[nodiscard]] inline auto RootCA() const { return root_ca_; };
[[nodiscard]] inline auto ClientCas() const { return client_cas_; };
[[nodiscard]] inline auto KeyFilePassword() const { return key_file_password_; };
[[nodiscard]] inline auto IssuerCertFile() const { return issuer_cert_file_; };
[[nodiscard]] inline auto Name() const { return name_; };

View File

@@ -580,6 +580,10 @@ namespace OpenWifi::RESTAPI::Protocol {
static const char *INTERVAL = "interval";
static const char *UI = "UI";
static const char *BANDWIDTH = "bandwidth";
static const char *FIXEDCONFIG = "fixedconfig";
static const char *CABLEDIAGNOSTICS = "cable-diagnostics";
static const char *REENROLL = "reenroll";
} // namespace OpenWifi::RESTAPI::Protocol
namespace OpenWifi::uCentralProtocol {
@@ -608,6 +612,7 @@ namespace OpenWifi::uCentralProtocol {
static const char *CFGPENDING = "cfgpending";
static const char *RECOVERY = "recovery";
static const char *COMPRESS_64 = "compress_64";
static const char *COMPRESS_SZ = "compress_sz";
static const char *CAPABILITIES = "capabilities";
static const char *REQUEST_UUID = "request_uuid";
static const char *SANITY = "sanity";
@@ -692,6 +697,11 @@ namespace OpenWifi::uCentralProtocol {
static const char *RRM = "rrm";
static const char *ACTIONS = "actions";
static const char *FIXEDCONFIG = "fixedconfig";
static const char *CABLEDIAGNOSTICS = "cable-diagnostics";
static const char *REENROLL = "reenroll";
} // namespace OpenWifi::uCentralProtocol
namespace OpenWifi::uCentralProtocol::Events {
@@ -788,6 +798,9 @@ namespace OpenWifi::APCommands {
certupdate,
transfer,
powercycle,
fixedconfig,
cablediagnostics,
reenroll,
unknown
};
@@ -802,7 +815,9 @@ namespace OpenWifi::APCommands {
RESTAPI::Protocol::EVENTQUEUE, RESTAPI::Protocol::TELEMETRY,
RESTAPI::Protocol::PING, RESTAPI::Protocol::SCRIPT,
RESTAPI::Protocol::RRM, RESTAPI::Protocol::CERTUPDATE,
RESTAPI::Protocol::TRANSFER, RESTAPI::Protocol::POWERCYCLE
RESTAPI::Protocol::TRANSFER, RESTAPI::Protocol::POWERCYCLE,
RESTAPI::Protocol::FIXEDCONFIG, RESTAPI::Protocol::CABLEDIAGNOSTICS,
RESTAPI::Protocol::REENROLL
};
inline const char *to_string(Commands Cmd) { return uCentralAPCommands[(uint8_t)Cmd]; }

View File

@@ -590,6 +590,26 @@ namespace OpenWifi::Utils {
return false;
}
//
// Compress given data using utility function and encode it in base64 format.
//
bool CompressAndEncodeBase64(const std::string& UnCompressedData, std::string& CompressedBase64Data) {
unsigned long CompressedDataSize = UnCompressedData.size();
std::vector<Bytef> CompressedData(CompressedDataSize);
auto status = compress(&CompressedData[0], &CompressedDataSize,
(Bytef*) UnCompressedData.c_str(), UnCompressedData.size());
if (status == Z_OK) {
CompressedBase64Data = OpenWifi::Utils::base64encode(&CompressedData[0], CompressedDataSize);
}
else {
// failed to compress data
return false;
}
return true;
}
bool IsAlphaNumeric(const std::string &s) {
return std::all_of(s.begin(), s.end(), [](char c) -> bool { return isalnum(c); });
}

View File

@@ -151,6 +151,8 @@ namespace OpenWifi::Utils {
bool ExtractBase64CompressedData(const std::string &CompressedData,
std::string &UnCompressedData, uint64_t compress_sz);
bool CompressAndEncodeBase64(const std::string& UnCompressedData, std::string& CompressedData);
inline bool match(const char* first, const char* second)
{
// If we reach at the end of both strings, we are done

View File

@@ -14,6 +14,7 @@
#include "nlohmann/json.hpp"
#include "Poco/NObserver.h"
#include <Poco/Net/Context.h>
#include "Poco/Net/SocketNotification.h"
#include "Poco/Net/NetException.h"
#include "Poco/Net/WebSocketImpl.h"
@@ -71,6 +72,7 @@ namespace OpenWifi {
const auto &RootCas =
MicroServiceConfigPath("ucentral.websocket.host.0.rootca", "");
const auto &Cas = MicroServiceConfigPath("ucentral.websocket.host.0.cas", "");
const auto &ClientCasFile = MicroServiceConfigPath("ucentral.websocket.host.0.clientcas", "");
Poco::Net::Context::Params P;
@@ -86,6 +88,7 @@ namespace OpenWifi {
Poco::Crypto::X509Certificate Cert(CertFileName);
Poco::Crypto::X509Certificate Root(RootCaFileName);
Poco::Crypto::X509Certificate Issuing(IssuerFileName);
std::vector<Poco::Crypto::X509Certificate> ClientCasCerts;
Poco::Crypto::RSAKey Key("", KeyFileName, KeyPassword);
DeviceSecureContext->useCertificate(Cert);
@@ -93,7 +96,11 @@ namespace OpenWifi {
DeviceSecureContext->addCertificateAuthority(Root);
DeviceSecureContext->addChainCertificate(Issuing);
DeviceSecureContext->addCertificateAuthority(Issuing);
DeviceSecureContext->addCertificateAuthority(Root);
ClientCasCerts = Poco::Net::X509Certificate::readPEM(ClientCasFile);
for (const auto &cert : ClientCasCerts) {
DeviceSecureContext->addChainCertificate(cert);
DeviceSecureContext->addCertificateAuthority(cert);
}
DeviceSecureContext->enableSessionCache(true);
DeviceSecureContext->setSessionCacheSize(0);
DeviceSecureContext->setSessionTimeout(120);
@@ -1117,4 +1124,4 @@ namespace OpenWifi {
RTTYS_EndPoint::~RTTYS_EndPoint() {
}
} // namespace OpenWifi
} // namespace OpenWifi

View File

@@ -644,21 +644,7 @@ namespace OpenWifi {
uint64_t Size = FileContent.str().size();
Poco::Data::Session Sess = Pool_->get();
Sess.begin();
Poco::Data::Statement Statement(Sess);
std::string StatementStr;
// Get the existing command
StatementStr =
"UPDATE CommandList SET WaitingForFile=?, AttachDate=?, AttachSize=? WHERE UUID=?";
Statement << ConvertParams(StatementStr), Poco::Data::Keywords::use(WaitForFile),
Poco::Data::Keywords::use(Now), Poco::Data::Keywords::use(Size),
Poco::Data::Keywords::use(UUID);
Statement.execute();
Sess.commit();
if (Size < FileUploader()->MaxSize()) {
Poco::Data::BLOB TheBlob;
@@ -680,7 +666,20 @@ namespace OpenWifi {
} else {
poco_warning(Logger(), fmt::format("File {} is too large.", UUID));
}
// update CommandList here to ensure that file us uploaded
Sess.begin();
Poco::Data::Statement Statement(Sess);
std::string StatementStr;
StatementStr =
"UPDATE CommandList SET WaitingForFile=?, AttachDate=?, AttachSize=? WHERE UUID=?";
Statement << ConvertParams(StatementStr), Poco::Data::Keywords::use(WaitForFile),
Poco::Data::Keywords::use(Now), Poco::Data::Keywords::use(Size),
Poco::Data::Keywords::use(UUID);
Statement.execute();
Sess.commit();
return true;
} catch (const Poco::Exception &E) {
Logger().log(E);
@@ -689,7 +688,7 @@ namespace OpenWifi {
}
bool Storage::GetAttachedFileContent(std::string &UUID, const std::string &SerialNumber,
std::string &FileContent, std::string &Type) {
std::string &FileContent, std::string &Type, int &WaitingForFile) {
try {
Poco::Data::BLOB L;
/*
@@ -702,10 +701,10 @@ namespace OpenWifi {
Poco::Data::Statement Select1(Sess);
std::string TmpSerialNumber;
std::string st1{"SELECT SerialNumber, Command FROM CommandList WHERE UUID=?"};
std::string st1{"SELECT SerialNumber, Command , WaitingForFile FROM CommandList WHERE UUID=?"};
std::string Command;
Select1 << ConvertParams(st1), Poco::Data::Keywords::into(TmpSerialNumber),
Poco::Data::Keywords::into(Command), Poco::Data::Keywords::use(UUID);
Poco::Data::Keywords::into(Command), Poco::Data::Keywords::into(WaitingForFile), Poco::Data::Keywords::use(UUID);
Select1.execute();
if (TmpSerialNumber != SerialNumber) {
@@ -825,4 +824,4 @@ namespace OpenWifi {
return false;
}
} // namespace OpenWifi
} // namespace OpenWifi

View File

@@ -172,27 +172,18 @@ namespace OpenWifi {
R.set<30>(D.connectReason);
}
bool Storage::GetDeviceCount(uint64_t &Count, const std::string &platform, bool includeProvisioned) {
bool Storage::GetDeviceCount(uint64_t &Count, const std::string &platform) {
try {
Poco::Data::Session Sess = Pool_->get();
Poco::Data::Statement Select(Sess);
std::string st;
std::string whereClause = "";
if(!platform.empty()) {
if (includeProvisioned == false) {
whereClause = fmt::format("WHERE entity='' and venue='' and DeviceType='" + platform + "'");
} else {
whereClause = fmt::format("WHERE DeviceType='" + platform + "'");
}
std::string st{"SELECT COUNT(*) FROM Devices WHERE DeviceType='" + platform + "'"};
Select << st, Poco::Data::Keywords::into(Count);
} else {
if (includeProvisioned == false) {
whereClause = fmt::format("WHERE entity='' and venue=''");
}
std::string st{"SELECT COUNT(*) FROM Devices"};
Select << st, Poco::Data::Keywords::into(Count);
}
st = fmt::format("SELECT COUNT(*) FROM Devices {}", whereClause);
Select << st, Poco::Data::Keywords::into(Count);
Select.execute();
return true;
} catch (const Poco::Exception &E) {

View File

@@ -1 +0,0 @@
3.0.6