From 74ff074136ded8be87ba0d7c8909eab40b1746a8 Mon Sep 17 00:00:00 2001 From: Mirko Van Colen Date: Thu, 11 Jul 2024 13:00:26 +0200 Subject: [PATCH 01/21] typos --- docs/_data/help.yaml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/_data/help.yaml b/docs/_data/help.yaml index bfbb5b23..332982c6 100644 --- a/docs/_data/help.yaml +++ b/docs/_data/help.yaml @@ -4217,7 +4217,7 @@ description: "" awxCredentials: - vmware - execution_environment: my_execution_environment + executionEnvironment: my_execution_environment roles: - public categories: [] @@ -4261,7 +4261,7 @@ columns: - name valueColumn: name - - name: __execution_environment__ # use this special name to override the execution_environment from the form + - name: __executionEnvironment__ # use this special name to override the executionEnvironment from the form label: Inventory type: enum expression: "fn.fnRestJwtSecure('get','https://172.16.50.1/api/v2/execution_environments?organization=$(organization)','','awx_rest','[.results[]]')" @@ -4307,11 +4307,11 @@ You can also choose if the repository must be cloned when AnsibleForms starts, and you can add cron-schedule to schedule recurring pull-actions. Additionally, in the swagger interface, you will find a clone and pull rest api for webhooks. In case you want long-lived access tokens for the webhooks, with swagger you can pass an expiryDays parameter (for admin roles only) and create long-lived tokens. - - name: Host Ansibleforms in a subfolder - short: Host Ansibleforms in a subfolder - description: | - In the case you want to host ansibleforms in subfolder, for example `https://af.domain.local/mysubfolder/`. - You can use the environment variable `BASE_URL`. Set it to `/mysubfolder/` (default is `/`). + # - name: Host Ansibleforms in a subfolder + # short: Host Ansibleforms in a subfolder + # description: | + # In the case you want to host ansibleforms in subfolder, for example `https://af.domain.local/mysubfolder/`. + # You can use the environment variable `BASE_URL`. Set it to `/mysubfolder/` (default is `/`). - name: Enable ytt short: Enable ytt description: | From 9006d665991dc380a0fa6f391c9d9ac7019292b7 Mon Sep 17 00:00:00 2001 From: Mirko Van Colen Date: Thu, 11 Jul 2024 13:03:25 +0200 Subject: [PATCH 02/21] add oracle --- docs/introduction.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/introduction.md b/docs/introduction.md index 29291236..d0951877 100644 --- a/docs/introduction.md +++ b/docs/introduction.md @@ -40,13 +40,14 @@ page_nav: * **Swagger API** : Has a rest-api and Swagger documentation * **Job History & Log** : See the history of your jobs* * **Designer** : Although the forms are NOT built using a graphical designer, a YAML based editor/designer with validation is present +* **Git integration** : Sync your forms config files, ansible playbooks and other required files with a git repo # Form Capabilities * **Categories** : Group multiple forms under categories * **Role based access** : Limit forms based on roles * **Cascaded dropdowns** : Allow references between fields to create responsive, cascaded dropdown boxes -* **Database sources** : Import data into fields from databases (MySql, MSSql, Postgres, Mongo) +* **Database sources** : Import data into fields from databases (MySql, MSSql, Postgres, Mongo, Oracle) * **Expression based sources** : Import data using serverside expressions (javascript), such as Rest API's, json-files, yaml-files, ... and filter, manipulate and sort them * **Local expressions** : Use the power of javascript (local browser sandbox) to calculate, manipulate, generate, ... * **Field dependencies** : Show/hide fields based on values of other fields From e4f06fbd4b03b8c1abe0fbdb66ebf3faa9659738 Mon Sep 17 00:00:00 2001 From: Mirko Van Colen Date: Thu, 11 Jul 2024 13:04:47 +0200 Subject: [PATCH 03/21] auto create base forms.yam if missing --- server/src/controllers/config.controller.js | 1 + server/src/models/form.model.js | 19 ++++++++++++++++--- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/server/src/controllers/config.controller.js b/server/src/controllers/config.controller.js index 6339ef4b..a4dfa6ba 100644 --- a/server/src/controllers/config.controller.js +++ b/server/src/controllers/config.controller.js @@ -13,6 +13,7 @@ exports.findAll = async function(req,res){ var forms = await Form.load() res.json(forms) }catch(err){ + // console.log(err) res.json({error:helpers.getError(err)}) } } diff --git a/server/src/models/form.model.js b/server/src/models/form.model.js index 76f6010c..84e6aa1a 100644 --- a/server/src/models/form.model.js +++ b/server/src/models/form.model.js @@ -127,7 +127,20 @@ Form.load = async function() { rawdata = fs.readFileSync(appFormsPath, 'utf8'); } } catch (e) { - logger.error(`failed to load '${appFormsPath}'.\n${e}`); + logger.error(`Failed to load '${appFormsPath}'.`,e); + + } + if(!rawdata){ + try{ + logger.warning("No forms found in database or forms.yaml... creating empty one from template") + fs.copyFileSync(path.join(__dirname,"../../templates/forms.yaml"),appFormsPath) + logger.warning("File copied") + rawdata = fs.readFileSync(appFormsPath, 'utf8'); + } catch (e) { + logger.error(`Failed to copy forms from template and/or to load '${appFormsPath}'.`,e); + throw new Error(Helpers.getError(e,"Error reading the forms")) + } + } } // read extra form files @@ -159,7 +172,7 @@ Form.load = async function() { value: itemRawData }) }catch(e){ - logger.error(`failed to load file '${item}'.\n${e}`); + logger.error(`Failed to load file '${item}'`,e); } }); } @@ -186,7 +199,7 @@ Form.load = async function() { logger.error(`failed to parse file '${item.name}'.\n${e}`) } }) - if(!forms.forms){ + if(!forms?.forms){ forms.forms=[] } // merge extra files From b18407a7769745e620addb8b088dfd35282cebe5 Mon Sep 17 00:00:00 2001 From: Mirko Van Colen Date: Sun, 18 Aug 2024 16:20:50 +0200 Subject: [PATCH 04/21] when run natively, you need a blank forms file and certificates. --- ldap.model.js | 152 +++++++++++++++++++++++++++ server/templates/cert.pem.template | 22 ++++ server/templates/forms.yaml.template | 27 +++++ server/templates/key.pem.template | 28 +++++ 4 files changed, 229 insertions(+) create mode 100644 ldap.model.js create mode 100644 server/templates/cert.pem.template create mode 100644 server/templates/forms.yaml.template create mode 100644 server/templates/key.pem.template diff --git a/ldap.model.js b/ldap.model.js new file mode 100644 index 00000000..6ce4e18e --- /dev/null +++ b/ldap.model.js @@ -0,0 +1,152 @@ +'use strict'; +const logger=require("../lib/logger"); +const mysql=require("./db.model") +const helpers=require("../lib/common") +const YAML=require("yaml") +const {encrypt,decrypt} = require("../lib/crypto") + +//ldap object create +var Ldap=function(ldap){ + this.server = ldap.server; + this.port = ldap.port; + this.ignore_certs = (ldap.ignore_certs)?1:0; + this.enable_tls = (ldap.enable_tls)?1:0; + this.cert = ldap.cert; + this.ca_bundle = ldap.ca_bundle; + this.bind_user_dn = ldap.bind_user_dn; + this.bind_user_pw = encrypt(ldap.bind_user_pw); + this.search_base = ldap.search_base; + this.username_attribute = ldap.username_attribute; + this.groups_attribute = ldap.groups_attribute; + this.enable = (ldap.enable)?1:0; + this.is_advanced = (ldap.is_advanced)?1:0; + this.groups_search_base = (ldap.is_advanced)?ldap.groups_search_base:"" + this.group_class = (ldap.is_advanced)?ldap.group_class:"" + this.group_member_attribute = (ldap.is_advanced)?ldap.group_member_attribute:"" + this.group_member_user_attribute = (ldap.is_advanced)?ldap.group_member_user_attribute:"" + this.mail_attribute = ldap.mail_attribute +}; +Ldap.update = function (record) { + logger.info(`Updating ldap ${record.server}`) + return mysql.do("UPDATE AnsibleForms.`ldap` set ?", record) +}; +Ldap.find = function(){ + return mysql.do("SELECT * FROM AnsibleForms.`ldap` limit 1;") + .then((res)=>{ + if(res.length>0){ + try{ + res[0].bind_user_pw=decrypt(res[0].bind_user_pw) + }catch(e){ + logger.error("Couldn't decrypt ldap binding password, did the secretkey change ?") + res[0].bind_user_pw="" + } + return res[0] + }else{ + logger.error("No ldap record in the database, something is wrong") + throw "No ldap record in the database, something is wrong" + } + }) +} +Ldap.check = function(ldapConfig){ + return new Promise(async (resolve,reject)=>{ + + const { authenticate } = require('../lib/ldap-authentication') + // auth with admin + var badCertificates=false + let options = { + ldapOpts: { + url: ((ldapConfig.enable_tls==1)?"ldaps":"ldap") + "://" + ldapConfig.server + ":" + ldapConfig.port, + tlsOptions: { + // cert: cert, + // requestCert: tls, + // rejectUnauthorized: rejectUnauthorized, + // ca: ca + } + }, + adminDn: ldapConfig.bind_user_dn, + adminPassword: decrypt(ldapConfig.bind_user_pw), + userPassword: "dummypassword_for_check", + userSearchBase: ldapConfig.search_base, + usernameAttribute: ldapConfig.username_attribute, + username: "dummyuser_for_check", + // starttls: false + } + // new in v4.0.20, add advanced ldap properties + if(ldapConfig.is_advanced){ + if(ldapConfig.groups_search_base){ options.groupsSearchBase = ldapConfig.groups_search_base } + if(ldapConfig.group_class){ options.groupClass = ldapConfig.group_class } + if(ldapConfig.group_member_attribute){ options.groupMemberAttribute = ldapConfig.group_member_attribute } + if(ldapConfig.group_member_user_attribute){ options.groupMemberUserAttribute = ldapConfig.group_member_user_attribute } + } + // console.log(options) + // ldap-authentication has bad cert check, so we check first !! + if(ldapConfig.enable_tls && !(ldapConfig.ignore_certs==1)){ + if(!helpers.checkCertificate(ldapConfig.cert)){ + badCertificates=true + } + if(!helpers.checkCertificate(ldapConfig.ca_bundle)){ + badCertificates=true + } + }else{ + ldapConfig.cert="" + ldapConfig.ca_bundle="" + } + // enable tls/ldaps + if(ldapConfig.enable_tls==1){ + options.ldapOpts.tlsOptions.requestCert = (ldapConfig.enable_tls==1) + if(ldapConfig.cert!=""){ + options.ldapOpts.tlsOptions.cert = ldapConfig.cert + } + if(ldapConfig.ca_bundle!=""){ + options.ldapOpts.tlsOptions.ca = ldapConfig.ldapTlsCa + } + options.ldapOpts.tlsOptions.rejectUnauthorized = !(ldapConfig.ignore_certs==1) + logger.info("use tls : " + (ldapConfig.enable_tls==1)) + logger.info("reject invalid certificates : " + !(ldapConfig.ignore_certs==1)) + } + + if(badCertificates){ + reject("Certificate is not valid") + }else{ + logger.notice("Certificates are valid") + try{ + // logger.debug(JSON.stringify(options)) + logger.notice("Authenticating") + var user = await authenticate(options) + resolve(user) + }catch(err){ + var em ="" + if(err.message){ + em = err.message + }else{ + try{ em = YAML.stringify(err)}catch(e){em = err} + } + if(err.admin){ + if(err.admin.lde_message){ + try{ em = YAML.stringify(err.admin.lde_message)}catch(e){em = err} + } + else if(err.admin.code){ + try{ em = YAML.stringify(err.admin)}catch(e){em = err} + if(err.admin.code=="UNABLE_TO_VERIFY_LEAF_SIGNATURE"){ + em = "Unable to verify the certificate" + }else if(err.admin.code==49){ + em = "Wrong binding credentials" + }else if(err.admin.code=="ENOTFOUND"){ + em = "Bad server or port (connection failed)" + } + } + } + + if(em.includes("user not found")){ + logger.notice("Checking ldap connection ok") + resolve() + }else{ + logger.notice("Checking ldap connection result : " + em) + reject(em) + } + } + } + }) +} + +module.exports= Ldap; diff --git a/server/templates/cert.pem.template b/server/templates/cert.pem.template new file mode 100644 index 00000000..1d7f51c6 --- /dev/null +++ b/server/templates/cert.pem.template @@ -0,0 +1,22 @@ +-----BEGIN CERTIFICATE----- +MIIDujCCAqKgAwIBAgIJAMMW5u/nBgdrMA0GCSqGSIb3DQEBCwUAMHYxCzAJBgNV +BAYTAkJFMREwDwYDVQQIDAhCcnVzc2VsczERMA8GA1UEBwwIQnJ1c3NlbHMxEzAR +BgNVBAoMCkFuc2libGVHdXkxFTATBgNVBAsMDEFuc2libGVGb3JtczEVMBMGA1UE +AwwMYW5zaWJsZWZvcm1zMB4XDTIxMTEwMjE1MjIzMVoXDTMxMTAzMTE1MjIzMVow +djELMAkGA1UEBhMCQkUxETAPBgNVBAgMCEJydXNzZWxzMREwDwYDVQQHDAhCcnVz +c2VsczETMBEGA1UECgwKQW5zaWJsZUd1eTEVMBMGA1UECwwMQW5zaWJsZUZvcm1z +MRUwEwYDVQQDDAxhbnNpYmxlZm9ybXMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw +ggEKAoIBAQCy4Gb5xWWG7w1CQ09m+PhG3kZaytv0nNs44q4rIBJNmphJ2tem8AIb +Ggg81SeuOW7e+Ze04IXzNGqEMJ+2I/Hq357a/SlSCL6HnW2c/hZ3CRdrHu1SyFk9 +YrbpWIOBPaJB0KEY5tn4SAds0WR7HUhDsd9/EgkV95mFm16EPfNIzGAdEAZgQkfi +GGUdfwPAUJoZlZzmSz2soxZJBFA0/x+cq21f0xrxesqM7Il4bWCZAVmYiIkAY1HA +YOy4C7DKDrPpvifPJdMiOHQ6fwP/JAOZ4HoyDYoUQDCBbAadK3ws4x6i8YlKfltm +Vq2t3zWZHSVwRF9abDbEGzmUSeYx91k3AgMBAAGjSzBJMA4GA1UdDwEB/wQEAwID +iDATBgNVHSUEDDAKBggrBgEFBQcDATAiBgNVHREEGzAZggxhbnNpYmxlZm9ybXOC +CWxvY2FsaG9zdDANBgkqhkiG9w0BAQsFAAOCAQEAHiY3mFawVyqeAN01jjGvF2+E +QjtHOL5Q4dtVdoZLvVFdXlRZkqtCZaQna3nYdzLlwWYQy8SC23QkU+P1wNAakf0N +na4M11Yi71h1hkHTo5Ub88DMWbz/VMaCo/Iefr4Sv1QoEmEeFEUtPbEAO6v9trqp +GOZv+6H3tuhuQkR+wWllBw7hqnWTvXTGRZXBlQH1wH04Vw6uUXg91ZMUE8DSddEz +nygVGVTEGWs3eld2j7rICRvGrtKOYrg6m+MfQN/skE1aa+auqu6OySAy0HBvS0u9 +G2+Ka6a54la0RR13lKmUR4y8B0izh5ThI0/FXtmVPI5XmG27Fellw7JG9bt1PQ== +-----END CERTIFICATE----- diff --git a/server/templates/forms.yaml.template b/server/templates/forms.yaml.template new file mode 100644 index 00000000..1ce7dc89 --- /dev/null +++ b/server/templates/forms.yaml.template @@ -0,0 +1,27 @@ +categories: # a list of categories to group forms + - name: Default + icon: bars +roles: # a list of roles + - name: admin + groups: + - local/admins + - name: public + groups: [] +constants: {} # free objects to re-use over all forms +forms: # a list of forms + - name: Demo Form + showHelp: true + help: > + This is a demo form + roles: + - public + description: A simple form + categories: + - Demo + icon: heart + playbook: dummy.yaml + type: ansible + fields: + - type: text + name: username + label: Username \ No newline at end of file diff --git a/server/templates/key.pem.template b/server/templates/key.pem.template new file mode 100644 index 00000000..93f34acd --- /dev/null +++ b/server/templates/key.pem.template @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCy4Gb5xWWG7w1C +Q09m+PhG3kZaytv0nNs44q4rIBJNmphJ2tem8AIbGgg81SeuOW7e+Ze04IXzNGqE +MJ+2I/Hq357a/SlSCL6HnW2c/hZ3CRdrHu1SyFk9YrbpWIOBPaJB0KEY5tn4SAds +0WR7HUhDsd9/EgkV95mFm16EPfNIzGAdEAZgQkfiGGUdfwPAUJoZlZzmSz2soxZJ +BFA0/x+cq21f0xrxesqM7Il4bWCZAVmYiIkAY1HAYOy4C7DKDrPpvifPJdMiOHQ6 +fwP/JAOZ4HoyDYoUQDCBbAadK3ws4x6i8YlKfltmVq2t3zWZHSVwRF9abDbEGzmU +SeYx91k3AgMBAAECggEAYX0X4m0JBl9m9IRG1DJA7i7aXUVOV6TdfcVdczeJgi4N +bcMN4XfRTgAEGVN6yuOWX4PcgMIVfxVEMENn6BbzFDVIGMX9LS6C2NqeEQASMlIM +J1+1rHZw3JneYpLRKTD0K7aO9klq5nwrP81nXAn7hpl8235y4TwOudiRzLUO0M9Z +Da6DT5R7bgGfXpkwZKKzlZwLYZq/gw+TBtEXRN//k8cRDqGCNvetgYczinJrg3HQ ++PDayHAhMqm5ufr59cpGKGWScdNdnLAnporbSHd+6UHHm0Lpza3z2nI3z1R0B7xs +kgvKh8g4N3lcAkjzMYSYr6Sf+aL16GP7imM8I/O2kQKBgQDaNZVYxs0pbnKocNaU +YYqNkxT4eNxgU7h3Zqxq9dd++bVq4tOOpO7ocugBc1lrmbQ9kQKRtv3/nUgU9iCI +ohPjJZRBlvlIaQfPo6zDD3K4xtVe083nlyeKiEN7yslTRl3N9zQDN+Cmb0h6U0Id +g+q2hX8Ke1ofopQEKqZxpbxvswKBgQDR2vuNdfd77G/Qp7kOU6u+V7752W2nDLEu +f7FBlwB9tHkfGiS2G/8HZ90Ei9ZC9rDjlsib7HG8tV/EZMhAtKgta3MHJm3h8tEA +KtX3sRo9W+ArOp+jvu+6Bj7Jn+GOHXYwpOodP1zt47xPrnayFuPV7/5XtS2zrsuo +CwZyEuAObQKBgE2nwhWM8lhrSPyu43581A0cKdtfT7YsNTqw3G1YPi+e+DQosvdR +tQAeXHifr1P+qEk8wPhQckY0mAF1shBN9dvhdMh+zQo67p+zdPkaF06w3CBaKi3f ++h9v7OwyN8GeCiYRcn4utZEli1qVJLNSTgZUrehyC5m0hw6QixlozQ3HAoGBAItq +cuIo8/C1RBeXxb554chDnRF53Ho1WWSt2oHboqzgf/MkuCzv7n7qBpBlokO8hgm8 ++6ty6qDW0je0SMGMA4qhLrsaUbfhS+5ThvDWDLuk1QmDGdl8GOE6Eu56NCvo8MMi +XJJvrPox6MH7AsoPoO9ZUFzOdf1Aa/ZI1NBmL8oFAoGBALduRfv4J1kFvauCWueM +QKn7y8DIuFuPZiBmLIKPSPZlzayPLJjS99jRjX/f1l27wOVAbW2Jl6HcRgcZiyoo +BNT1HmUcnP/G9OljB76j/URVjcfAUY12R8bnfuGlb1gzXaUccg0NdXMhOELXgnvx +eO0iTAwAMg0zebMbkmfBe/Zw +-----END PRIVATE KEY----- From ac62d6b5a7803dc7fbf520e3259f14816c0f4620 Mon Sep 17 00:00:00 2001 From: Mirko Van Colen Date: Sun, 18 Aug 2024 16:21:06 +0200 Subject: [PATCH 05/21] podman support --- docs/installation.md | 31 +++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/docs/installation.md b/docs/installation.md index 4acee819..8b554b54 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -34,9 +34,14 @@ AnsibleForms can be installed in a few ways. -# Install using Docker-Compose +# Install using Docker-Compose + +The recommended way to install AnsibleForms is using `Docker Compose`, which is the fastest way to start AnsibleForms with Docker. However, if you are skilled with docker, podman and/or Kubernetes, the [docker-compose (with Kubernetes sample)](https://github.com/ansibleguy76/ansibleforms-docker), together with the environment variables should get you started as well. + +
+

Note You can also use Podman and Podman-Compose. The commands are similar (docker- > podman and docker-compose -> podman-compose)

+
-The recommended way to install AnsibleForms is using `Docker Compose`, which is the fastest way to start AnsibleForms with Docker. However, if you are skilled with docker and/or Kubernetes, the [docker-compose (with Kubernetes sample)](https://github.com/ansibleguy76/ansibleforms-docker), together with the environment variables should get you started as well.

Note Using docker and docker-compose for the first time, requires some basic linux skills and some knowledge about containers

@@ -45,7 +50,7 @@ The recommended way to install AnsibleForms is using `Docker Compose`, which is ## Prerequisites -* **Linux machine** : Any flavour should do, The need of CPU and memory is not very high, but, of course can grow if you start a lot of playbooks simultaniously +* **Linux machine** : Any flavour should do, The need of CPU and memory is not very high, but, of course can grow if you start a lot of playbooks simultaniously. When using Podman, I recommand Debian (ubuntu has some issues with Podman) * **Github access** : The easiest way is to download or clone the docker-compose project on Github * **Install Docker** : You need to have a container environment, and in this example we use Docker * **Install Docker Compose** : To spin-up AnsibleForms and MySql with docker, using a few simple configuration-files, we need Docker Compose @@ -68,10 +73,8 @@ cd /srv/apps ## Clone the docker-compose project ```bash -# centos -sudo yum install -y git -# ubuntu +# ubuntu or debian sudo apt-get install -y git ‌sudo ‌git init @@ -94,10 +97,7 @@ sudo chmod -R +x ./data/mysql/init/ [Docker installation manuals](https://docs.docker.com/engine/install) ```bash -# centos -yum install -y docker-ce docker-ce-cli containerd.io docker-compose - -# ubuntu +# ubuntu / debian sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin docker-compose # the below is to ensure dns works properly inside the dockerimages @@ -109,6 +109,13 @@ sudo systemctl start docker sudo systemctl enable docker ``` +## Install Podman and podman-compose + +```bash +# ubuntu / debian +sudo apt-get install -y podman podman-compose +``` + ## Customize Feel free to look at the variables in the `.env` file and `docker-compose.yaml` file. @@ -118,6 +125,10 @@ Feel free to look at the variables in the `.env` file and `docker-compose.yaml` ```bash sudo docker-compose up -d +# note, with some plavors and versions, it's `docker compose` (with a space) +# or +sudo podman-compose up -d +# note that podman is service-less. You can run it as any user. Your choice to use sudo or not. ``` ## Test the application From 075493c66064c673d63fa68c4124bef9fc2d6eee Mon Sep 17 00:00:00 2001 From: Mirko Van Colen Date: Sun, 18 Aug 2024 16:21:31 +0200 Subject: [PATCH 06/21] add reset --- server/src/controllers/repository.controller.js | 5 +++++ server/src/models/repository.model.js | 5 +++++ server/src/routes/repository.routes.js | 2 ++ 3 files changed, 12 insertions(+) diff --git a/server/src/controllers/repository.controller.js b/server/src/controllers/repository.controller.js index 2673f47c..993adc5a 100644 --- a/server/src/controllers/repository.controller.js +++ b/server/src/controllers/repository.controller.js @@ -47,6 +47,11 @@ exports.clone = function(req, res) { .then(()=>{res.json(new RestResult("success","repository cloned",null,""))}) .catch((err)=>{ res.json(new RestResult("error","failed to clone repository",null,err.toString())) }) }; +exports.reset = function(req, res) { + Repository.reset(req.params.name) + .then(()=>{res.json(new RestResult("success","repository reset",null,""))}) + .catch((err)=>{ res.json(new RestResult("error","failed to reset repository",null,err.toString())) }) + }; exports.pull = function(req, res) { Repository.pull(req.params.name) .then(()=>{res.json(new RestResult("success","repository pulled",null,""))}) diff --git a/server/src/models/repository.model.js b/server/src/models/repository.model.js index 91a9ca88..708cfc8b 100644 --- a/server/src/models/repository.model.js +++ b/server/src/models/repository.model.js @@ -44,6 +44,11 @@ Repository.update = function (record,name) { return res }) }; +Repository.reset = async function(name){ + logger.info(`Resetting repository ${name}`) + await Repo.delete(name) // delete the repo on disk + await Repository.clone(name) // recreate the repo +} Repository.delete = function(name){ logger.info(`Deleting repository ${name}`) Repo.delete(name) diff --git a/server/src/routes/repository.routes.js b/server/src/routes/repository.routes.js index e8cd3b30..bcd1e086 100644 --- a/server/src/routes/repository.routes.js +++ b/server/src/routes/repository.routes.js @@ -13,6 +13,8 @@ router.put('/:name', repositoryController.update); router.delete('/:name', repositoryController.delete); // Clone a repository by name router.post('/:name/clone/', repositoryController.clone); +// reset a repository by name +router.post('/:name/reset/', repositoryController.reset); // Pull a repository by name router.post('/:name/pull/', repositoryController.pull); From d6c9ed519953954716a40b634c1a79ae5aa84428 Mon Sep 17 00:00:00 2001 From: Mirko Van Colen Date: Sun, 18 Aug 2024 16:25:53 +0200 Subject: [PATCH 07/21] download logs bug with headers --- client/src/views/Jobs.vue | 2 +- client/src/views/Logs.vue | 2 +- ldap.model.js | 152 -------------------------------------- 3 files changed, 2 insertions(+), 154 deletions(-) delete mode 100644 ldap.model.js diff --git a/client/src/views/Jobs.vue b/client/src/views/Jobs.vue index 15a17458..55868d69 100644 --- a/client/src/views/Jobs.vue +++ b/client/src/views/Jobs.vue @@ -383,7 +383,7 @@ var ref=this axios({ method: 'get', - headers, + headers: headers.headers, url, responseType: 'arraybuffer', }) diff --git a/client/src/views/Logs.vue b/client/src/views/Logs.vue index b10bbc6e..dfbe5310 100644 --- a/client/src/views/Logs.vue +++ b/client/src/views/Logs.vue @@ -104,7 +104,7 @@ var ref=this axios({ method: 'get', - headers, + headers:headers.headers, url, responseType: 'arraybuffer', }) diff --git a/ldap.model.js b/ldap.model.js deleted file mode 100644 index 6ce4e18e..00000000 --- a/ldap.model.js +++ /dev/null @@ -1,152 +0,0 @@ -'use strict'; -const logger=require("../lib/logger"); -const mysql=require("./db.model") -const helpers=require("../lib/common") -const YAML=require("yaml") -const {encrypt,decrypt} = require("../lib/crypto") - -//ldap object create -var Ldap=function(ldap){ - this.server = ldap.server; - this.port = ldap.port; - this.ignore_certs = (ldap.ignore_certs)?1:0; - this.enable_tls = (ldap.enable_tls)?1:0; - this.cert = ldap.cert; - this.ca_bundle = ldap.ca_bundle; - this.bind_user_dn = ldap.bind_user_dn; - this.bind_user_pw = encrypt(ldap.bind_user_pw); - this.search_base = ldap.search_base; - this.username_attribute = ldap.username_attribute; - this.groups_attribute = ldap.groups_attribute; - this.enable = (ldap.enable)?1:0; - this.is_advanced = (ldap.is_advanced)?1:0; - this.groups_search_base = (ldap.is_advanced)?ldap.groups_search_base:"" - this.group_class = (ldap.is_advanced)?ldap.group_class:"" - this.group_member_attribute = (ldap.is_advanced)?ldap.group_member_attribute:"" - this.group_member_user_attribute = (ldap.is_advanced)?ldap.group_member_user_attribute:"" - this.mail_attribute = ldap.mail_attribute -}; -Ldap.update = function (record) { - logger.info(`Updating ldap ${record.server}`) - return mysql.do("UPDATE AnsibleForms.`ldap` set ?", record) -}; -Ldap.find = function(){ - return mysql.do("SELECT * FROM AnsibleForms.`ldap` limit 1;") - .then((res)=>{ - if(res.length>0){ - try{ - res[0].bind_user_pw=decrypt(res[0].bind_user_pw) - }catch(e){ - logger.error("Couldn't decrypt ldap binding password, did the secretkey change ?") - res[0].bind_user_pw="" - } - return res[0] - }else{ - logger.error("No ldap record in the database, something is wrong") - throw "No ldap record in the database, something is wrong" - } - }) -} -Ldap.check = function(ldapConfig){ - return new Promise(async (resolve,reject)=>{ - - const { authenticate } = require('../lib/ldap-authentication') - // auth with admin - var badCertificates=false - let options = { - ldapOpts: { - url: ((ldapConfig.enable_tls==1)?"ldaps":"ldap") + "://" + ldapConfig.server + ":" + ldapConfig.port, - tlsOptions: { - // cert: cert, - // requestCert: tls, - // rejectUnauthorized: rejectUnauthorized, - // ca: ca - } - }, - adminDn: ldapConfig.bind_user_dn, - adminPassword: decrypt(ldapConfig.bind_user_pw), - userPassword: "dummypassword_for_check", - userSearchBase: ldapConfig.search_base, - usernameAttribute: ldapConfig.username_attribute, - username: "dummyuser_for_check", - // starttls: false - } - // new in v4.0.20, add advanced ldap properties - if(ldapConfig.is_advanced){ - if(ldapConfig.groups_search_base){ options.groupsSearchBase = ldapConfig.groups_search_base } - if(ldapConfig.group_class){ options.groupClass = ldapConfig.group_class } - if(ldapConfig.group_member_attribute){ options.groupMemberAttribute = ldapConfig.group_member_attribute } - if(ldapConfig.group_member_user_attribute){ options.groupMemberUserAttribute = ldapConfig.group_member_user_attribute } - } - // console.log(options) - // ldap-authentication has bad cert check, so we check first !! - if(ldapConfig.enable_tls && !(ldapConfig.ignore_certs==1)){ - if(!helpers.checkCertificate(ldapConfig.cert)){ - badCertificates=true - } - if(!helpers.checkCertificate(ldapConfig.ca_bundle)){ - badCertificates=true - } - }else{ - ldapConfig.cert="" - ldapConfig.ca_bundle="" - } - // enable tls/ldaps - if(ldapConfig.enable_tls==1){ - options.ldapOpts.tlsOptions.requestCert = (ldapConfig.enable_tls==1) - if(ldapConfig.cert!=""){ - options.ldapOpts.tlsOptions.cert = ldapConfig.cert - } - if(ldapConfig.ca_bundle!=""){ - options.ldapOpts.tlsOptions.ca = ldapConfig.ldapTlsCa - } - options.ldapOpts.tlsOptions.rejectUnauthorized = !(ldapConfig.ignore_certs==1) - logger.info("use tls : " + (ldapConfig.enable_tls==1)) - logger.info("reject invalid certificates : " + !(ldapConfig.ignore_certs==1)) - } - - if(badCertificates){ - reject("Certificate is not valid") - }else{ - logger.notice("Certificates are valid") - try{ - // logger.debug(JSON.stringify(options)) - logger.notice("Authenticating") - var user = await authenticate(options) - resolve(user) - }catch(err){ - var em ="" - if(err.message){ - em = err.message - }else{ - try{ em = YAML.stringify(err)}catch(e){em = err} - } - if(err.admin){ - if(err.admin.lde_message){ - try{ em = YAML.stringify(err.admin.lde_message)}catch(e){em = err} - } - else if(err.admin.code){ - try{ em = YAML.stringify(err.admin)}catch(e){em = err} - if(err.admin.code=="UNABLE_TO_VERIFY_LEAF_SIGNATURE"){ - em = "Unable to verify the certificate" - }else if(err.admin.code==49){ - em = "Wrong binding credentials" - }else if(err.admin.code=="ENOTFOUND"){ - em = "Bad server or port (connection failed)" - } - } - } - - if(em.includes("user not found")){ - logger.notice("Checking ldap connection ok") - resolve() - }else{ - logger.notice("Checking ldap connection result : " + em) - reject(em) - } - } - } - }) -} - -module.exports= Ldap; From 68c1ee01d103a6f929abf6253d15035ec0de3314 Mon Sep 17 00:00:00 2001 From: Mirko Van Colen Date: Sun, 18 Aug 2024 16:26:28 +0200 Subject: [PATCH 08/21] credentials bug fix --- client/src/views/Credentials.vue | 11 ++++++++++- server/src/models/credential.model.js | 25 +++++++++++++++---------- 2 files changed, 25 insertions(+), 11 deletions(-) diff --git a/client/src/views/Credentials.vue b/client/src/views/Credentials.vue index 55bf7382..feb11640 100644 --- a/client/src/views/Credentials.vue +++ b/client/src/views/Credentials.vue @@ -151,7 +151,16 @@ }else{ console.log("No item selected") this.credential = { - name:"" + is_database:false, + name:"", + user:"", + password:"", + host:"NA", + port:3306, + db_name:"", + description:"", + secure:false, + db_type:"" } } }, diff --git a/server/src/models/credential.model.js b/server/src/models/credential.model.js index 6069ef9c..e6579458 100644 --- a/server/src/models/credential.model.js +++ b/server/src/models/credential.model.js @@ -12,24 +12,29 @@ const cache = new NodeCache({ //credential object create var Credential=function(credential){ - this.name = credential.name; - this.host = credential.host; - this.port = credential.port; - this.user = credential.user; - this.db_name = credential.db_name; - this.secure = (credential.secure)?1:0; - this.is_database = (credential.is_database)?1:0; - this.password = encrypt(credential.password); - this.description = credential.description || ""; - this.db_type = credential.db_type; + if(credential.name!=undefined){this.name = credential.name } + if(credential.host!=undefined){this.host = credential.host } + if(credential.port!=undefined){this.port = credential.port } + if(credential.user!=undefined){this.user = credential.user } + if(credential.db_name!=undefined){this.db_name = credential.db_name } + if(credential.secure!=undefined){this.secure = (credential.secure)?1:0 } + if(credential.is_database!=undefined){this.is_database = (credential.is_database)?1:0 } + if(credential.password!=undefined){this.password = encrypt(credential.password) } + if(credential.description!=undefined){this.description = credential.description } + if(credential.db_type!=undefined){this.db_type = credential.db_type } }; Credential.create = async function (record) { + if(!record.name){ + throw "Name is required" + } logger.info(`Creating credential ${record.name}`) var res = await mysql.do("INSERT INTO AnsibleForms.`credentials` set ?", record) return res.insertId }; Credential.update = async function (record,id) { + const r = await Credential.findById(id) // quickly search name + record.name = r[0].name logger.info(`Updating credential ${record.name}`) var res = await mysql.do("UPDATE AnsibleForms.`credentials` set ? WHERE id=?", [record,id]) cache.del(record.name) From 9bce2a982cf5232bd8ec18e382ad32faa0535d2a Mon Sep 17 00:00:00 2001 From: Mirko Van Colen Date: Sun, 18 Aug 2024 16:27:02 +0200 Subject: [PATCH 09/21] beauty changes --- server/src/models/form.model.js | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/server/src/models/form.model.js b/server/src/models/form.model.js index 84e6aa1a..2776339d 100644 --- a/server/src/models/form.model.js +++ b/server/src/models/form.model.js @@ -99,9 +99,9 @@ Form.load = async function() { var formfiles=[] var files=undefined - var ytt_env_data_opt = '' + var yttEnvDataOpt = '' if ('YTT_VARS_PREFIX' in process.env) { - ytt_env_data_opt = ` --data-values-env ${process.env.YTT_VARS_PREFIX}`; + yttEnvDataOpt = ` --data-values-env ${process.env.YTT_VARS_PREFIX}`; } var yttLibDataOpts = getYttLibDataOpts(); @@ -115,9 +115,9 @@ Form.load = async function() { try { if (appConfig.useYtt) { logger.info(`interpreting ${appFormsPath} with ytt.`); - logger.debug(`executing 'ytt -f ${appFormsPath} -f ${formslibdirpath}${ytt_env_data_opt}${yttLibDataOpts}'`) + logger.debug(`executing 'ytt -f ${appFormsPath} -f ${formslibdirpath}${yttEnvDataOpt}${yttLibDataOpts}'`) rawdata = execSync( - `ytt -f ${appFormsPath} -f ${formslibdirpath}${ytt_env_data_opt}${yttLibDataOpts}`, + `ytt -f ${appFormsPath} -f ${formslibdirpath}${yttEnvDataOpt}${yttLibDataOpts}`, { env: process.env, encoding: 'utf-8' @@ -133,7 +133,8 @@ Form.load = async function() { if(!rawdata){ try{ logger.warning("No forms found in database or forms.yaml... creating empty one from template") - fs.copyFileSync(path.join(__dirname,"../../templates/forms.yaml"),appFormsPath) + var formsTemplatePath = path.join(__dirname,"../../templates/forms.yaml.template") + fs.copyFileSync(formsTemplatePath,appFormsPath) logger.warning("File copied") rawdata = fs.readFileSync(appFormsPath, 'utf8'); } catch (e) { @@ -156,9 +157,9 @@ Form.load = async function() { var itemRawData = ''; if (appConfig.useYtt) { logger.info(`interpreting ${itemFormPath} with ytt.`); - logger.debug(`executing 'ytt -f ${itemFormPath} -f ${formslibdirpath}${ytt_env_data_opt}'`) + logger.debug(`executing 'ytt -f ${itemFormPath} -f ${formslibdirpath}${yttEnvDataOpt}'`) itemRawData = execSync( - `ytt -f ${itemFormPath} -f ${formslibdirpath}${ytt_env_data_opt}`, + `ytt -f ${itemFormPath} -f ${formslibdirpath}${yttEnvDataOpt}`, { env: process.env, encoding: 'utf-8' From 9c005b990453965f7e118727207c645a63611f3c Mon Sep 17 00:00:00 2001 From: Mirko Van Colen Date: Sun, 18 Aug 2024 16:27:23 +0200 Subject: [PATCH 10/21] create certs from template --- server/config/https.config.js | 35 +++++++++++++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/server/config/https.config.js b/server/config/https.config.js index bd3a8ebb..5a5d0ceb 100644 --- a/server/config/https.config.js +++ b/server/config/https.config.js @@ -1,12 +1,43 @@ const fs=require('fs') +const path=require('path') const logger=require("../src/lib/logger"); var privatekey=undefined var certificate=undefined if(process.env.HTTPS=="1"){ + + var certificatePath = process.env.HTTPS_CERT || (__dirname + '/../persistent/certificates/cert.pem') + var privatekeyPath = process.env.HTTPS_KEY || (__dirname + '/../persistent/certificates/key.pem') + // logger.info("Using https certificate : " + certificatePath) + // logger.info("Using https private key : " + privatekeyPath) + + // check if httpsConfig.httpsKey and httpsConfig.httpsCert exist + if(!fs.existsSync(certificatePath) || !fs.existsSync(privatekeyPath)){ + logger.warning("httpsKey or httpsCert not found, copying from templates") + var certificateTemplatePath = path.join(__dirname,"/../templates/cert.pem.template") + var privatekeyTemplatePath = path.join(__dirname,"/../templates/key.pem.template") + var certificateDirPath = path.dirname(certificatePath) + // logger.info("Using https certificate template : " + certificateTemplatePath) + // logger.info("Using https private key template : " + privatekeyTemplatePath) + try{ + // create the folder if it doesn't exist + logger.info("Creating folder " + certificateDirPath) + fs.mkdirSync(certificateDirPath, { recursive: true }); + logger.info("Copying templates to " + certificatePath + " and " + privatekeyPath) + fs.copyFileSync(certificateTemplatePath, certificatePath); + fs.copyFileSync(privatekeyTemplatePath, privatekeyPath); + logger.info("Copied templates to " + certificatePath + " and " + privatekeyPath) + }catch(e){ + logger.error("No certificate found and could not copy templates",e) + // exit // no point to continue + process.exit(1) + } + } + + try{ - privatekey = fs.readFileSync(process.env.HTTPS_KEY || (__dirname + '/../persistent/certificates/key.pem')) - certificate = fs.readFileSync(process.env.HTTPS_CERT || (__dirname + '/../persistent/certificates/cert.pem')) + privatekey = fs.readFileSync(privatekeyPath) + certificate = fs.readFileSync(certificatePath) }catch(err){ logger.error("Failed to open https private key and certificate : ",err) throw new Error("Failed to open https private key and certificate : " + err.message) From 0a0a99a75073ae70bef78bfddcce784cb91c8661 Mon Sep 17 00:00:00 2001 From: Mirko Van Colen Date: Sun, 18 Aug 2024 16:27:42 +0200 Subject: [PATCH 11/21] database bug --- server/src/lib/mysql.js | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/server/src/lib/mysql.js b/server/src/lib/mysql.js index f529220d..21ee8d75 100644 --- a/server/src/lib/mysql.js +++ b/server/src/lib/mysql.js @@ -23,6 +23,27 @@ MySql.query = async function (connection_name, query) { logger.debug(`[${connection_name}] query : ${query}`) var conn try{ + + // fixed in 5.0.4 + config.multipleStatements=true + // remove database if not defined + if(config.db_name){ + config.database = config.db_name + } + if(config.secure){ + config.ssl={ + sslmode:"required", + rejectUnauthorized:false + } + }else{ + config.ssl={ + sslmode:"none", + rejectUnauthorized:false + } + } + // get connection + config=MySql.clean(config) // remove unsupported properties + conn = await client.createConnection(MySql.clean(config)) var result try{ From d56a35d9cb5d0a013b5bd6034848c7a801715649 Mon Sep 17 00:00:00 2001 From: Mirko Van Colen Date: Sun, 18 Aug 2024 16:28:03 +0200 Subject: [PATCH 12/21] async await --- server/src/controllers/group.controller.js | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/server/src/controllers/group.controller.js b/server/src/controllers/group.controller.js index 2a49b956..e6b598c2 100644 --- a/server/src/controllers/group.controller.js +++ b/server/src/controllers/group.controller.js @@ -51,15 +51,16 @@ exports.update = function(req, res) { .catch((err)=>{res.json(new RestResult("error","failed to update group",null,err.toString()))}) } }; -exports.delete = function(req, res) { - Group.delete( req.params.id) - .then((deleted)=>{ - if(deleted.affectedRows==1){ - res.json(new RestResult("success","group deleted",null,"")) - }else{ - res.json(new RestResult("error","unknown group or group has users",null,`affected rows : ${deleted.affectedRows}`)) - } +exports.delete = async function(req, res) { + try{ + const deleted = await Group.delete( req.params.id) + if(deleted.affectedRows==1){ + res.json(new RestResult("success","group deleted",null,"")) + }else{ + res.json(new RestResult("error","unknown group or group has users",null,`affected rows : ${deleted.affectedRows}`)) + } + }catch(err){ + res.json(new RestResult("error","failed to delete group",null,err.toString())) + } - }) - .catch((err)=>{res.json(new RestResult("error","failed to delete group",null,err.toString()))}) }; From fa4e1942d1e0cf9e292ad798cd151a49065a8e7a Mon Sep 17 00:00:00 2001 From: Mirko Van Colen Date: Sun, 18 Aug 2024 16:28:26 +0200 Subject: [PATCH 13/21] version bumps --- client/package.json | 6 +++--- server/package.json | 28 ++++++++++++++-------------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/client/package.json b/client/package.json index f61e0c28..41a0a7d9 100644 --- a/client/package.json +++ b/client/package.json @@ -1,6 +1,6 @@ { "name": "ansible_forms_vue", - "version": "5.0.3", + "version": "5.0.4", "private": true, "scripts": { "serve": "vue-cli-service serve", @@ -19,7 +19,7 @@ "@fortawesome/free-regular-svg-icons": "~6.5.2", "@fortawesome/free-solid-svg-icons": "~6.5.2", "@fortawesome/vue-fontawesome": "2.0.10", - "axios": "~1.7.2", + "axios": "~1.7.4", "brace": "~0.11.1", "bulma": "0.9.4", "bulma-calendar": "6.1.19", @@ -28,7 +28,7 @@ "bulma-quickview": "*", "bulmaswatch": "0.8.1", "copy-to-clipboard": "~3.3.3", - "core-js": "~3.37.1", + "core-js": "~3.38.0", "es6-promise": "~4.2.8", "highlight.js": "9.11.0", "jsonwebtoken": "^9.0.2", diff --git a/server/package.json b/server/package.json index 39587f53..cafec81a 100644 --- a/server/package.json +++ b/server/package.json @@ -1,6 +1,6 @@ { "name": "ansible_forms", - "version": "5.0.3", + "version": "5.0.4", "repository": { "type": "git", "url": "git://github.com/ansibleguy76/ansibleforms.git" @@ -20,34 +20,34 @@ "@outlinewiki/passport-azure-ad-oauth2": "~0.1.0", "ajv": "~6.12.6", "ajv-error-parser": "~1.0.7", - "axios": "~1.7.2", + "axios": "~1.7.4", "bcrypt": "~5.1.0", "bluebird": "~3.7.2", "cert-info": "~1.5.1", - "cheerio": "~1.0.0-rc.12", + "cheerio": "~1.0.0", "connect-history-api-fallback": "~2.0.0", - "core-js": "~3.37.1", + "core-js": "~3.38.0", "cors": "~2.8.5", "cron-parser": "~4.9.0", - "dayjs": "1.11.11", + "dayjs": "1.11.12", "express": "~4.19.2", "cookie-session": "~2.1.0", "fs-extra": "~11.2.0", "ip": "2.0.1", "json-bigint": "~1.0.0", - "ldap-authentication": "~3.2.1", + "ldap-authentication": "~3.2.2", "ldapjs": "~3.0.7", "lodash": "~4.17.21", "modern-passport-http": "~0.3.0", "moment": "~2.30.1", - "mongodb": "~6.7.0", - "oracledb": "~6.5.1", + "mongodb": "~6.8.0", + "oracledb": "~6.6.0", "mssql": "~10.0.2", "multer": "~1.4.5-lts.1", - "mysql2": "~3.10.0", + "mysql2": "~3.11.0", "node-cache": "~5.1.2", "node-jq": "~4.4.0", - "nodemailer": "~6.9.8", + "nodemailer": "~6.9.14", "openid-client": "^5.6.5", "passport": "~0.7.0", "passport-jwt": "~4.0.1", @@ -55,16 +55,16 @@ "read-last-lines": "~1.8.0", "swagger-ui-express": "~5.0.1", "thenby": "~1.3.4", - "winston": "~3.13.0", + "winston": "~3.14.1", "winston-daily-rotate-file": "~5.0.0", "winston-syslog": "~2.7.0", "yaml": "~2.4.5" }, "devDependencies": { "@babel/cli": "~7.24.7", - "@babel/core": "7.24.7", - "@babel/eslint-parser": "7.24.7", - "@babel/node": "~7.24.7", + "@babel/core": "7.25.2", + "@babel/eslint-parser": "7.25.1", + "@babel/node": "~7.25.0", "dotenv": "~16.4.1", "eslint": "~8.56.0", "nodemon": "~3.1.3", From 7a1374af96d0985a44e33570ae52019179eba43f Mon Sep 17 00:00:00 2001 From: Mirko Van Colen Date: Sun, 18 Aug 2024 16:28:45 +0200 Subject: [PATCH 14/21] async await --- server/src/models/group.model.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/server/src/models/group.model.js b/server/src/models/group.model.js index 07f8d4d3..879c5f0f 100644 --- a/server/src/models/group.model.js +++ b/server/src/models/group.model.js @@ -15,13 +15,13 @@ Group.update = function (record,id) { logger.info(`Updating group ${record.name}`) return mysql.do("UPDATE AnsibleForms.`groups` set ? WHERE name=?", [record,id]) }; -Group.delete = function(id){ +Group.delete = async function(id){ if(id==1){ logger.warning("Someone is trying to remove the admins group !") - return new Promise.reject("You cannot delete group 'admins'") + throw new Error("You cannot delete group 'admins'") }else{ logger.info(`Deleting group ${id}`) - return mysql.do("DELETE FROM AnsibleForms.`groups` WHERE id = ? AND name<>'admins' AND NOT EXISTS(SELECT id FROM AnsibleForms.users u WHERE u.group_id=groups.id)", [id]) + return await mysql.do("DELETE FROM AnsibleForms.`groups` WHERE id = ? AND name<>'admins' AND NOT EXISTS(SELECT id FROM AnsibleForms.users u WHERE u.group_id=groups.id)", [id]) } }; From b8c227360dd93a87f53cf32e9b51d86bfae0ee89 Mon Sep 17 00:00:00 2001 From: Mirko Van Colen Date: Sun, 18 Aug 2024 16:29:02 +0200 Subject: [PATCH 15/21] some db init bugs --- server/src/db/create_schema_and_tables.sql | 50 ++++++++++++++++++++-- server/src/db/create_settings_table.sql | 4 +- 2 files changed, 49 insertions(+), 5 deletions(-) diff --git a/server/src/db/create_schema_and_tables.sql b/server/src/db/create_schema_and_tables.sql index 7fcda862..6826bab3 100644 --- a/server/src/db/create_schema_and_tables.sql +++ b/server/src/db/create_schema_and_tables.sql @@ -1,6 +1,10 @@ +-- disable foreign key checks to avoid errors when creating tables SET FOREIGN_KEY_CHECKS=0; +-- create the database if it does not exist CREATE DATABASE /*!32312 IF NOT EXISTS*/`AnsibleForms` /*!40100 DEFAULT CHARACTER SET utf8 */; +-- use the database USE `AnsibleForms`; +-- create groups table DROP TABLE IF EXISTS `groups`; CREATE TABLE `groups`( `id` int(11) NOT NULL AUTO_INCREMENT, @@ -8,6 +12,7 @@ CREATE TABLE `groups`( PRIMARY KEY (`id`), UNIQUE KEY `uk_AnsibleForms_groups_natural_key` (`name`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; +-- create users table DROP TABLE IF EXISTS `users`; CREATE TABLE `users`( `id` int(11) NOT NULL AUTO_INCREMENT, @@ -20,6 +25,7 @@ CREATE TABLE `users`( KEY `FK_users_group` (`group_id`), CONSTRAINT `FK_users_group` FOREIGN KEY (`group_id`) REFERENCES `groups` (`id`) ON DELETE CASCADE ON UPDATE CASCADE ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; +-- create tokens table DROP TABLE IF EXISTS `tokens`; CREATE TABLE `tokens` ( `username` varchar(250) NOT NULL, @@ -27,6 +33,7 @@ CREATE TABLE `tokens` ( `refresh_token` text DEFAULT NULL, `timestamp` datetime NOT NULL DEFAULT current_timestamp() ) ENGINE=InnoDB DEFAULT CHARSET=utf8; +-- create credentials table DROP TABLE IF EXISTS `credentials`; CREATE TABLE `credentials` ( `id` int(11) NOT NULL AUTO_INCREMENT, @@ -43,6 +50,7 @@ CREATE TABLE `credentials` ( PRIMARY KEY (`id`), UNIQUE KEY `uk_AnsibleForms_credentials_natural_key` (`name`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; +-- create ldap table DROP TABLE IF EXISTS `ldap`; CREATE TABLE `ldap` ( `server` varchar(250) DEFAULT NULL, @@ -64,6 +72,7 @@ CREATE TABLE `ldap` ( `mail_attribute` varchar(250) DEFAULT NULL, `enable` tinyint(4) DEFAULT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8; +-- create awx table DROP TABLE IF EXISTS `awx`; CREATE TABLE `awx` ( `uri` varchar(250) NOT NULL, @@ -74,6 +83,7 @@ CREATE TABLE `awx` ( `ignore_certs` tinyint(4) DEFAULT NULL, `ca_bundle` text DEFAULT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8; +-- create job_output and jobs tables DROP TABLE IF EXISTS `job_output`; DROP TABLE IF EXISTS `jobs`; CREATE TABLE `jobs` ( @@ -106,6 +116,7 @@ CREATE TABLE `job_output` ( KEY `FK_job_output_jobs` (`job_id`), CONSTRAINT `FK_job_output_jobs` FOREIGN KEY (`job_id`) REFERENCES `jobs` (`id`) ON DELETE CASCADE ON UPDATE CASCADE ) ENGINE=InnoDB AUTO_INCREMENT=1650 DEFAULT CHARSET=utf8; +-- create settings table DROP TABLE IF EXISTS `settings`; CREATE TABLE `settings` ( `mail_server` varchar(250) DEFAULT NULL, @@ -117,17 +128,50 @@ CREATE TABLE `settings` ( `url` varchar(250) DEFAULT NULL, `forms_yaml` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8; -USE `AnsibleForms`; +-- create azuread table DROP TABLE IF EXISTS `azuread`; CREATE TABLE `azuread` ( `client_id` text DEFAULT NULL, `secret_id` text DEFAULT NULL, `enable` tinyint(4) DEFAULT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8; +-- create oidc table +DROP TABLE IF EXISTS `oidc`; +CREATE TABLE `oidc` ( + `issuer` text DEFAULT NULL, + `client_id` text DEFAULT NULL, + `secret_id` text DEFAULT NULL, + `enabled` tinyint(4) DEFAULT NULL, + `groupfilter` varchar(250) DEFAULT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8; +-- create repositories table +DROP TABLE IF EXISTS `repositories`; +CREATE TABLE `repositories` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `name` varchar(250) NOT NULL, + `user` varchar(250) DEFAULT NULL, + `password` text DEFAULT NULL, + `uri` varchar(250) DEFAULT NULL, + `description` text NOT NULL, + `use_for_forms` tinyint(4) DEFAULT NULL, + `use_for_playbooks` tinyint(4) DEFAULT NULL, + `cron` varchar(50) DEFAULT NULL, + `status` varchar(50) DEFAULT NULL, + `output` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `head` varchar(50) DEFAULT NULL, + `rebase_on_start` tinyint(4) DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `uk_AnsibleForms_repositories_natural_key` (`name`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- insert default values INSERT INTO AnsibleForms.azuread(client_id,secret_id,enable) VALUES('','',0); +INSERT INTO AnsibleForms.oidc(issuer, client_id,secret_id,enabled,groupfilter) VALUES('', '','',0,''); INSERT INTO AnsibleForms.groups(name) VALUES('admins'); -INSERT INTO AnsibleForms.awx(uri,token,username,password) VALUES('','','',''); +INSERT INTO AnsibleForms.awx(uri,token,username,password,ignore_certs,use_credentials,ca_bundle) VALUES('','','','',0,0,''); INSERT INTO AnsibleForms.users(username,password,email,group_id) VALUES('admin','$2b$10$Z/W0HXNBk2aLR4yVLkq5L..C8tXg.G.o1vkFr8D2lw8JSgWRCNiCa','',1); INSERT INTO AnsibleForms.ldap(server,port,ignore_certs,enable_tls,cert,ca_bundle,bind_user_dn,bind_user_pw,search_base,username_attribute,enable) VALUES('',389,1,0,'','','','','','sAMAccountName',0); -INSERT INTO AnsibleForms.settings(mail_server,mail_port,mail_secure,mail_username,mail_password,mail_from,url) VALUES('',25,0,'','','',''); +INSERT INTO AnsibleForms.settings(mail_server,mail_port,mail_secure,mail_username,mail_password,mail_from,url,forms_yaml) VALUES('',25,0,'','','','',''); + +-- enable foreign key checks SET FOREIGN_KEY_CHECKS=1; diff --git a/server/src/db/create_settings_table.sql b/server/src/db/create_settings_table.sql index 2830ea8d..c88d1e6b 100644 --- a/server/src/db/create_settings_table.sql +++ b/server/src/db/create_settings_table.sql @@ -7,6 +7,6 @@ CREATE TABLE `settings` ( `mail_password` text DEFAULT NULL, `mail_from` varchar(250) DEFAULT NULL, `url` varchar(250) DEFAULT NULL, - `forms_yaml` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL, + `forms_yaml` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8; -INSERT INTO AnsibleForms.settings(mail_server,mail_port,mail_secure,mail_username,mail_password,mail_from,url) VALUES('',25,0,'','','','',''); +INSERT INTO AnsibleForms.settings(mail_server,mail_port,mail_secure,mail_username,mail_password,mail_from,url,forms_yaml) VALUES('',25,0,'','','','',''); From 6844b5eb0c5b6b60bd1c4fa924dbd871a91b5bb0 Mon Sep 17 00:00:00 2001 From: Mirko Van Colen Date: Sun, 18 Aug 2024 16:29:25 +0200 Subject: [PATCH 16/21] download log bug fix --- client/src/components/Form.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/components/Form.vue b/client/src/components/Form.vue index 1f05ba6e..2b13eb19 100644 --- a/client/src/components/Form.vue +++ b/client/src/components/Form.vue @@ -1740,7 +1740,7 @@ var ref=this axios({ method: 'get', - headers, + headers: headers.headers, url, responseType: 'arraybuffer', }) From 58ef9b998a20de286780683748bfd27bb965ca5b Mon Sep 17 00:00:00 2001 From: Mirko Van Colen Date: Sun, 18 Aug 2024 16:30:06 +0200 Subject: [PATCH 17/21] fixed use_credentials bug --- server/src/models/schema.model.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/server/src/models/schema.model.js b/server/src/models/schema.model.js index a969955e..4e51f1d3 100644 --- a/server/src/models/schema.model.js +++ b/server/src/models/schema.model.js @@ -158,7 +158,6 @@ async function patchVersion4(messages,success,failed){ await checkPromise(addColumn("ldap","group_member_user_attribute","varchar(250)",true,"NULL"),messages,success,failed) // add column to have group member user attribute await checkPromise(addColumn("ldap","is_advanced","tinyint(4)",true,"0"),messages,success,failed) // is advanced config await checkPromise(addColumn("ldap","mail_attribute","varchar(250)",true,"NULL"),messages,success,failed) // add column to have mail attribute - // also the settings tables was not present before 4.0.0, it contains the URL and mail settings for notification // later the column forms_yaml was added to store the forms in yaml format (but that was in 5.x.x) buffer = fs.readFileSync(`${__dirname}/../db/create_settings_table.sql`) @@ -190,6 +189,9 @@ async function patchVersion5(messages,success,failed){ // on request, the awx_id was added to the jobs table, to later retrieve it for future tracking await checkPromise(addColumn("jobs","awx_id","int(11)",true,"NULL"),messages,success,failed) // add for future tracking + // patch for awx credentials, the use_credentials was added to the awx table, to allow the use of credentials + await checkPromise(addColumn("awx","use_credentials","tinyint(4)",true,"0"),messages,success,failed) // bugfix for awx credentials + // A new feature was added, the repositories table, to store the repositories for the forms and playbooks // A real gamechanger, because now the forms and playbooks can be stored in a git repository buffer = fs.readFileSync(`${__dirname}/../db/create_repositories_table.sql`) From cf701c6d87a16f3d38ce5e0ad00aaa3bdff32c03 Mon Sep 17 00:00:00 2001 From: Mirko Van Colen Date: Sun, 18 Aug 2024 16:31:09 +0200 Subject: [PATCH 18/21] version bump --- server/src/swagger.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/src/swagger.json b/server/src/swagger.json index 0cc39896..458e3b39 100644 --- a/server/src/swagger.json +++ b/server/src/swagger.json @@ -2,7 +2,7 @@ "swagger": "2.0", "info": { "description": "This is the swagger interface for AnsibleForms.\r\nUse the `/auth/login` api with basic authentication to obtain a JWT token.\r\nThen use the access token, prefixed with the word '**Bearer**' to use all other api's.\r\nNote that the access token is limited in time. You can then either login again and get a new set of tokens or use the `/token` api and the refresh token to obtain a new set (preferred).", - "version": "5.0.3", + "version": "5.0.4", "title": "AnsibleForms", "contact": { "email": "info@ansibleforms.com" From fbf6f3cee14639a12cc7670a45b045bac821527e Mon Sep 17 00:00:00 2001 From: Mirko Van Colen Date: Sun, 18 Aug 2024 16:31:41 +0200 Subject: [PATCH 19/21] typo --- docs/_data/help.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/_data/help.yaml b/docs/_data/help.yaml index 332982c6..23b556cf 100644 --- a/docs/_data/help.yaml +++ b/docs/_data/help.yaml @@ -3837,7 +3837,7 @@ 'get', 'https://resturl/api/', '', - {'a_custom_http_header','your_value'}, + {'a_custom_http_header':'your_value'}, '.records[].name', {name:{ignoreCase:true,direction:'desc'}}, false From 2c145ee41cd29ad454ae9954aa81b1080a54a11c Mon Sep 17 00:00:00 2001 From: Mirko Van Colen Date: Sun, 18 Aug 2024 16:32:39 +0200 Subject: [PATCH 20/21] changelog --- CHANGELOG.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 97fc5173..85a93c93 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- If forms.yaml is missing, it will be auto created. This is useful for new installations without docker-compose +- Same for certificates + +### Changed + +- Async await replacements for promises (readability) + +### Fixed + +- Some credentials bugfixes + ## [5.0.3] - 2024-06-21 ### Added From 5efedef044f2c6c873007b0ac85678d92e2017de Mon Sep 17 00:00:00 2001 From: GitHub Actions Date: Sun, 18 Aug 2024 17:27:38 +0000 Subject: [PATCH 21/21] Prepare release 5.0.4 --- CHANGELOG.md | 6 +++++- app_versions.gradle | 4 ++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 85a93c93..ffba79e1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [5.0.4] - 2024-08-18 + ### Added - If forms.yaml is missing, it will be auto created. This is useful for new installations without docker-compose @@ -722,7 +724,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Allow change password for current local user - Start tracking versions -[Unreleased]: https://github.com/ansibleguy76/ansibleforms/compare/5.0.3...HEAD +[Unreleased]: https://github.com/ansibleguy76/ansibleforms/compare/5.0.4...HEAD + +[5.0.4]: https://github.com/ansibleguy76/ansibleforms/compare/5.0.3...5.0.4 [5.0.3]: https://github.com/ansibleguy76/ansibleforms/compare/5.0.2...5.0.3 diff --git a/app_versions.gradle b/app_versions.gradle index 22ed8bcf..fc4569a3 100644 --- a/app_versions.gradle +++ b/app_versions.gradle @@ -1,2 +1,2 @@ -ext.version_code = 50003 -ext.version_name = "5.0.3" +ext.version_code = 50004 +ext.version_name = "5.0.4"