diff --git a/CHANGELOG.md b/CHANGELOG.md index f7ab3d71..e0076661 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [3.0.7] - 2022-09-26 + +### Added + +- Allow secure connection for mysql + +### Changed + +- Updated nodejs packages + ## [3.0.6] - 2022-08-10 ### Fixed @@ -303,7 +313,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/3.0.6...HEAD +[Unreleased]: https://github.com/ansibleguy76/ansibleforms/compare/3.0.7...HEAD + +[3.0.7]: https://github.com/ansibleguy76/ansibleforms/compare/3.0.6...3.0.7 [3.0.6]: https://github.com/ansibleguy76/ansibleforms/compare/3.0.5...3.0.6 diff --git a/app_versions.gradle b/app_versions.gradle index 29e3cbed..bbc0606b 100644 --- a/app_versions.gradle +++ b/app_versions.gradle @@ -1,2 +1,2 @@ -ext.version_code = 30006 -ext.version_name = "3.0.6" +ext.version_code = 30007 +ext.version_name = "3.0.7" diff --git a/client/src/components/BulmaSelect.vue b/client/src/components/BulmaSelect.vue index 0bcd455d..a20509b8 100644 --- a/client/src/components/BulmaSelect.vue +++ b/client/src/components/BulmaSelect.vue @@ -26,8 +26,8 @@ icon:{type:[String,Array],default:''}, label:{type:String,required:true}, list:{type:Array,required:true}, - valuecol:{type:String,required:true}, - labelcol:{type:String,required:true}, + valuecol:{type:String,required:false}, + labelcol:{type:String,required:false}, hasError:{type:Boolean,default:false}, errors:{type:Array}, },data(){ diff --git a/client/src/views/Credentials.vue b/client/src/views/Credentials.vue index fece0400..6272a9db 100644 --- a/client/src/views/Credentials.vue +++ b/client/src/views/Credentials.vue @@ -17,11 +17,12 @@ :columns="['name','user','host']" :filters="['name','user','host']" identifier="id" - :actions="[{name:'select',title:'edit credential',icon:'pencil-alt',color:'has-text-warning'},{name:'delete',title:'delete credential',icon:'times',color:'has-text-danger'}]" + :actions="[{name:'select',title:'edit credential',icon:'pencil-alt',color:'has-text-warning'},{name:'delete',title:'delete credential',icon:'times',color:'has-text-danger'},{name:'test',title:'test credential',icon:'database',color:'has-text-link'}]" :currentItem="credentialItem" @select="selectItem" @reset="resetItem" @delete="deleteItem" + @test="testItem" /> @@ -32,6 +33,8 @@ + +

@@ -49,6 +52,8 @@ import BulmaAdminTable from './../components/BulmaAdminTable.vue' import BulmaInput from './../components/BulmaInput.vue' import BulmaModal from './../components/BulmaModal.vue' + import BulmaCheckbox from './../components/BulmaCheckRadio.vue' + import BulmaSelect from './../components/BulmaSelect.vue' import TokenStorage from './../lib/TokenStorage' import { required, email, minValue,maxValue,minLength,maxLength,helpers,requiredIf,sameAs,numeric } from 'vuelidate/lib/validators' @@ -59,7 +64,7 @@ authenticated:{type:Boolean}, isAdmin:{type:Boolean} }, - components:{BulmaButton,BulmaInput,BulmaModal,BulmaAdminTable}, + components:{BulmaButton,BulmaInput,BulmaModal,BulmaAdminTable,BulmaCheckbox,BulmaSelect}, data(){ return { credential:{ @@ -68,7 +73,9 @@ password:"", host:"", port:3306, - description:"" + description:"", + secure:false, + db_type:"" }, showDelete:false, credentialItem:undefined, @@ -104,6 +111,21 @@ this.selectItem(value) this.showDelete=true }, + testItem(value){ + var ref= this; + if(value){ + axios.get('/api/v1/credential/testdb/' + value,TokenStorage.getAuthentication()) + .then((result)=>{ + if(result.data.status=='success'){ + ref.$toast.success(result.data.message) + }else{ + ref.$toast.error(result.data.message + "\r\n" + result.data.data.error) + } + }),function(error){ + ref.$toast.error(error.message); + }; + } + }, loadCredential(){ var ref= this; if(this.credentialItem!=undefined && this.credentialItem!=-1){ @@ -121,7 +143,8 @@ name:"" } } - },deleteCredential(){ + }, + deleteCredential(){ var ref= this; axios.delete('/api/v1/credential/'+this.credentialItem,TokenStorage.getAuthentication()) .then((result)=>{ diff --git a/server/src/controllers/credential.controller.js b/server/src/controllers/credential.controller.js index 3a686039..bab164d9 100644 --- a/server/src/controllers/credential.controller.js +++ b/server/src/controllers/credential.controller.js @@ -1,6 +1,9 @@ 'use strict'; const Credential = require('../models/credential.model'); var RestResult = require('../models/restResult.model'); +const mysql=require("../lib/mysql") +const postgres=require("../lib/postgres") +const mssql=require("../lib/mssql") exports.find = function(req, res) { if(req.query.name){ @@ -49,3 +52,30 @@ exports.delete = function(req, res) { .then(()=>{res.json(new RestResult("success","credential deleted",null,""))}) .catch((err)=>{res.json(new RestResult("error","failed to delete credential",null,err))}) }; +exports.testDb = function(req,res){ + Credential.findById(req.params.id) + .then((cred)=>{ + var db_type = cred[0].db_type + if(db_type=='mysql'){ + return mysql.query(cred[0].name,'select 1') + }else if(db_type=='mssql'){ + return mssql.query(cred[0].name,'select 1') + }else if(db_type=='postgres'){ + return postgres.query(cred[0].name,'select 1') + }else if(db_type=='mongodb'){ + throw "Mongodb test is not implemented" + }else{ + throw "Database type not set" + } + }) + .then(()=>{res.json(new RestResult("success","Database connection ok",null,""))}) + .catch((err)=>{ + if(err.includes("not set")){ + res.json(new RestResult("error","Database type not set",null,"")) + }else{ + res.json(new RestResult("error","Database connection failed",null,err)) + } + + }) + +} diff --git a/server/src/db/create_schema_and_tables.sql b/server/src/db/create_schema_and_tables.sql index 4e32c606..34c3876b 100644 --- a/server/src/db/create_schema_and_tables.sql +++ b/server/src/db/create_schema_and_tables.sql @@ -35,6 +35,8 @@ CREATE TABLE `credentials` ( `host` varchar(250) DEFAULT NULL, `port` int(11) DEFAULT NULL, `description` text NOT NULL, + `secure` tinyint(4) DEFAULT NULL, + `db_type` varchar(10) DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `uk_AnsibleForms_credentials_natural_key` (`name`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; diff --git a/server/src/lib/mysql.js b/server/src/lib/mysql.js index c67547df..5896e529 100644 --- a/server/src/lib/mysql.js +++ b/server/src/lib/mysql.js @@ -15,6 +15,17 @@ MySql.query=function(connection_name,query){ try{ logger.debug(`[${connection_name}] connection found : ${config.name}`) config.multipleStatements=true + if(config.secure){ + config.ssl={ + sslmode:"required", + rejectUnauthorized:false + } + }else{ + config.ssl={ + sslmode:"none", + rejectUnauthorized:false + } + } // get connection conn = client.createConnection(config) }catch(err){ diff --git a/server/src/models/credential.model.js b/server/src/models/credential.model.js index 1c138294..413aa290 100644 --- a/server/src/models/credential.model.js +++ b/server/src/models/credential.model.js @@ -16,8 +16,10 @@ var Credential=function(credential){ this.host = credential.host; this.port = credential.port; this.user = credential.user; + this.secure = (credential.secure)?1:0; this.password = encrypt(credential.password); this.description = credential.description; + this.db_type = credential.db_type; }; Credential.create = function (record) { @@ -39,7 +41,7 @@ Credential.delete = function(id){ }; Credential.findAll = function () { logger.info("Finding all credentials") - return mysql.do("SELECT id,name,user,host,port,description FROM AnsibleForms.`credentials`;") + return mysql.do("SELECT id,name,user,host,port,description,secure,db_type FROM AnsibleForms.`credentials`;") }; Credential.findById = function (id) { logger.info(`Finding credential ${id}`) @@ -79,7 +81,7 @@ Credential.findByName = function (name) { logger.debug(`Finding credential ${name}`) var cred = cache.get(name) if(cred==undefined){ - return mysql.do("SELECT host,port,name,user,password FROM AnsibleForms.`credentials` WHERE name=?;",name) + return mysql.do("SELECT host,port,name,user,password,secure,db_type FROM AnsibleForms.`credentials` WHERE name=?;",name) .then((res)=>{ if(res.length>0){ res[0].multipleStatements = true diff --git a/server/src/models/db.model.js b/server/src/models/db.model.js index bd776151..32c2a320 100644 --- a/server/src/models/db.model.js +++ b/server/src/models/db.model.js @@ -37,36 +37,6 @@ MySql.do=function(query,vars){ } }) }; -// MySql.query=function(query,vars,callback){ -// logger.info("[ansibleforms] running query : " + query) -// var conn -// try{ -// var conn = client.createConnection(dbConfig) -// }catch(err){ -// logger.error("[ansibleforms] Connection error : " + err) -// callback(null,null) -// return; -// } -// try{ -// conn.query(query,vars,function(err,result){ -// // logger.debug("[ansibleforms] Closing connection") -// conn.end() -// if(err){ -// logger.error("[ansibleforms] Query error : " + err) -// callback(err,null) -// }else{ -// logger.debug("[ansibleforms] query result : " + JSON.stringify(result)) -// callback(null,result) -// } -// }) -// }catch(err){ -// // logger.debug("[ansibleforms] Closing connection") -// conn.end() -// logger.error("[ansibleforms] " + err) -// callback(null,null) -// } -// -// }; module.exports = MySql diff --git a/server/src/models/schema.model.js b/server/src/models/schema.model.js index 2769983c..188ff70c 100644 --- a/server/src/models/schema.model.js +++ b/server/src/models/schema.model.js @@ -204,6 +204,8 @@ function patchAll(){ tablePromises.push(addColumn("jobs","parent_id","int(11)",true,"NULL")) // add for multistep tablePromises.push(renameColumn("jobs","playbook","target","VARCHAR(250)")) // better column name tablePromises.push(addColumn("jobs","step","varchar(250)",true,"NULL")) // add column to hold current step + tablePromises.push(addColumn("credentials","secure","tinyint(4)",true,"0")) // add column to have secure connection + tablePromises.push(addColumn("credentials","db_type","varchar(10)",true,"NULL")) // add column to have db type buffer = fs.readFileSync(`${__dirname}/../db/create_settings_table.sql`) sql = buffer.toString() tablePromises.push(addTable("settings",sql)) // add settings table diff --git a/server/src/routes/credential.routes.js b/server/src/routes/credential.routes.js index 57ccdcb4..7e1c15e8 100644 --- a/server/src/routes/credential.routes.js +++ b/server/src/routes/credential.routes.js @@ -12,4 +12,6 @@ router.put('/:id', credentialController.update); // Delete a credential with id router.delete('/:id', credentialController.delete); +router.get('/testdb/:id', credentialController.testDb) + module.exports = router diff --git a/server/src/swagger.json b/server/src/swagger.json index c6e060a9..ffea8fa9 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": "3.0.6", + "version": "3.0.7", "title": "AnsibleForms", "contact": { "email": "info@ansibleforms.com" @@ -1997,6 +1997,54 @@ } } }, + "/credential/testdb/{credentialId}": { + "get": { + "tags": [ + "credentials" + ], + "security": [ + { + "bearerAuth": [] + } + ], + "summary": "Test a credential against a database", + "parameters": [ + { + "in": "path", + "name": "credentialId", + "type": "integer", + "required": true, + "description": "Numeric ID of the credential to get." + } + ], + "produces": [ + "application/json" + ], + "responses": { + "200": { + "description": "successful operation", + "schema": { + "type": "object", + "example": { + "status": "success", + "message": "Mysql connection success", + "data": { + "output": "", + "error": "" + } + } + } + }, + "401": { + "description": "unauthorized", + "schema": { + "type": "string", + "example": "Authorize with a valid Bearer access token" + } + } + } + } + }, "/user": { "get": { "tags": [