Compare commits

..

403 Commits

Author SHA1 Message Date
Drasko DRASKOVIC 4efd25eed5 Bump Mainflux version to 0.12.0 (#1399)
Signed-off-by: Drasko DRASKOVIC <drasko.draskovic@gmail.com>
2021-03-29 12:13:20 +02:00
Drasko DRASKOVIC 6b6f18317f Add CHANGELOG for 0.12.0 (#1398)
Signed-off-by: Drasko DRASKOVIC <drasko.draskovic@gmail.com>
2021-03-29 00:57:08 +02:00
Mirko Teodorovic 0cdcf28683 MF-1394 - SDK groups (#1396)
* remove owner id

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix groups sdk

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix groups sdk

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix groups sdk

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* small fix, revert some changes

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix test fail

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix unassign request

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* decouple util method form group repo

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* resolve comments

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix param parsing

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>
2021-03-26 22:51:02 +01:00
Mirko Teodorovic f9f51470b1 NOISSUE - fix response for passwd endpoints (#1393)
* fix response for passwd endpoints

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix test for passwd related responses

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix test for passwd related responses

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>
2021-03-23 17:43:06 +01:00
Mirko Teodorovic e87715ba31 dont retrieve groups (#1392)
Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>
2021-03-23 12:20:56 +01:00
Manuel Imperiale 9245e2593b MF-1368 - Add internal http api package for query params reading (#1384)
* MF-1368 - Add internal http api package for query params reading

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix comments

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix comments

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix reviews

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Use internal/http and internalhttp alias

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Mv errors types to pkg

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Use httputil/query.go and remove aliases

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Add blank lines after error definitions

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Add ReadBoolValueQuery

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Mv readBoolValueQuery

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* User ErrNotFoundParam instead of pointer

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Revert ReadUintQuery to use default values

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Use default values for all query readers

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2021-03-23 11:48:05 +01:00
Manuel Imperiale bcdc6d21f6 MF-1390 - Fix docker-compose env_file (#1391)
* MF-1390 - Fix docker-compose env_file

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Update README

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2021-03-23 11:39:40 +01:00
Ivan Milošević 0f3a262ca3 put order direction in response body (#1387)
Signed-off-by: Ivan Milosevic <iva@blokovi.com>
2021-03-15 19:55:57 +01:00
Mirko Teodorovic 74aa93fbb6 NOISSUE - Certs service refactor (#1369)
* remove owner id

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add certs mock

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* remove not wanted changes

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* refactor certs

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* addint tests

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* addint tests

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* adding tests

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add certs test

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add certs test

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add cert test, remove default implementation

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix default value for vault host

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add cert test, remove default implementation

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* linter cleaning

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix comments, and logging

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* use mocks from other services

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* rename struct and url path params

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* resolve minor comments

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* resolve comments

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* align url params naming

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* resolve comments

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* resolve comments

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix typo

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* resolve comments

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* remove struct revoke

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* refactor certRes

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>
2021-03-15 12:27:32 +01:00
Ivan Milošević 30ba38c919 MF-1357 - Add new endpoint for searching things (#1383)
* Add new enpoint for thing search

Signed-off-by: Ivan Milosevic <iva@blokovi.com>

* Rename endpoint to /search
Use same request as list endpoint

Signed-off-by: Ivan Milosevic <iva@blokovi.com>

* Add optional parameters in body (offset, limit)
Add swagger file

Signed-off-by: Ivan Milosevic <iva@blokovi.com>

* move all parameters into body

Signed-off-by: Ivan Milosevic <iva@blokovi.com>

* fix swagger

Signed-off-by: Ivan Milosevic <iva@blokovi.com>

* fix error description

Signed-off-by: Ivan Milosevic <iva@blokovi.com>

* Add tests

Signed-off-by: Ivan Milosevic <iva@blokovi.com>

* remove dead code
fix tests

Signed-off-by: Ivan Milosevic <iva@blokovi.com>

* remove unused var

Signed-off-by: Ivan Milosevic <iva@blokovi.com>

* fix sdk tests

Signed-off-by: Ivan Milosevic <iva@blokovi.com>

* add url endpoint for search test

Signed-off-by: Ivan Milosevic <iva@blokovi.com>

* description in swagger
fix tracer string
change test offset

Signed-off-by: Ivan Milosevic <iva@blokovi.com>

* rename in tests searchThReq to searchThingReq

Signed-off-by: Ivan Milosevic <iva@blokovi.com>
2021-03-11 10:28:44 +01:00
Ivan Milošević a1e18a770a init commmit (#1385)
Signed-off-by: Ivan Milosevic <iva@blokovi.com>
2021-03-10 11:52:33 +01:00
Mirko Teodorovic 259950b009 NOISSUE - Add missing auth port in nginx enrypoint.sh (#1380)
* remove owner id

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix auth http port

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>
2021-03-04 19:02:59 +01:00
Mirko Teodorovic 530f925c4d MF-1346 - Create Groups API - add grouping of entities (#1334)
* remove owner id

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add users endpoint for retrieving users from group

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* remove  groups from things and users

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* move groups into auth

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* separate endpoints for users and things

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix problems with retrieving members

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add groups test

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* remove groups from users

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* remove groups from things

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* rename constant

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add new errors

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* remove unnecessary constants

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix validation

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* create groups db mock

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* adding tests

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* revert changes to docker related files

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* remove groups endpoints from users openapi

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* remove groups endpoints from users openapi

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* move constant from postgres to groups

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* move constant from postgres to groups

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* move constant from postgres to groups

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* remove testing group

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* renam typ to groupType

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add error for max level

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* remove print

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* remove groups.Member interface

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix query building and add test cases

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* uncomment tests

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* move groups package

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* remove group type, add bulk assign and unassign

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* update openapi, remove parentID from create request, reorder endpoints

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* update openapi

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* update openapi for users and things

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix groups test

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix linter errors

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* resolve comments

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* rename assignReq structure

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* refactor mocks, response, remove type from endpoint

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* some refactor, renaming, errors

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* simplify check

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* remove package alias

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix naming and comment

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* additional comments

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add members grpc endpoint test

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix retrieving members for different types

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix retrieving members for different types

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* remove unecessary structure

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix api grpc

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* rename const

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* refactore retrieve parents and children with common function

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* small changes for errors

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix compile error

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix sorting in mock

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* remove regexp for groups

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* revert as change is made by mistake

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* revert as change is made by mistake

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* refactor groups and keys package

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix naming

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix naming

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix test for timestamp compare

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix error handling

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* remove errors not being used

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* var renaming

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* resolve comments

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* minor changes

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix test

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add endpoints for groups into nginx

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* reorganize endpoints, remove some errors

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* reorganize endpoints, remove some errors

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* small fix

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix linter errors

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* minor changes

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* resolve comments

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix group save path problem

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* description constant

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* rename variables

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix validation

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* get back return

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix compile

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>
2021-03-04 10:29:03 +01:00
Mirko Teodorovic 6b1f4d54f8 NOISSUE - Fix certs and vault deployment, reorganize and remove unnecessary vars (#1368)
* remove owner id

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix certs, fix scripts, reorganize env vars

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* set pki path vars

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add certs mock

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* remove not wanted changes

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* update readme

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* correct rsa_bits to key_bits

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix tabulation

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* hardcode vault version

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add env desc

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* renam variables

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* use Mainflux Labs

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* replace BG with Belgrade

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>
2021-03-02 12:20:52 +01:00
Ivan Milošević 7bcaa323d4 MF-1317 - Configurable regexp rule for password (#1355)
* read and validate regex envar

Signed-off-by: Ivan Milosevic <iva@blokovi.com>

* pass regexp to user/api

Signed-off-by: Ivan Milosevic <iva@blokovi.com>

* resolve conflicts

Signed-off-by: Ivan Milosevic <iva@blokovi.com>

* use exported regexp variable

Signed-off-by: Ivan Milosevic <iva@blokovi.com>

* move password validation from users package

Signed-off-by: Ivan Milosevic <iva@blokovi.com>

* remove dead code

Signed-off-by: Ivan Milosevic <iva@blokovi.com>

* add password change request

Signed-off-by: Ivan Milosevic <iva@blokovi.com>

* move regexp from api to users package

Signed-off-by: Ivan Milosevic <iva@blokovi.com>

* fix tests

Signed-off-by: Ivan Milosevic <iva@blokovi.com>

* remove commented code

Signed-off-by: Ivan Milosevic <iva@blokovi.com>

* add regexp as field in userService, remove it as user exported global var

Signed-off-by: Ivan Milosevic <iva@blokovi.com>

* add passwd validation in service

Signed-off-by: Ivan Milosevic <iva@blokovi.com>

* Add psswd validation for change password in service

Signed-off-by: Ivan Milosevic <iva@blokovi.com>

* add password validation in password reset

Signed-off-by: Ivan Milosevic <iva@blokovi.com>

* Remove password validation from user validation test

Signed-off-by: Ivan Milosevic <iva@blokovi.com>

* Replace email and passwords in test with constants

Signed-off-by: Ivan Milosevic <iva@blokovi.com>

* compile error not fail silently

Signed-off-by: Ivan Milosevic <iva@blokovi.com>

* fix tempate path

Signed-off-by: Ivan Milosevic <iva@blokovi.com>
2021-03-01 15:22:57 +01:00
Dušan Borovčanin e334569d81 Fix CoAP Adapter README (#1376)
Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>
2021-03-01 12:25:39 +01:00
Mirko Teodorovic 0432b2aa03 NOISSUE - Fix default values for port and x509 provision (#1367)
* remove owner id

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix default port and certs settings

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* small typo fix

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* refactor env vars

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* revert users

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>
2021-02-27 01:01:48 +01:00
Filip Bugarski 56d04cda64 NOISSUE - Added missing endpoints for users service (#1372)
* Added missing endpoints for user's service

Signed-off-by: Filip Bugarski <filipbugarski@gmail.com>

* Type fix

Signed-off-by: Filip Bugarski <filipbugarski@gmail.com>
2021-02-25 17:19:35 +01:00
Nikola Marčetić e01874e557 MF-1365 - Add ADOPTERS.md file (#1371)
* Added ADOPTERS.md file

Signed-off-by: Nikola Marcetic <n.marcetic86@gmail.com>

* Add Note

Signed-off-by: Nikola Marcetic <n.marcetic86@gmail.com>

* Remarks resolved

Signed-off-by: Nikola Marcetic <n.marcetic86@gmail.com>
2021-02-25 13:46:07 +01:00
Darko Draskovic 9e532d2a72 Fix grpc endpoint parameter permutation (#1370)
Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>
2021-02-24 13:23:50 +01:00
Darko Draskovic 7ba3fdb3bf NOISSUE - Add IsChannelOwner grpc endpoint (#1366)
* Add CanAccessChannelByOwner grpc endpoint

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Rename grpc endpoint to IsChannelOwner

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add tests for IsChannelOwner grpc

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Bump protoc version

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Make proto

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>
2021-02-22 19:41:59 +01:00
Manuel Imperiale 7834cc48b3 MF-1362 - Sort Things and Channels connections by name (#1363)
* MF-1362 - Sort Things and Channels connections by name

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix reviews

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Remove test

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix reviews

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Add order and direction tests

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2021-02-17 16:21:40 +01:00
Manuel Imperiale f0f60e2d2a MF-1314 - Add value comparison filters for readers (#1353)
* MF-1314 - Add value comparison filters for readers

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Check if comparison parameter is valid

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Use eq, lt, lte, gt, gte as comparison operator keys

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Use consts for comparison operators

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Use comparator naming instead of comparison

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix openapi.yml

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix typo

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2021-02-09 22:44:04 +01:00
Dušan Borovčanin 24b902d049 Fix env configuration and documentation (#1360)
Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>
2021-02-08 22:38:52 +01:00
Dušan Borovčanin 13c426c09c NOISSUE - Support disabling Email Agent authentication (#1356)
* Fix mailer configuration

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Remove unused Driver option

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Add Email Agent README

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>
2021-02-08 13:21:52 +01:00
Manuel Imperiale ca5ff63dfb NOISSUE - Upgrade Mongo, Cassandra and Influx docker images (#1354)
* NOISSUE - Upgrade Mongo, Cassandra and Influx docker images

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Use cassandra version 3.11.9

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Add MF_INFLUXDB_HTTP_AUTH_ENABLED envar

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Use influxdb 1.8.4-alpine

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Use influxdb 1.8.4

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2021-02-05 12:32:21 +01:00
Darko Draskovic 8b2ae46324 Add READMEs to pkg packages (#1352)
Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>
2021-02-04 20:04:41 +01:00
Drasko DRASKOVIC 241ccf8fb4 Correct README (#1349)
Signed-off-by: Drasko DRASKOVIC <drasko.draskovic@gmail.com>
2021-02-02 17:18:12 +01:00
Mirko Teodorovic 39a649c1bb MF-1342 - Use environment variables in docker-compose to use tagged version of image (#1343)
* add MF_RELEASE_TAG

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* update readme file for MF_RELEASE_TAG

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* update readme file for MF_RELEASE_TAG

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix explanation, space

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix bad search/replace

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix bad search/replace

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* minor changes

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix readme

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* improve explanation for tag

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* replace snippet with link to code section

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* improve explanation for tag

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* replace snippet with link to code section

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* remove MF_RELEASE_TAG from service readme

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>
2021-02-02 16:26:02 +01:00
Dušan Borovčanin a3dbfecb2d MF-1311 - Add Notifications service (#1324)
* Add repo to Notify

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Remove notify

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Add initial repo implementation

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Add Subscriptions repo implementation

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Add working version of main method

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Add Notifier API

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Add API layer

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Add Notifier interface

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Fix SMTP notifier main

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Fix email sending

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Fix main log

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Add Readme and fix env vars

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Update README

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Remove commented code

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Rename notifiers to notify

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Move Consumer API to writers

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Fix contact naming

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Fix spacing in init.go

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Fix API

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Add mocks

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Add initial service test

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Use pagination

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Update List method

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Fix listing

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Rename SubscriptionPage to Page

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Finish service tests

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Add DB tests

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Add API tests

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Add more API tests

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Remove Things dependency

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Add API docs

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Add docs

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Fix Things references

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Fix AuthN typo

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Fix linter warnings

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Add license comments

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Fix test order

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Use service error for wrappers

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Fix repo setup test

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Use total const in tests

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Use constant contacts

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Fix code formatting

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Rename notifier implementation

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Rename Notify to Notifiers

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Resolve remarks

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Fix naming and docs

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Fix docker-compose

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Rename Subscriptions to Notifiers service

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Fix logging

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Fix formatting and typo

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Resolve remarks

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Fix topic request

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>
2021-02-01 14:08:10 +01:00
Jonathan Dreyer 73c175020e MF-1344 - Fix links to API documentations #1345
Signed-off-by: Jonathan Dreyer <jonathan.dreyer@he-arc.ch>
2021-01-31 20:25:19 +01:00
Manuel Imperiale 1bf485b71b NOISSUE - Upgrade influxdb and postgres docker images (#1341)
* NOISSUE - Upgrade influxdb and postgres docker images

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Upgrade grafana version

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Upgrade influxdb to 1.8.3-alpine

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2021-01-29 12:37:50 +01:00
Manuel Imperiale 8c87ee8328 NOISSUE - Revert cli to use user token from command args (#1339)
* NOISSUE - Revert cli to use user token from command args

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Remove CLI binary

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Add offset, limit, name and rawOutput to config file

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Revert cli.ParseConfig()

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Check config values before to use them

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2021-01-28 11:29:32 +01:00
Manuel Imperiale 0631900d5c MF-1276 - Fix openapi IDs and Keys format (#1338)
* MF-1276 - Fix openapi IDs and Keys format

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix jwt

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix acces-by-key

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2021-01-27 11:42:55 +01:00
Manuel Imperiale 4619576e94 MF-1061 - Add PageMetadata to readers (#1333)
* MF-1061 - Add PageMetadata to readers

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix merge conflicts

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix typo

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Mv Total to MessagesPage

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix review

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix readers mock and add filters tests

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Add Total check and allow combinations of query parameters

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Use slices length as Total

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Simplify readers mock

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Add empty lines

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2021-01-26 12:23:15 +01:00
Dušan Borovčanin 31f5bf714a NOISSUE - Fix run script and compiler warnings (#1336)
* Fix NATS server naming in run script

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Fix warnings

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Fix typo

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>
2021-01-26 01:07:06 +01:00
Dušan Borovčanin bf5ede086a Fix Postgres writer transaction handling (#1335)
Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>
2021-01-23 02:03:33 +01:00
Dušan Borovčanin 5cfc9305e5 Make Transformer type configurable (#1331)
Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>
2021-01-20 13:10:40 +01:00
Manuel Imperiale 85f73a87b6 MF-1061 - Implement v, vb, vs, vd and from/to mongodb-reader filters (#1326)
* MF-1061 - Implement v, vb, vs, vd and from/to mongodb-reader filters

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Rm duplicated test parameters

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Use pubID2 naming

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix merge

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix typo

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix merge

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Rm unecessary *bson.D pointer

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Use from >= and to <= conditions and add tests

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Revert to half open interva

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2021-01-19 09:30:48 +01:00
Manuel Imperiale 0f856f5667 NOISSUE - Rename package aliases uuidProvider into uuid (#1323)
* NOISSUE - Rename pkg aliases uuidProvider into uuid and fix authn typo

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Add missing error checks

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Use global uuidProvider

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Use expTime globally

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix user uuid provider

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix review

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Use idProvider name

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Use idProvider instead of uuidProvider

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Use idProvider instead of uuidProvider

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2021-01-17 23:12:45 +01:00
Ivan Milošević 0516fe2fd7 MF-1034 - Wrapping MQTT client (#1318)
* use subscribe interface

Signed-off-by: Ivan Milosevic <iva@blokovi.com>

* delete old implementation

Signed-off-by: Ivan Milosevic <iva@blokovi.com>

* add subscribeToLoRaBroker method
change declaration

Signed-off-by: Ivan Milosevic <iva@blokovi.com>

* remove alias for package

Signed-off-by: Ivan Milosevic <iva@blokovi.com>
2021-01-15 23:22:18 +01:00
Manuel Imperiale a8c652f96c MF-1061 - Fix cassandra-reader count for json format (#1327)
Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2021-01-13 10:23:05 +01:00
Manuel Imperiale 8e5a9cfc9a MF-1061 - Implement v, vb, vs, vd and from/to cassandra-reader filters (#1325)
* MF-1061 - Implement v, vb, vs, vd and from/to cassandra-reader filters

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Revert JSON implementation

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2021-01-12 20:39:31 +01:00
Dušan Borovčanin 6b7dc54c8b NOISSUE - Switch to Consumers interface (#1316)
* Replace Writer with Consumer

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Add Notifications package

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Update Consumer Start

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Fix Readers

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Fix Consumer naming

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Add repo to Notify

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Remove notify

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Rename consumer field in middlewares

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Fix remarks and add Readme

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>
2021-01-11 23:55:34 +01:00
Manuel Imperiale 973ca177ea MF-1061 - Implement protocol, name, v, vb, vs, vd and from/to Postgres reader… (#1322)
* MF-1061 - Implement protocol, name, v, vb, vs, vd and from/to postgres-reader filters and tests

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix reviews

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2021-01-11 13:17:12 +01:00
Manuel Imperiale a185855c06 MF-1061 - Add name, protocol and publisher tests to influxdb-reader (#1320)
* MF-1061 - Add name, protocol and publisher tests to influxdb-reader

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix typo

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Use short package aliases

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2021-01-11 10:53:38 +01:00
Manuel Imperiale ac09815457 NOISSUE - Fix Auth typo (#1319)
Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2021-01-11 10:00:13 +01:00
Ivan Milošević 3eca6920e1 NOISSUE - Add health check for MQTT broker (#1305)
* Add health check

Signed-off-by: Ivan Milosevic <iva@blokovi.com>

* update readme
use constants

Signed-off-by: Ivan Milosevic <iva@blokovi.com>

* close body after each HEAD request

Signed-off-by: Ivan Milosevic <iva@blokovi.com>

* log and close health check response body

Signed-off-by: Ivan Milosevic <iva@blokovi.com>

* use healthcheck endpoint instead ping for docker healthcheck

Signed-off-by: Ivan Milosevic <iva@blokovi.com>

* remove for loop, only one check and exit if failed healthcheck

Signed-off-by: Ivan Milosevic <iva@blokovi.com>

* changed cmd for healthcheck
new version of verne image

Signed-off-by: Ivan Milosevic <iva@blokovi.com>

* use false instead exit 1

Signed-off-by: Ivan Milosevic <iva@blokovi.com>

* return for loop

Signed-off-by: Ivan Milosevic <iva@blokovi.com>

* review

Signed-off-by: Ivan Milosevic <iva@blokovi.com>

* add continue after unsuccesful http.get

Signed-off-by: Ivan Milosevic <iva@blokovi.com>

* Use backoff package

Signed-off-by: Ivan Milosevic <iva@blokovi.com>

* extracting healtcheck function outside main()

Signed-off-by: Ivan Milosevic <iva@blokovi.com>

* go mod vendor

Signed-off-by: Ivan Milosevic <iva@blokovi.com>

* go mod tidy

Signed-off-by: Ivan Milosevic <iva@blokovi.com>

* go mod vendor

Signed-off-by: Ivan Milosevic <iva@blokovi.com>
2021-01-05 19:45:09 +01:00
Dušan Borovčanin e326494166 MF-1264 - Add support for JSON readers (#1295)
* MF-1254 - Create universal JSON writer (#1260)

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Add JSON support to Readers

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Fix Influx Reader tests

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Fix messages format query

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Fix Postgres reader

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Fix Cassandra Readers and writers

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Fix Mongo reader

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Extract utility method to the JSON transformer

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Fix Influx and Postgres count

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Update JSON transformer

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Fix Influxdb Reader total count

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Refactor init.go for Cassandra writer

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Create a Payload type

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Add comments for defaults

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Fix variable declarations

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Replace interface{} with a new type

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Don't set channel just to overwrite it later

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Fix range search

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Rename Messages field

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

Co-authored-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2020-12-30 15:43:04 +01:00
Mirko Teodorovic 47217cb5b9 NOISSUE - Merge authz and authn into new service auth (#1313)
* remove owner id

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* move authz into authn and merge into new service

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add groups

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add groups

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add groups endpoints

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add group type

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* adding mocks, some renaming, refactor

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* update proto

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* adding mocks, some renaming, refactor

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix linter err,and comments

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* undo renaming, add interface for authn and authz

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* renam some variables

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* renaming

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* remove extra slashes from comment

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* resolving small remarks

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>
2020-12-29 23:02:35 +01:00
Manuel Imperiale b2ccbaec27 MF-1061 - Implement InfluxDB filters value, v, vb, vs, vd, from, to (#1312)
* MF-1061 - Implement InfluxDB filters value, v, vb, vs, vd, from, to

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Use time filters as float64 instead of int64

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix reviews

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Remove unnecessary cast

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Use a const for limit in tests

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix typo

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Revert float64 cast when dividing

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Remove value filter in favour to v

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Use v, vb, vs, vd

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Use v, vb, vs, vd

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Rm unecessary cast

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2020-12-25 19:23:54 +01:00
Manuel Imperiale cb9985d160 NOISSUE - Correct readers openapi.yml (#1310)
Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2020-12-21 11:45:37 +01:00
Dušan Borovčanin 1810cec82b NOISSUE - Fix MQTT Forwarder client id (#1309)
* Fix MQTT Forwarder client ID

Don't set client ID for MQTT client in MQTT forwarder. It results in
error in case of multiple instances of the MQTT adapter because they all
share MQTT client ID, which makes broker disconnect all the other
adapters.

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Inline broker address option

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Add MQTT client username

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

Co-authored-by: Drasko DRASKOVIC <drasko.draskovic@gmail.com>
2020-12-17 17:17:33 +01:00
Mirko Teodorovic d2af0602a0 NOISSUE - Fix dates not being init properly on save, change path construction, replace UUID with ULID for group ID (#1300)
* fix path, group saving with parent

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* change path, enable name change

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* revert changes for port

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* replace UUID with ULID

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add ulid

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix migrations

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* revert user groups

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add ulid provider

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* enable group name change

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* use null string for parent id

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* remove migrations, disable group delete if group not empty

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* revert docker compose

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add ulid provider

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* remove dash character replacment

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add ulid lib

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* rename UUIDProvider

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* rename package alias

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* remove unused error

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>
2020-12-17 17:08:28 +01:00
Mirko Teodorovic 8aadc0ff40 NOISSUE - Remove authz from docker comp (#1307)
* remove owner id

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* remove authz from docker

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* revert init.go

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>
2020-12-16 18:15:27 +01:00
Darko Draskovic c7d2feb434 Shorten descriptions and add formats (#1306)
Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>
2020-12-16 16:00:56 +01:00
Mirko Teodorovic 18dd8967cc NOISSUE - remove owner id from user table and object (#1303)
* remove owner id

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* remove owner id

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>
2020-12-15 15:57:37 +01:00
Darko Draskovic 9864b27271 NOISSUE - Add missing fields to openapi specs and enclose http codes in single quotes (#1302)
* Add req body to token req

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Shorten summaries and add/remove keys

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add single quotes to html response codes

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>
2020-12-15 00:20:42 +01:00
Manuel Imperiale 3653e6b5d2 MF-1290 - Sort Things and Channels by name (#1293)
* MF-1290 - Sort Things and Channels by name

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Add sort HTTP parameter and use PageMetadata to pass filters

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix channels conn list

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix logs

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Replace sort naming by order

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Add order in responses

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Improve tests

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Improve order tests

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Improve getNameQuery func

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix commits

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Add name-asc and name-desc

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix typo

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Add dir query parameter

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix openapi

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix typo

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix openapi format

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix test messages and openapi.yml

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix openapi uuid format

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Check query parameters in endpoint layer

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Add asc endpoint tests

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Rename pageMeta into pageMetadata

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2020-12-08 21:30:47 +01:00
Mirko Teodorovic fbba7aaa1a MF-1248 - Add access policies for users (#1246)
* authz service init

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* authz service init

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add proto

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add proto

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* authorize method

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add casbib

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add casbib

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add casbib

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add policy grpc

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add policy grpc

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix db connection, add environment variables, docker-compose

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add model.conf policy

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add model.conf policy

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix test

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* adding tests, and token for auth

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* revert changes

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix auth service

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* small changes, add model conf env var

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* remove users grpc for now

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix error

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* some cosmetics

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* resolvin minor comments

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* change endpoint, add file to fix test

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix docker env

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix alignment and path for model.conf

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>
2020-12-08 17:04:09 +01:00
Ivan Savčić 20f5290d7a Fixes, without spaces. (#1296)
Signed-off-by: Ivan Savcic <isavcic@gmail.com>
2020-12-04 18:59:16 +01:00
Darko Draskovic 23bc822433 Add different CNs for CA and certs (#1292)
Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>
2020-11-26 15:32:52 +01:00
Mirko Teodorovic 7195cad0f6 MF-397 - Introduce Thing Groups (#1259)
* add things group

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add things group repository

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add things group repository

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add things group repository

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add parents and children methods

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add parents and children methods

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix logging message and temporary test fix

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix bootstrap test fail

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* move groups to pkg

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* move groups to pkg

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* move groups to pkg

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* move groups to internal

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* move groups to internal

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix import

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix linter errors

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix comments

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* resolve comments, add hierarchy info when retrieving groups

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* code refactor, separate http into multiple files

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* code refactor, separate http into multiple files

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix group update

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* use user id when saving

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* use user id when saving

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* rename methods

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* move code

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* move code

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* remove temporary test

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add groups test

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix comments, fix responses in api for groups

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* revert changes

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

add checks for name length

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

fix validation

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

small change to response

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

add comment for Level

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

add level to limit hierarchy retrieval

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

tidy vendor

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

add level to limit hierarchy retrieval

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

mod tidy

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

revert

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

resolve comments

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

small naming and code organize refactor

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

revert Member type

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

fix typo

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

use ltree

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

use ltree

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

add level for retrieving

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

add level

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

add ltre

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

upgrade postgres version in test

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

add ltre

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

remove test for now

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

minor fixes

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

fix id setting

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

fix tree endpoint

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

minor style changes

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* small changes, adding new lines

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* minor changes

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* change function signature

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* change primary key

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* change function signature

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* simplufy code

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* remove groups test for now

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix tabulation

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix whitespace

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* remove white space, fix grammar

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix sqls so that not additional retrieve is needed

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix sqls so that not additional retrieve is needed

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

fix primary key

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

remove retrive by name

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* remove name and parent update

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix cound sql

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add line

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* simplify sql, fix table name

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add date

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add date

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add timestamp to api

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix var name

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix var name

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>
2020-11-23 11:34:29 +01:00
Dušan Borovčanin 663bd4e18d Add Enhancement section to the issue template (#1284)
Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>
2020-11-17 22:52:56 +01:00
Darko Draskovic c03644524e Fix hardcoded env var values (#1283)
Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>
2020-11-16 20:44:25 +01:00
Dušan Borovčanin f1aa32d89c NOISSUE - Improve AuthN service docs (#1282)
* Update AuthN service README

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Update Authn service docs

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>
2020-11-13 21:46:04 +01:00
João Matos 3273c30d8b MF-1268 - CLI improvements (#1274)
* Prefix error messages in CLI with a bold "error: ".

Signed-off-by: Joao Matos <joao@tritao.eu>

* Remove duplicated "Usage: " from groups command help.

Signed-off-by: Joao Matos <joao@tritao.eu>

* Add a raw output mode for CLI and use it on logCreated.

Signed-off-by: Joao Matos <joao@tritao.eu>

* Add CLI global flag for user auth token.

Signed-off-by: Joao Matos <joao@tritao.eu>

* Add CLI config flag and parsing logic.

Signed-off-by: Joao Matos <joao@tritao.eu>

* Refactor CLI users commands outside array structure.

Signed-off-by: Joao Matos <joao@tritao.eu>

* Refactor CLI certificates commands using flags.

Signed-off-by: Joao Matos <joao@tritao.eu>

* Refactor CLI things create command using flags.

Signed-off-by: Joao Matos <joao@tritao.eu>

Co-authored-by: Drasko DRASKOVIC <drasko.draskovic@gmail.com>
2020-11-01 00:29:06 +01:00
João Matos 46c675cd5f NOISSSUE - Vault integration as an addon. (#1266)
Integrates Vault PKI service as a service addon.

Also adds some helper scripts to help setup the CA in Vault, as well as
some docs to explain how to use them.

Originally based from https://github.com/mteodor/vault.

Signed-off-by: Joao Matos <joao@tritao.eu>

Co-authored-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>
2020-10-31 21:44:25 +01:00
Dušan Borovčanin 02db4066b1 Fix naming in Authn API tests (#1275)
Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>
2020-10-27 20:08:16 +01:00
Dušan Borovčanin f6b1ae735c MF-1244 - Return UserID alongside with user Email in Identify response (#1245)
* Add both an ID and an Email to API key requests

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Use return UserIdentity response

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Replace GetValue with GetEmail

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Refactor Mainflux Key

Add `Subject` field and reorganize Key manipulation.
**Remove backward compatibility**

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Fix service test

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Fix DB tests

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Fix API tests

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Fix JWT tests

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Uncomment and fix API tests

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Fix SQL statements alignment

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Fix Issue method docs

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Fix Retrieve API and API docs

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Update tests

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>
2020-10-27 19:42:53 +01:00
Manuel Imperiale 926e9799d6 NOISSUE - Fix ViewGroup and UpdateGroup (#1269)
* NOISSUE - Fix ViewGroup and UpdateGroup

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Add ID check and fix naming

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix id in groupUpdateReq

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix review

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Add tests

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2020-10-27 11:26:32 +01:00
Manuel Imperiale 1c298d8f27 NOISSUE - Add ListUsers, ViewUser and ViewProfile methods (#1262)
* NOISSUE - Add admin method in users service to return users list

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix loggings and metrics

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Add email and metadata filters

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix typo

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Add comment

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Retrieve User infos by ID if Admin

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Remove admin checks and fix comments

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix missing query

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Use generic funccs to create email and metadata queries

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Add /users/profile endpoint

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Simplify db helpers

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix View, List, Retrieve prefix methods naming

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix tracer endpoints naming

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix comment

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix typo

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix typo

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Add tests and remove TODO comments

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2020-10-26 10:17:08 +01:00
Manuel Imperiale 420b598ac7 NOISSUE - Rm users http package (#1256)
* NOISSUE - Mv users http gokit layers to http package

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Remove users/api/http dir

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2020-10-16 18:00:24 +02:00
Manuel Imperiale 86fe3f7cdb NOISSUE - Remove content-type check from decodeListUserGroupsRequest (#1255)
Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2020-10-16 12:54:27 +02:00
Darko Draskovic 3c794d4a03 NOISSUE - Migrate swaggers to openapi 3 spec (#1250)
* Migrate authn swagger to openapi 3

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Migrate http swagger to openapi 3

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Migrate bootstrap swagger to openapi 3

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Fix users spec parameters indentation

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Refactor user svc errs to return input related errors

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Migrate certs swagger to openapi 3

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Migrate provision swagger to openapi 3

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Migrate provision swagger to openapi 3

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Migrate readers swagger to openapi 3

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Migrate twins swagger to openapi 3

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Refactor and rename bootstrap swagger

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Rename certs, http, provision and readers swagger to openapi

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Refactor and rename user swagger to openapi

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Refactor twins swagger and rename to openapi

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Refactor things swagger and rename to openapi

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Cleanup whitespace

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>
2020-10-09 15:25:09 +02:00
Dušan Borovčanin 8dd59f8557 Update MQTT Broker Docker scripts (#1253)
Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>
2020-10-09 11:57:08 +02:00
Ivan Milošević bc41c32cf5 update mproxy version (#1251)
Signed-off-by: Ivan Milošević <iva@blokovi.com>
2020-10-09 01:55:09 +02:00
Mirko Teodorovic 42e4e054c2 NOISSUE - Fix group retrieval when parent id is not specified (#1247)
* fix group retrieval

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* refactor for better readability

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>
2020-10-01 17:03:35 +02:00
Mirko Teodorovic 9ed5f8334f NOISSUE - Add new endpoint to retrieve configuration to be used as a template. (#1242)
* add provision service

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix code style

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix test for provision

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* extra line

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* return map[string]interface instead of interface

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>
2020-09-29 10:25:26 +02:00
Mirko Teodorovic 8ea26c5ab7 NOISSUE - Add user groups (#1228)
* adding group

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* adding user group

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* adding group

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add groups

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add groups

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add retrieve methods

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add default admin user

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add default admin user

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* adding endpoints

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* adding endpoints

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* adding tests

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* changes signature for AssignUser

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* adding tests

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* bug fixing retrieving groups

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* remove unused code

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* bug fixing retrieving groups

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* retrieve groups

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* change environment for admin

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* change environment for admin

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* retrieve groups

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* remove adding default group

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* expose port for debugging purposes

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix tests, and linter errors

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add prefix Users for groups endpoint

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix linter problems

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix endpoint prefix url

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix endpoint test

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add group features in cli

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* remove comments

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* remove println

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* when user is created return id in response

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* when user is created return id in response

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* adding default admin env

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* proper alignment

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* proper alignment

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix comments

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* rename  method

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* return user id when created

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* return user id when created

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* remove unused variable

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* rename methods

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix to retrieve whole tree starting from parent

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add endpoint to list groups for user

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add readme for groups

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fixing bugs

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fixing bugs

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add group commands for add and remove user

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* replace default email, use example.com

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix capital letters beginning of sentence

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* remove warning for deprecated api, mistakenly copied

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* simplify repo methods, rely on db driver rather than the check before operation

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* check if group is valid

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* openapi spec 3.0

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* remove check for existing users in groups before delete

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* renaming methods

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* renaming methods

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* renaming methods

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* change func signature

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* change func signature

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix bugs, resolve comments

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix bugs, resolve comments

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix alignment

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add missing command

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* reorganize envs

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix doc

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix compile

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* reorganize cli commands

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* minor corrections

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* renaming

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* renaming

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* renaming

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* rename methods

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix naming

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* renaming

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* renaming

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* resolve comments, minor changes

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>
2020-09-23 23:18:53 +02:00
Darko Draskovic 043d1e0aac MF-1237 - Return to transport only things service errors (#1236)
* Replace error messages for things and channels with error messages for entities

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add db and cache errors

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Migrate swagger to openapi

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Remove db and cache specific err msgs and generalize errs

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Redefine status codes

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Define endpoint error codes in service and swagger

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Fix tests

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Refactor sdk errors

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Fix grpc tests

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Reformat and add err check

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>
2020-09-23 15:44:39 +02:00
Dušan Borovčanin f10e49e6b5 MF-928 - Change CoAP lib (#1233)
* Switch CoAP lib

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Revert removed adapter code

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* WIP CoAP refactor

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Add auth key

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Fix observers map

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Fix reading message body

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Fix subtopic parsing

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Fix error handling

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Fix multi-protocol communication

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Separate client from observer

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Remove unused config

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Remove TCP option

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Inline error check

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Add logging client errors

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Replace RWMutex since we're not using RLock

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Inline error handling

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Inline error handling

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>
2020-09-22 11:59:10 +02:00
Manuel Imperiale f18f2c1c98 NOISSUE - Simplify make cleandocker (#1230)
Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2020-09-09 22:32:05 +02:00
Darko Draskovic dfa6d8ba0d [NOISSUE] - Fix malformed Swagger API specs (#1229)
* Fix authn swagger

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Fix certs swagger

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Fix http swagger

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Fix provision swagger

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Fix readers swagger

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Fix things swagger

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Fix users swagger

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add missing description strings and improve naming in definitions

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>
2020-09-09 13:05:36 +02:00
Al Hoang e272d9f7ad MF-435 - Add support for env file loading (#1223)
* NO-ISSUE - add support for env file loading

Signed-off-by: Al Hoang <hoanga@gmail.com>

* update go mod

Signed-off-by: Al Hoang <hoanga@gmail.com>

Co-authored-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>
2020-09-07 15:45:36 +02:00
Mirko Teodorovic 26c944b5c3 update certs docs (#1227)
Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>
2020-08-07 17:35:11 +02:00
Mirko Teodorovic 3694a82de6 NOISSUE - Fix certs update in bootstrap config and make content handling in config.toml user friendly (#1221)
* adding certificate issuing

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add certs update

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix minor comments, default env vars, add agent and export config into config.toml for provision

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* simplify check

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>
2020-07-29 14:20:43 +02:00
Ivan Milošević 930f8beb29 NOISSUE - Fix typo in authorization.js (#1226)
Signed-off-by: Ivan Milošević <iva@blokovi.com>
2020-07-28 11:09:18 +02:00
Manuel Imperiale 9334568ba2 MF-983 - Add HTTP query param to connections list endpoints to fetch disconnected Things or Channels (#1217)
* MF-983 - Add HTTP query param to connections list endpoints to fetch non connected Things or Channels

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix reviews

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix typos and add Swagger

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Update SDK and CLI

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix typo

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Simplify queries

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix reviews

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix tabulation

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix reviews

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix reviews

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2020-07-26 22:47:15 +02:00
Mirko Teodorovic b91024465c MF-1179 - Add a certificate service and certs endpoint to SDK (#1188)
* adding certificate issuing

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* adding cert endpoint

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* update envs

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* update envs

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* move certs creation to sdk

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* move certs creation to sdk

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* move certs creation to sdk

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix env vars

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add comment

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* update sdk

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix vars

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add volumes

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix merge config for int

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* remove env

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix error handling

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add cert test, change receiver to pointer

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add docs

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix var naming

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* correct error naming

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* adding certs service

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add certs

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add certs

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* change func receiever

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add default cert issue method

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add config

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* small fix

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* remove some testing code

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add cert issue

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add vault api client

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* additional endpoints

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add swagger for certs

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* remove certs from provision

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* clean provision from certs

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add list certificates endpoint

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add vault api in vendor

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add certs

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add revoke, fix bugs

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix sdk for certs

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* minor changes, add env, doc

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* minor changes, add env, doc

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* minor changes, add env, doc

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* small changes

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* remove CA for signing from provision

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add docker file for certs

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix mock sdk

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add line

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix RevokeCert

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* renam ENV

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* remove tests temporarily

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix naming

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* renam vars

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add cli for issue cert

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add cli for issue cert

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add cli for issue cert

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add cli for issue cert

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* remove not needed envs

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix linter errors, add cli

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix linter errors, add cli, var rename

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix reviews, add viewcert, fix view all certs

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* remove view cert, as it will be retrieved from PKI

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* change endpoints

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add default env val

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* remove some errors

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* refactor, make wrapper lib for vault

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* refactor, make wrapper lib for vault

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* refactor, make wrapper lib for vault

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix revoking

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* refactor, make wrapper lib for vault

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* update vendor

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix comment

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add comments

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* remove unused

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* remove unused field

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* update vendor

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* refactor pki

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* refactor pki

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* refactor pki, update vendor

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* refactor pki

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix comment

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* minor fix

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* remove methods, use fields

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix comments and package desc

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix comments and package desc

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>
2020-07-21 12:53:21 +02:00
Dušan Borovčanin be1361311d NOISUE - Fix cache error when key is not in Redis (#1220)
* Fix cache error when key is not in Redis

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Fix tests

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Fix errors message

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>
2020-07-14 22:54:33 +02:00
Darko Draskovic c1088b9315 MF-1199 - Add NATS messaging tests (#1209)
* Add test setup for mqtt

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add pubsub tests

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add message and payload helper funcs

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add time.Sleep after subscribe to topic

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add NATS tests setup

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add NATS test cases

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add message forwarded check to handler

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Remove race affected mqtt tests

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>
2020-07-14 18:11:27 +02:00
Mirko Teodorovic 2453cd75ed NOISSUE: Fix emailer (#1219)
* update swagger

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* dont return nil

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* simplify emailer New

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

Co-authored-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>
2020-07-14 03:06:13 +02:00
Dušan Borovčanin 7dc6b0d879 NOISSUE - Update dependencies (#1218)
* Update Go version

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Update dependencies

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>
2020-07-13 15:24:55 +02:00
Darko Draskovic 381ebb1e51 NOISSUE - Add subtopic wildcard for twin attribute's definition (#1214)
* Add wildcard to attribute subtopic

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add MF_TWINS_SUBTOPIC_WILDCARD env var

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Remove configurable wildcard env var and mqtt notif leftovers

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add mongodb RetrieveByAttribute tests

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add redis wildcard subtopic IDs retrieval

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add tests for wildcard state save

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>
2020-07-09 12:18:19 +02:00
Mirko Teodorovic 09d09c6ef5 fix envs for nginx (#1215)
Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>
2020-07-08 10:42:33 +02:00
Darko Draskovic a7eee53dfb Remove twin mqtt related obsolete var and fix es-redis address (#1213)
Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>
2020-07-02 18:03:59 +02:00
Alexander Obukhov e6b7fd818b NOISSUE - Remove unused MF_THINGS_SECRET env var (#1211)
Signed-off-by: Alexander Obukhov <dev@sprql.space>

Co-authored-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>
2020-06-29 22:28:20 +02:00
Alexander Obukhov ecff066421 NOISSUE - Fix some typos (#1212)
Signed-off-by: Alexander Obukhov <dev@sprql.space>
2020-06-29 20:48:14 +02:00
Dušan Borovčanin 8d33285489 NOISSUE - Remove unknown Bootstrap requests (#1210)
* Remove unknown Bootstrap requests

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Update docs

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Fix migrations

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>
2020-06-26 13:24:56 +02:00
Alexander Obukhov ee5c76395a NOISSUE — Use pgcrypto instead uuid-ossp for UUIDs generation (version 4) (#1208)
According to
https://www.postgresql.org/docs/current/uuid-ossp.html
it's recommended to use
https://www.postgresql.org/docs/current/pgcrypto.html
for randomly-generated (version 4) UUIDs

Signed-off-by: Alexander Obukhov <dev@sprql.space>
2020-06-19 00:37:54 +02:00
Darko Draskovic 4a9c7403d8 MF-1198 - Add errors package tests (#1207)
* Add tests for errors package

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add recursive fn to gen wrapped errs and func to gen corresponding msg

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add TestWrap func

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Replace literal string msgs with res of messages()

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add nil tests for errors.Contains()

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add nil tests for Wrap()

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>
2020-06-12 12:13:23 +02:00
Mirko Teodorovic 381a15a695 MF-1025 - timeout env in sec, use parseduration (#1206)
* timeout env in sec, use parsduration

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* timeout env in sec, use parsduration

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* rename var

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>
2020-06-08 18:17:16 +02:00
Manuel Imperiale a7b81dc37a MF-1201 - Fix MF_THINGS_AUTH_GRPC_URL mongo reader ENVAR (#1203)
Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2020-06-05 17:39:22 +02:00
Dušan Borovčanin a1643b1e7e NOISSUE - Fix CI (#1204)
Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>
2020-06-05 17:29:24 +02:00
Darko Draskovic 340e685d70 MF-1180 - Add redis based twins and states cache (#1184)
* Add twins redis cache

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add connectToRedis to twins main and twinCache to twins service

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add tracing to twins cache

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add twins cache mock and test setup for redis cache

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add TestTwinSave to redis twins cache tests

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add remove twin redis cache test

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add channels param to CreateDefinition helper method in mocks

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add IDs test to redis twins cache

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Simplify senml rec array and attribute creation funcs by removing unnecessary params

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Align cache remove twin method with service remove twin method

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add cache funcs to twins save, update and remove

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add def SaveIDs to redis cache and ref to service SaveStates

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add TwinSaveIDs tests for redis cache

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add cache related env vars desc to README.md

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add twinid bson key constant

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add Update method to cache

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Integrate uuid unification related changes

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Use named arguments in interface method declarations

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add env vars to docker-compose file

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Make parameter names in interface methods and implementations consistent

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Wrap vars and consts in var and const blocks

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>
2020-06-05 11:42:16 +02:00
Manuel Imperiale 97f8d65885 MF-739 - Add ID to the User entity (#1152)
* MF-739 - Add ID to the User entity

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Resolve remarks

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Move idp to project root

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Use RetrieveByEmail func and UUIDProvider

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Rm idp.go

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix comment

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Rename UserInfo into ViewUser

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix ViewUser naming

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2020-06-04 22:29:16 +02:00
Ivan Milošević 7f3e2c1b21 NOISSUE - Fix default db name for storage databases (#1194)
* Fix db name in influxdb

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Align db name and namespace from .env with default values  in mongodb and cassandra also

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Rename databases' names to mainflux

Signed-off-by: Ivan Milošević <iva@blokovi.com>

Co-authored-by: Drasko DRASKOVIC <drasko.draskovic@gmail.com>
2020-06-03 20:33:52 +02:00
Alexander Obukhov 8b004b3daf NOISSUE - Add MF_DOCKER_IMAGE_NAME_PREFIX to Makefile (#1173)
Signed-off-by: Alexander Obukhov <dev@sprql.space>
2020-06-03 20:27:09 +02:00
Manuel Imperiale a7c3cfcf1c MF-1154 - Move UUID provider to project root (#1172)
* MF-1154 - Move UUID provider to project root

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Use naming uuidProvider/up instead of identityProvider/idp

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix typo

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Mutualize UUID mocks

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Move uuid into pkg directory

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2020-06-03 18:49:44 +02:00
Ivan Milošević f1ee9d0665 Fix typo in error messages (#1193)
Signed-off-by: Ivan Milošević <iva@blokovi.com>
2020-06-03 17:34:01 +02:00
Dušan Borovčanin ea3a891c91 MF-1190 - Add pkg for library packages (#1191)
* Move messaging to pkg

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Move errors to pkg

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Move Transformers to pkg

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Move SDK to pkg

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Remove Transformers from root

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Fix make proto

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Add copyrights header

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Fix CI

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Move Auth client to pkg

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Fix dependencies

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Update dependencies and vendors

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Fix CI

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>
2020-06-03 15:16:19 +02:00
Dušan Borovčanin c26c7f34cd MF-1177 - Implement caching in MQTT adapter (#1187)
* Add auth cache to MQTT adapter

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Remove unused fields from MQTT adapter

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Fix removal from Things cache bug

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Use Auth cache naming

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Parse duration instead of int

This is more convenient because duration contains unit.

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Remove commented code

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Remove unused environment variables

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Update README.md

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Remove unused code

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Fix Things mocks

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Fix README timeouts

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Remove WS Scheme

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>

* Remove address from the mProxy target

Signed-off-by: dusanb94 <dusan.borovcanin@mainflux.com>
2020-06-02 21:21:30 +02:00
Ivan Milošević a5fb55c328 NOISSUE - Refactor provision tool (#1189)
* Use bulk sdk functions for generating Things and Channels
Add prefix option

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Update readme
Remove dead code
Rename variable

Signed-off-by: Ivan Milošević <iva@blokovi.com>
2020-06-01 17:34:10 +02:00
Drasko DRASKOVIC 8906943d1d Prepare 0.11.0 release (#1186)
Signed-off-by: Drasko Draskovic <drasko.draskovic@gmail.com>
2020-05-29 16:05:55 +02:00
Drasko DRASKOVIC a57fb03c01 Add VerneMQ docker image build from source (#1178)
Signed-off-by: Drasko Draskovic <drasko.draskovic@gmail.com>
2020-05-25 12:24:21 +02:00
Darko Draskovic 877005538c MF-994 - Add tracing middleware for twins and states repos (#1181)
* Add tracing for twin repository

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add state repository tracer

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Remove unused tracer meant for NATS publisher

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>
2020-05-21 13:06:31 +02:00
Darko Draskovic b4c80132e6 MF-995 - Add Twins tests for endpoint list twins and list states (#1174)
* Add ListTwins test

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Remove monotonic time from twins, definitions and attributes creation and update

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Separate twins and states endpoint tests in two files

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add state generation helper funcs to state endpoint tests

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add createStateResponse() to states test

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add states test cases

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Simplify RetrieveAll twins and states methods

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add service.go to mocks

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Rename mocks.NewService to mocks.New

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add error checking to endpoint state tests

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Fix method comment

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add json response decode success check

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Remove created and updated fields from twin and state res

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Remove definition fields from twin req and res

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add Create funcs to mocks package

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add service save state tests

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add service list states test

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>
2020-05-18 18:46:50 +02:00
Dušan Borovčanin d7670e7adb NOISSUE - Update dependencies (#1176)
* Update dependencies

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Fix mProxy version

Signed-off-by: dusanb <borovcanindusan1@gmail.com.com>

Co-authored-by: dusanb <borovcanindusan1@gmail.com.com>
2020-05-14 19:09:55 +02:00
Manuel Imperiale df6f5adff8 MF-1163 - Fix influxdb-reader to use nanoseconds precision (#1171)
Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2020-05-12 18:19:04 +02:00
Ivan Milošević 5089fccf36 Rename environment variable MF_MQTT_ADAPTER_PORT to MF_MQTT_ADAPTER_MQTT_PORT in docker environment (#1170)
Signed-off-by: Ivan Milošević <iva@blokovi.com>
2020-05-12 14:28:59 +02:00
Darko Draskovic b13a3135dd Remove thing related code from twins service (#1169)
Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>
2020-05-07 17:26:34 +02:00
Darko Draskovic 4d9694c027 MF-997 - Add twins service swagger file (#1167)
* Add twins service swagger file

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add TwinRes and TwinsPage definitions

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add StateRes and StatesPage

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add missing response codes

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add name parameter

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>
2020-05-07 14:58:23 +02:00
Dušan Borovčanin b8be18129c MF-1079 - Add MQTT forwarder (#1164)
* Add MQTT forwarder

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Cleanup forwarder code

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Use MQTT Publisher in MQTT forwarder

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Cleanup MQTT messaging

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Add Paho client timeout errors

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Simplify MQTT fowarder

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Fix naming in main method

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Use interface and struct instead of function

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Use Mainflux errors package

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Rename `tkn` to `token`

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Use "/" instead of "." as topic separator

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Use async MQTT Publisher

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Fix timeout errors messages

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Add connect token check

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Rename package alias

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>
2020-05-07 09:34:09 +02:00
Mirko Teodorovic f9432c6525 MF-1159 - add gateway metadata update in provision method (#1160)
* provision fix

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* update config

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix naming, Boostrap to Bootstrap

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add name as param

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add name as param

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix typo, and remove some props

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add name as param

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add name as param

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix typo

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix typo

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* refactor service

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* improve gateway provision

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* revert content

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* minor changes

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix test

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* change docs, and error in service

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* change docs, and error in service

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>
2020-05-06 16:53:32 +02:00
Mirko Teodorovic f482df9033 MF-1055 - rollback/release transaction on error (#1166)
* fix error message

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* release transaction on error

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* reove println

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* move defer func up

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* refactor errors handling

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* refactor errors handling

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* move defer

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>
2020-05-06 15:15:19 +02:00
Manuel Imperiale 16ac44f377 NOISSUE - Use log level error for VermeMQ docker (#1162)
* NOISSUE - Use log level error for VermeMQ docker

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Add vermemq ENVARS

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Remove DOCKER_VERNEMQ_ALLOW_ANONYMOUS from docker-compose

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Revert DOCKER_VERNEMQ_ALLOW_ANONYMOUS

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2020-05-05 16:44:06 +02:00
Manuel Imperiale 2b4cf8a990 NOISSUE - Fix default nats pubsub subject (#1153)
* NOISSUE - Fix default nats publisher subject

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Use created timestamp inn transformer

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Use created timestamp in transformer

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* rename topic -> subject for nats

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* revert

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* revert

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix subscriber topic

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix timestamp

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Use int64 nanoseconds Created timestamp

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Typo

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Add comment to created protobuf field

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Rm gogo from scripts/ci.sh

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix comment

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Return publisher in NewPublisher

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2020-05-04 13:14:06 +02:00
Mirko Teodorovic 0c016cd7ce MF-1125 - Document Provision service (#1143)
* update readme

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* update docs

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* small changes

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* update docs

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* update docs

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* update docs

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* update docs

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* minor changes

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* additional paragraphs

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* additional paragraphs

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* additional paragraphs

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add some space

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>
2020-05-01 01:31:02 +02:00
Manuel Imperiale f872546925 NOISSUE - Fix bootstrap SDK args naming (#1151)
Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2020-04-29 20:05:08 +02:00
Nikola Marčetić 9d5202c46b Use VerneMQ default log level (#1150)
Signed-off-by: Nikola Marcetic <n.marcetic86@gmail.com>
2020-04-29 13:13:40 +02:00
Mirko Teodorovic bf189fbd23 NOISSUE - Update provision service (#1133)
* Add provision service

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* delete sdk

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add provision file

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add provision

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add provision

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix envs

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix vendor

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* deleete LICENSE

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix modules

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix modules

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix modules

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* refactoring provison service

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* provision

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix test, and use sdk

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* remove bootstrap mock

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* implement channel

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* export errors

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix test

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix tests

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix comments

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix tests

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add new line

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* remove docker-compse

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add new line

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix load config

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix load config

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* move certs to sdk

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* move certs to sdk

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* move certs to sdk

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* move certs to sdk

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* change dflt file

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix content

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* cert sdk

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix bootstraping

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix errors

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* update swagger

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix mproxy mod

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix test

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix test

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix linter messages

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix linter errors

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* upd vendor

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* upd vendor

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* remove dead code

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add some space for readability

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* use snake case

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* use snake case

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* use snake case

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* use snake case

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* use snake case

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* remov thingids

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* revert readme

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix logging

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* use snake_case

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix var naming

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix signature

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* aling with defaults

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix logging info

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix logging

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix tag

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

Co-authored-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>
2020-04-29 00:39:23 +02:00
Dušan Borovčanin c3019fffb6 NOISSUE - Refactor messaging (#1141)
* Refactor messaging

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Rename SubscribeHandler to MessageHandler

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Remove `Auth` event logs

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Update message pubsub APi

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Fix topics handling

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Update CoAP adapter

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Update Twins service

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Update LoRa adapter

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Update OPC UA adapter

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Remove broker package

Package `broker` is conceptually renamed to package `nats`.

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Update makefile

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Add comment explanation

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Fix MQTT adapter

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Fix typo

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Move NATS pub/sub implementation to pubsub pkg

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Remove an empty line in main methods

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Move messaging-related code to messaging package

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Fix Twins mocks

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Change Occurred back to Created

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Fix tranformer test

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Fix message proto commands

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Replace string literal with constant

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Remove alias from main method

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Change messaging pubsub alias

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Rename occured to created

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Handle NATS connection in the NATS PubSub

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Rename n to pub/pubSub

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Fix typos

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>
2020-04-28 11:02:35 +02:00
Manuel Imperiale 36d00d8b9e Add JSON tags to SDK entities (#1146)
Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2020-04-27 18:49:09 +02:00
Manuel Imperiale 9b4eb7056f NOISSUE - Update CLI README.md (#1139)
Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2020-04-23 10:24:42 +02:00
Dušan Borovčanin cb3b70d567 NOISSUE - Update mProxy version (#1137)
* Use Events interface in MQTT adapter

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Switch to the new version of mProxy

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Update vendor

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Update mProxy version

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>
2020-04-22 20:41:51 +02:00
Mirko Teodorovic c7a8e75353 fix nginx, channel connect (#1136)
Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>
2020-04-22 18:59:50 +02:00
Ivan Milošević 9d430fa02c Remove concurrency flag for golangci-lint (#1134)
Signed-off-by: Ivan Milošević <iva@blokovi.com>
2020-04-21 19:49:37 +02:00
Manuel Imperiale 7a18d3fa56 MF-1088 - Remove message payload content type (#1121)
* MF-1088 - Remove message payload content type

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Add default contentFormat

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2020-04-21 19:33:11 +02:00
Manuel Imperiale ccbfe20607 MF-1129 - Use snake_case for Lora and OPC-UA metadata fields (#1130)
Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2020-04-21 17:49:17 +02:00
Ivan Milošević eb9092494e MF-1128 - Add golangci-linter to a CI script (#1131)
* Add golangci-linter to CI script

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* add no-config flag

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* disable staticcheck

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* disable all except errcheck

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* add more linters

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* add verbose flag for debug
decrease concurrency to 1

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* remove errcheck and verbose flag

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* enable just golint

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* fix lint errors

Signed-off-by: Ivan Milošević <iva@blokovi.com>
2020-04-21 17:05:19 +02:00
Dušan Borovčanin 88fa724fc3 MF-1123 - Move Provision service to monorepo (#1132)
* Add provision service

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Fix Provision service initial version

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>
2020-04-21 16:05:11 +02:00
Manuel Imperiale 7910d64ad4 MF-845 - Add FOSSA badge for licensing (#1127)
Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2020-04-20 18:21:55 +02:00
Manuel Imperiale 01aa4190c5 MF-1087 - Remove WebSocket adapter (#1120)
* MF-1087 - Remove WebSocket adapter

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Rm all ws directory

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Revert authorization.js

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Remove /ws endpoint from ssl/authorization.js

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Rm gorilla from vendor

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Revert gorilla to vendor

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2020-04-17 13:16:21 +02:00
Manuel Imperiale f982b8ccc6 NOISSUE - Use HTTP Status in SDK error messages (#1119)
* NOISSUE - Use HTTP Status in SDK error messages

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Add helper function to create errors

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fyx Typo

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Revert ErrNotFound

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Rm InvalidArgs and NotFoundound errors from SDK

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2020-04-16 14:45:28 +02:00
Manuel Imperiale 7d839b7b61 NOISSUE - Fix bootstrap token naming and interfaces named args (#1117)
* NOISSUE - Fix bootstrap token naming and interfaces named args

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix CI bot

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Use owner for repository layer

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix reviews

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2020-04-16 12:32:21 +02:00
Manuel Imperiale 6bd74575ff MF-1115 - Improve the SDK error encoding (#1118)
Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2020-04-15 23:41:31 +02:00
Manuel Imperiale 4c970a8079 MF-862 - Add boostrap CRUD to SDK and CLI (#1114)
* MF-862 - Add boostrap CRUD to SDK and CLI

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Revert ReaderPrefix

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix reviews

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix typo

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2020-04-15 17:38:20 +02:00
Manuel Imperiale b8bd011f9d NOISSUE - Update coding style in Things service (#1116)
* NOISSUE - Update coding style in Things service

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix things.Page

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2020-04-15 15:15:30 +02:00
Alexander Obukhov fbbf2c07a5 NOISSUE - Remove defers from TestMain (#1111)
Defers will not be run when using os.Exit (https://github.com/golang/go/issues/34129)

Signed-off-by: Alexander Obukhov <dev@sprql.space>
2020-04-14 14:47:34 +02:00
Manuel Imperiale 5b5229975b NOISSUE - Create func to encode SDK errors (#1110)
* NOISSUE - Create func to encode SDK errors

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Use var declaration

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2020-04-14 11:04:33 +02:00
Manuel Imperiale d8dca28072 MF-1078 - Add timestamp to published messages and use it in Transformer (#1106)
* MF-1078 - Add reception time to published messages and use it if SenML time is missing

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix comments

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Add message timestamp

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix pub

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Mv timestamp to service layer

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Revert WS

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix coap

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix CI bot

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Rename Timestamp Created

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Mv Created field to message creation

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Mv Created field to message creation

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Use UnixNano

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Use google.protobuf.Timestamp

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Add protoc-gen-gogo and gogoproto to CI script

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Use TimestampProto to generate timestamp

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2020-04-13 15:05:05 +02:00
Ivan Milošević 537d73d0c6 Fix prometheus namespace in postgres reader & writer (#1109)
Signed-off-by: Ivan Milošević <iva@blokovi.com>
2020-04-13 14:01:59 +02:00
Ivan Milošević 880e193b0a NOISSUE - Implement errors package in senml transformer, readers and writers (#1108)
* Implement errors package in senml transformer, readers and writers

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Remove unused const
Return wrapped error in postgres writer

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* fix default db host in postgres writer

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* fix capital letters in errors messages

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* use svcName instead of postgres for Promethius initialization

Signed-off-by: Ivan Milošević <iva@blokovi.com>
2020-04-13 12:57:53 +02:00
Ivan Milošević e438be4250 NOISSUE - Implement errors package in Authentication service (#1105)
* Implement errors package in Authn service

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* remove imported and not used fmt package

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* wrapped errors when issued new key

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* remove blank line

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Change error message in tests
Remove nil case in encode error

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* return back nil value error handling

Signed-off-by: Ivan Milošević <iva@blokovi.com>
2020-04-10 17:43:42 +02:00
Dušan Borovčanin 57f7ee2595 MF-1103 - API key should ignore empty expiration time (#1104)
* Fix API keys expiration validation

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Fix API docs

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>
2020-04-07 14:32:02 +02:00
Manuel Imperiale 6c39c85cc8 MF-1096 - Fix AuthN and Things Auth ENVARS (#1066)
* NOISSUE - Fix AuthN and Things Auth ENVARS

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Add DB envars to env.go

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix envars

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Add DefLogLLevelError

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix DB names

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix DB names

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix DB names and HTTP ports

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix .env

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Revert writers DB names

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Rm unused Twins envars

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix reviews

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix reviews

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix typo

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Remove definitions from env.go

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Revert HTTP gRPC ports

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Use default NATS URL as string

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix reviews

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Revert default ports

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix typo

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix cassandra ENVARS

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix cassandra reader ENVARS

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix readers and writers envars

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix readers and writers .env

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix reviews

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2020-04-07 12:02:17 +02:00
Ivan Milošević 338c55e554 fix Contains function for nil arguments (#1102)
Signed-off-by: Ivan Milošević <iva@blokovi.com>
2020-04-07 10:35:38 +02:00
Manuel Imperiale 382bc89161 MF-1099 - Add email subdomain validator (#1101)
* MF-1099 - Add email subdomain validator

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Add unlimited sub domains

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix users tests with error nil

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Revert users test

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2020-04-07 10:16:46 +02:00
Manuel Imperiale 8325c1caf6 MF-1091 - Use channels. as broker prefix (#1098)
Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2020-04-06 12:30:26 +02:00
Manuel Imperiale 1d78233fe6 MF-1090 - Use named Interfaces args (#1097)
Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2020-04-05 13:15:47 +02:00
Manuel Imperiale fff492bd50 NOISSUE - Create broker package for NATS (#1080)
* NOISSUEE - Create broker package for NATS

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Create funcs to return NATS connection

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* mv os.exit to main

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix Reviews

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix tests and typos

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix CI

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix reviews

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Unify Publisher and Subscriber interfaces

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Rename Nats interface

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* typo

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Mv message.pb.go, messsage.proto and topics.go to broker directory

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix go.mod

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Use mainflux broker for writers and twins services

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix go.mod

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix twins tests

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix make proto

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix message.proto

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix golangcibot

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* regenerate message.pb.go

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix comment

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix comment

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix make proto

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Add NATS errors

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2020-04-01 21:22:13 +02:00
Ivan Milošević 51ec256664 NOISSUE - Implement errors package in bootstrap service (#1093)
* implement errors in bootstrap service

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* remove dead code

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* change errors messages

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* edit more errors messages

Signed-off-by: Ivan Milošević <iva@blokovi.com>
2020-04-01 15:54:26 +02:00
Manuel Imperiale c20b43ed41 NOISSUE - Fix writers loadSubjectsConfig if file is missing (#1094)
Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2020-04-01 14:40:59 +02:00
Jonathan Dreyer 46aadcfd9d NOISSUE - Adding subtopics filtering in writer services (#1072)
* Add feature of filtering by subtopics in writer

Signed-off-by: Jonathan Dreyer <jonathan.dreyer@cleanenergie.ch>

* Fix mistake

Signed-off-by: Jonathan Dreyer <jonathan.dreyer@cleanenergie.ch>

* Refactoring writer sevices

Signed-off-by: Jonathan Dreyer <jonathan.dreyer@cleanenergie.ch>

* Rename variables related to filter (channels & subtopics)

Signed-off-by: Jonathan Dreyer <jonathan.dreyer@cleanenergie.ch>

* Set default value of filtering when configuration file doesn't exist

Signed-off-by: Jonathan Dreyer <jonathan.dreyer@cleanenergie.ch>

* Add a blank line at the end of the file

Signed-off-by: Jonathan Dreyer <jonathan.dreyer@cleanenergie.ch>

* Refactor loading filters configuration (moving into writer package, merge both loading methods & returning error)

Signed-off-by: Jonathan Dreyer <jonathan.dreyer@cleanenergie.ch>

* Remove useless log

Signed-off-by: Jonathan Dreyer <jonathan.dreyer@cleanenergie.ch>

* Change type of variables (channels & subtopics) and simplify loading method

Signed-off-by: Jonathan Dreyer <jonathan.dreyer@cleanenergie.ch>

* Add logging error when loading filters

Signed-off-by: Jonathan Dreyer <jonathan.dreyer@cleanenergie.ch>

* Simplify return configuration in loading method

Signed-off-by: Jonathan Dreyer <jonathan.dreyer@cleanenergie.ch>

* Merge both filter files into one file

Signed-off-by: Jonathan Dreyer <jonathan.dreyer@cleanenergie.ch>

* Move loading subjects into writer package

Signed-off-by: Jonathan Dreyer <jonathan.dreyer@cleanenergie.ch>

* Add subscribe to selected subjects

Signed-off-by: Jonathan Dreyer <jonathan.dreyer@cleanenergie.ch>

* Edit README of writer services

Signed-off-by: Jonathan Dreyer <jonathan.dreyer@cleanenergie.ch>

* Keep only subscribe loop

Signed-off-by: Jonathan Dreyer <jonathan.dreyer@cleanenergie.ch>

* Use full NATS subjects

Signed-off-by: Jonathan Dreyer <jonathan.dreyer@cleanenergie.ch>

* Edit comment in subjects files

Signed-off-by: Jonathan Dreyer <jonathan.dreyer@cleanenergie.ch>

Co-authored-by: Drasko DRASKOVIC <drasko.draskovic@gmail.com>
2020-03-30 18:24:27 +02:00
Ivan Milošević d2153a8846 [NOISSUE] - Improve errors package (#1086)
* Init commit - implement errors package on things service

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* things service errors issue

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Decode errors
Add authn service to run script

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Modify tests

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* service_test

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* debug lines

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Regulate tests

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Improve errors in Redis

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Remove dead code
Inline if conditions
Rename err var

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Transform errors messages to lowercase

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* improve errors package

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* modify wrap method
inline wrapping errors in redis

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Add copyright to errors package

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* wrapping nil error returns wrapper (instead of nil)

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* move response messages in test to vars

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* golangcibot review fix

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* golangbot fix review in transport

Signed-off-by: Ivan Milošević <iva@blokovi.com>
2020-03-30 15:22:18 +02:00
Nikola Marčetić 70955d1937 NOISSUE - Enable MQTT over WS in docker composition (#1085)
* Enable WS over MQTT docker composition

Signed-off-by: Nikola Marcetic <n.marcetic86@gmail.com>

* Remove commented code

Signed-off-by: Nikola Marcetic <n.marcetic86@gmail.com>
2020-03-25 22:26:50 +01:00
Manuel Imperiale c1df2cb21c NOISSUE - Rm unused opc-ua envars (#1083)
Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2020-03-25 16:56:57 +01:00
Vadim Filin b8818c4dd2 MF-798 - Add utf8 support for email validation (#1082)
* Add utf8 support for email validation

Signed-off-by: Vadim Filin <phil192@yandex.ru>

* Fix review comments

Signed-off-by: Vadim Filin <phil192@yandex.ru>

* Refactor

Signed-off-by: Vadim Filin <phil192@yandex.ru>

Co-authored-by: Drasko DRASKOVIC <drasko.draskovic@gmail.com>
2020-03-25 15:30:20 +01:00
Ivan Milošević a2d70c8907 Remove unused Tokenizer interface (#1084)
Signed-off-by: Ivan Milošević <iva@blokovi.com>
2020-03-25 10:29:48 +01:00
Nikola Marčetić 6ffa916ed2 Update mqtt adapter imports (#1081)
* Fixed mqtt adapter imports

Signed-off-by: Nikola Marcetic <n.marcetic86@gmail.com>

* PR remakrs resolved

Signed-off-by: Nikola Marcetic <n.marcetic86@gmail.com>
2020-03-24 11:07:41 +01:00
Darko Draskovic 19503742a6 NOISSUE - Update state based on SenML time value (#1075)
* Update state based on SenML time value

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Use Modf to parse SenML rec time

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add Update to State in mocks

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add Delta to Twin Definition and iota consts for state actions

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Use action consts for switch statement

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>
2020-03-18 19:56:39 +01:00
Manuel Imperiale c91fe0d453 NOISSUE - Fix StatusBadDecodingError for opc-ua browse (#1074)
* NOISSUE - Fix StatusBadDecodingError for opc-ua browse

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Reduce number of browse childrens level

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2020-03-14 20:09:02 +01:00
Darko Draskovic 3e4a9eb16a Save senml array msg to multiple states (#1073)
Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>
2020-03-13 16:20:57 +01:00
Manuel Imperiale b3f91f5c9e NOISSUE - Fix opc-ua message type handling (#1071)
* NOISSUE - Fix opc-ua message type handling

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Add comments

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix reviews

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix typo

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Return error

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2020-03-12 11:20:05 +01:00
Dušan Borovčanin ba97e865a4 NOISSUE - Add Publisher field to MQTT adapter (#1067)
* Add publisher field to the message

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Update SenML lib

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>
2020-03-10 11:44:48 +01:00
Manuel Imperiale cc5e0288df NOISSUE - Fix users CLI (#1062)
* NOISSSUE - Fix users CLI

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix README

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* use Mainflux entities

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Mv ConnectionIDs to requests

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2020-03-09 11:31:19 +01:00
Manuel Imperiale 6e4e5b351a NOISSUE - Fix SDK Messages response (#1064)
Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2020-03-07 05:02:26 +01:00
Drasko DRASKOVIC d587921eda Merged MQTT docker compose in core composition file (#1060)
Signed-off-by: Nikola Marcetic <n.marcetic86@gmail.com>
2020-03-05 13:34:07 +01:00
Manuel Imperiale 7df2ababc4 MF-1016 - Add UserUpdate and UpdatePassword to sdk and CLI (#1057)
* MF-1016 - Add UserUpdate and UpdatePassword to sdk and CLI

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Update READMEs

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2020-03-04 18:37:41 +01:00
Dušan Borovčanin 63dc1f69b6 Update mProxy (#1058)
Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>
2020-03-04 15:25:09 +01:00
Dušan Borovčanin 1c6f124062 MF-1053 - Add disconnect event to MQTT adapter (#1056)
* Add disconnect event to mProxy

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Rename publishers to pubs

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Use acync publishers

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>
2020-03-03 19:23:57 +01:00
Ivan Milošević cc90f568b6 Fix data type for data_value in databases (#1054)
Signed-off-by: Ivan Milošević <iva@blokovi.com>
2020-03-03 15:35:26 +01:00
Manuel Imperiale 5e145858f1 NOISSUE - Fix opc-ua subscriptions store (#1052)
Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2020-03-03 15:05:22 +01:00
Manuel Imperiale 9e2bb4933a NOISSUE - Fix connect CLI command and remove ConnectThing func from SDK (#1051)
* NOISSUE - Fix connect CLI command and remove ConnectThing func from SDK

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Rm ConnectThing from bootstrap

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2020-03-03 10:58:30 +01:00
Nikola Marčetić 3f77b87a91 NOISSUE - Update Vernemq image repository (#1050)
Signed-off-by: Nikola Marcetic <n.marcetic86@gmail.com>
2020-03-02 18:21:44 +01:00
Nikola Marčetić dcba41e8c4 Removed VerneMQ auth plugin, Aedes impl. Added mproxy support in docker (#1049)
Signed-off-by: Nikola Marcetic <n.marcetic86@gmail.com>
2020-03-01 23:55:43 +01:00
Manuel Imperiale f6c813ea8c NOISSUE - Add default subscription nodeID and Interval ENVAR (#1046)
* NOISSUE - Fix NodeID

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Add default subscription nodeID and Interval ENVAR

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* updatee README

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2020-02-27 01:06:14 +01:00
Nikola Marčetić 42b3682352 MF-415 - Merge mProxy support (#1045)
* NOISSUE - Add mProxy support (#1017)

* Add mproxy

Signed-off-by: Drasko DRASKOVIC <drasko.draskovic@gmail.com>

* Fix docker and add EMQ compose

Signed-off-by: Drasko DRASKOVIC <drasko.draskovic@gmail.com>

* Fix EMQX name

Signed-off-by: Drasko DRASKOVIC <drasko.draskovic@gmail.com>

* Add nats, auth and es

Signed-off-by: Drasko DRASKOVIC <drasko.draskovic@gmail.com>

* Removed unucessary vendoring

Signed-off-by: Drasko Draskovic <drasko.draskovic@gmail.com>

* Update vendoring

Signed-off-by: Drasko Draskovic <drasko.draskovic@gmail.com>

* Fix mproxy interface implementation

Signed-off-by: Drasko Draskovic <drasko.draskovic@gmail.com>

 NOISSUE - Aligned Event interface method signatures with new spec (#1025)

* Aligned Event interface method signatures with new spec

Signed-off-by: Nikola Marcetic <n.marcetic86@gmail.com>

* Updated deps

Signed-off-by: Nikola Marcetic <n.marcetic86@gmail.com>

NOISSUE - Update mproxy dependency (#1038)

Signed-off-by: Nikola Marcetic <n.marcetic86@gmail.com>

Update Vendor with new mProxy (#1043)

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

Twins merge conflict reverted

Signed-off-by: Nikola Marcetic <n.marcetic86@gmail.com>

Twins merge conflict reverted

Signed-off-by: Nikola Marcetic <n.marcetic86@gmail.com>

Twins fixed nats import

Signed-off-by: Nikola Marcetic <n.marcetic86@gmail.com>

Update deps

Signed-off-by: Nikola Marcetic <n.marcetic86@gmail.com>

* Resolved GolangCI remarks

Signed-off-by: Nikola Marcetic <n.marcetic86@gmail.com>

Resolved GolangCI remarks

Signed-off-by: Nikola Marcetic <n.marcetic86@gmail.com>

Resolved GolangCI remarks

Signed-off-by: Nikola Marcetic <n.marcetic86@gmail.com>

* Fixed Event interface Unsubscribe() typo

Signed-off-by: Nikola Marcetic <n.marcetic86@gmail.com>

* Update vendors

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Upgrade CI script

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

Co-authored-by: Drasko DRASKOVIC <drasko.draskovic@gmail.com>
Co-authored-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>
2020-02-26 17:14:16 +01:00
Darko Draskovic 3b5d51276f NOISSUE - Remove twins-service mqtt dependency and publish notifs to nats (#1042)
* Add Publish func to nats

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Remove mqtt client

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add nats publisher

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Separate nats publisher from subscriber

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Update tests creation methods

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add logger to NATS Publisher

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>
2020-02-20 00:10:10 +01:00
Darko Draskovic a30a3b9063 Add arbitrary SenML value type saving to twin state (#1039)
Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>
2020-02-18 19:56:39 +01:00
Nikola Marčetić e22d1fbbb4 Fixed Aedes dependencies (#1036)
Signed-off-by: Nikola Marcetic <n.marcetic86@gmail.com>
2020-02-13 14:37:16 +01:00
Darko Draskovic 51cd0524a1 MF-998 - Add Twins service to Makefile and docker-compose.yml (#1035)
* Add separate twins mongodb

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Move twins docker compose file to addons

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add depends_on to mainflux-twins container

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Remove hard-coded vals from .env

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add start explanation to README.md

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Bump mongo image to latest version

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>
2020-02-12 18:34:48 +01:00
Manuel Imperiale 1c2c86b030 MF-1032 - Fix redis docker volume of opcua-adapter (#1033)
Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2020-02-07 17:13:37 +01:00
Mirko Teodorovic a1078e6999 NOISSUE - add nats conf (#1031)
* add nats conf

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* change max payload

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>
2020-02-06 18:44:29 +01:00
antonio-salieri b8138fac32 MF-442 - Add SSL encryption to the MongoDB, InfluxDB and Cassanda readers (#1024)
* Add possibility of running influxdb-reader http server with tls

Signed-off-by: Lazar Ivanovic <laza@opencores.org>

* Add possibility of running mongodb-reader http server with tls

Signed-off-by: Lazar Ivanovic <laza@opencores.org>

* Add possibility of running cassandra-reaeder http server with tls

Signed-off-by: Lazar Ivanovic <laza@opencores.org>

* Addressed pr comments

Signed-off-by: Lazar Ivanovic <laza@opencores.org>

Co-authored-by: Drasko DRASKOVIC <drasko.draskovic@gmail.com>
2020-02-05 20:24:36 +01:00
Manuel Imperiale 2b7637fd75 NOISSUE - Add opc-ua type handling and unsubscription (#1029)
* NOISSUE - Add opc-ua type handling and unsubscription

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Create const for max childrens

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2020-02-05 17:16:45 +01:00
Darko Draskovic f785116a6f NOISSUE - Add aggregate attribute-based search for twin retrieval (#1027)
* Add attribute map for twin retrieval

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Restructure attributes from map[string] to []

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Remove RAM attribute map and use mongo aggregation

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Update tests

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Remove attribute map service property

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>
2020-02-04 23:25:51 +01:00
Manuel Imperiale 3f6a0cd14b NOISSUE - Fix metadata in add Things endpoint (#1028)
Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2020-02-04 12:33:28 +01:00
Aleksandar Novaković 269fe89a42 NOISSUE - Fix minimal password length (#1023)
* Fix minimum password length

Increase minimum password length to 8.

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>

* Update tests with valid password length

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>

* Update minimal pass length in the docs

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>
2020-01-29 18:54:34 +01:00
Jonathan Dreyer bdf1326933 MF-1020 - Change default password for CLI provision test (#1021)
Signed-off-by: Jonathan Dreyer <jonathan.dreyer@cleanenergie.ch>

Co-authored-by: Nikola Marčetić <n.marcetic86@gmail.com>
2020-01-29 12:31:15 +01:00
Manuel Imperiale a96db05cc0 NOISSUE - Add subtopic to opcua messages (#1022)
Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2020-01-28 23:30:55 +01:00
Manuel Imperiale 7b62f1ce8a NOISSUE - Add details to browsed OPC-UA nodes (#1019)
Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2020-01-27 12:25:16 +01:00
Darko Draskovic 575d1d6bbf NOISSUE Fix obsolete attribute persistance (#1018)
* Fix obsolete attribute update

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Inlined map key presence check

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>
2020-01-27 02:15:10 +01:00
Darko Draskovic 0d361f3df1 Fix twins update revision counter (#1011)
Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>
2020-01-22 15:58:24 +01:00
Nikola Marčetić 5714a6dfcb Fixed docs instructions in README (#1010)
Signed-off-by: Nikola Marcetic <n.marcetic86@gmail.com>
2020-01-17 14:44:13 +01:00
Ivan Milošević decfae9c9d Fix copyright year (#1009)
Signed-off-by: Ivan Milošević <iva@blokovi.com>
2020-01-17 12:51:01 +01:00
Dušan Borovčanin c4fa27fd7e Fix issuing recovery key (#1007)
Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>
2020-01-16 18:43:06 +01:00
Nikola Marčetić 8475e87fe7 Removed gatling load-test (#1005)
Signed-off-by: Nikola Marcetic <n.marcetic86@gmail.com>
2020-01-16 11:57:34 +01:00
Nikola Marčetić 0d3adfdc5c Removed old k8s manifests (#1004)
Signed-off-by: Nikola Marcetic <n.marcetic86@gmail.com>
2020-01-16 11:37:07 +01:00
Manuel Imperiale 160fda4d37 NOISSUE - Remove UI from docker-compose (#1001)
* NOISSUE - Remove UI from docker-compose

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Rm MF_UI_PORT envar

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2020-01-15 15:27:24 +01:00
Manuel Imperiale 4ba1717582 NOISSUE - Store successfull OPC-UA subscriptions (#999)
Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2020-01-10 17:56:03 +01:00
Darko Draskovic b3991b8497 MF-730 - Add digital twin service for things (#855)
* Add starter kit

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add http

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add cmd/main.go

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Remove reference to things from README.md

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add starter kit

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add http

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add cmd/main.go

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Remove reference to things from README.md

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Fix env vars in README.md

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Fix env vars in README.md

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Rename kit to mfxkit

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Rename kit to mfxkit

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add docker compose related files

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add docker compose related files

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add Dockerfile to mfxkit

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add Dockerfile to mfxkit

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add twin service to docker-compose.yml

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add twin service to docker-compose.yml

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add mongo db connection

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add mongo db connection

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add TwinRepository mockup

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Fix docker env vars

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Fix docker env vars

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add twins repo mongodb routines

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add twins repo mongodb routines

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add mongo db docker test suite

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add mongo db docker test suite

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add idp and toDBTwin

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add idp and toDBTwin

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add test cases to repo twin save test

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add test cases to repo twin save test

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add users grpc

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add users grpc

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add methods and tests for udpate and update key

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add methods and tests for udpate and update key

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add methods and tests for remove twin

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add methods and tests for remove twin

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add svc methods to loggin and metrics

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add svc methods to loggin and metrics

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add AddTwin endpoint

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add AddTwin endpoint

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add update endpoints

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add update endpoints

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add view and remove endpoints

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add view and remove endpoints

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add twin repo mock

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add twin repo mock

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add owner arg to twins repo methods

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add owner arg to twins repo methods

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add mock idp service

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add mock users service

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add tests for service

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add tests for service

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add mqtt client to twins service

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add mqtt client to twins service

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add protocol to mqtt string var

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add protocol to mqtt string var

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add time and attributes related data to Twin struct

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add time and attributes related data to Twin struct

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add mqtt publish JSON serialized twin to service

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add mqtt publish JSON serialized twin to service

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add endpoint tests for save and update twin

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add endpoint tests for save and update twin

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add endpoint tests for update key and view thing

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add endpoint tests for update key and view thing

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Fix test error for view twin

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Fix test error for view twin

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add endpoint tests for remove twin

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add endpoint tests for remove twin

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add NATS client

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add NATS client

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add normalizer to nats

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add normalizer to nats

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Refactor nats publish() and remove normalizer

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Refactor nats publish() and remove normalizer

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add ListThingsByChannel() and RetrieveByChannel()

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add ListThingsByChannel() and RetrieveByChannel()

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add Twin struct directly in mongodb

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add Twin struct directly in mongodb

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Change limit type to uint64

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Change limit type to uint64

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Compose nats subject from msg ch and subtopic

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Compose nats subject from msg ch and subtopic

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add mqtt notifs for thing creation and key update

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add mqtt notifs for thing creation and key update

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add RetrieveAll to twinRepository and ListTwins to service

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add RetrieveAll to twinRepository and ListTwins to service

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add tests for RetrieveAll and ListTwins

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add tests for RetrieveAll and ListTwins

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Fix Service interface error

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Remove test Ping endpoint

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add mqtt topic to service config

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Remove UpdateKey request and add fields to Twin related requests

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add Attributes and State add and view

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add ListTwins endpoint

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Fix service and database tests

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add ListTwinsByThing endpoint

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Remove twin directory

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add States and Definitions to Twin

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add defer publish to AddTwin

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add refactored and deferred mqtt client publish to Service methods

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add paho subservice

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add paho client to nats

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add mqtt publish and state update

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add Publish wrapper

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Use paho Publish wrapper

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Fix tests

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Save states in separate collection

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Replace []byte payload by []map[string]interface{}

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add offset to PageMetadata

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Rename TwinsPage to Page

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add StateRepository

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add StateRepository, mongodb StateRepository adapter and ListStates endpoint

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add RetrieveLast twin to states repository

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Parse def in nats to update state attrib

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add prepareState() helper

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Replace list things by id by view thing by id

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add state repo mock

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Fix service tests

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Fix mongo twins repo tests and remove owner from RetrieveByID params

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Fix mongo twins repo tests and remove owner from Remove params

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Replace ChannelID in Attribute by Channel

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Fix endpoint tests for add and update twin

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Fix endpoint tests for view and remove twin

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Use new auth service

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Fix mocks auth and mongodb tests

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Fix licensing info

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Fix 'for for'

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Replace short dec by var for default vals and add string map for mqtt op info

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Use Record from mainflux/senml and rename broker to nats in main.go

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Remove Key from Twin

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Remove redundant id uniqueness check from mongodb

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add attrib name to attrib update info

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Fix typos and remove isZeroOfUnderlyingType() helper

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Rename paho to mqtt

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Fix env vars in main.go and README.md

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Move twins/api/twins to twins/api and rename twins- prefix to tw- in main.go

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Remove flows for manual testing and revert to master docker-compose.yml

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Remove publish from nats and fix tests by updating package names

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Rename users.go to authn.go

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Fix crud op names

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Change id to twinID

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Inline if err != nil and change <prefix>ID to <prefix>_id

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Fix metadata db search test

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Rename mqtt and nats related files to publisher and subscriber respectively

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Move save state logic from nats to service

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Fix endpoint tests

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Remove close check from main.go

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Complete deploy section

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add unit tests for states

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Remove debugging artefacts and unneeded comments to exported funcs

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add port num to defAuthnURL

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>
2020-01-10 17:25:36 +01:00
Drasko DRASKOVIC 5834d364ad Fix Redis event naming (#996)
Signed-off-by: Drasko DRASKOVIC <drasko.draskovic@gmail.com>
2020-01-09 14:18:23 +01:00
Manuel Imperiale f42f2095ba NOISSUE - Add a Browse endpoint in opcua-adapter (#988)
Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

Co-authored-by: Nikola Marčetić <n.marcetic86@gmail.com>
2019-12-23 17:30:44 +01:00
Drasko DRASKOVIC 60a90d84d5 NOISSUE - Add Redis ES Username/Pass for VerneMQ (#991)
* Add Redis DB and Pass options

Signed-off-by: Drasko DRASKOVIC <drasko.draskovic@gmail.com>

* Fix ENV var parsing

Signed-off-by: Drasko DRASKOVIC <drasko.draskovic@gmail.com>

* Update protobuf for VerneMQ

Signed-off-by: Drasko DRASKOVIC <drasko.draskovic@gmail.com>

* Fix regular expression for MQTT re-publish

Signed-off-by: Drasko DRASKOVIC <drasko.draskovic@gmail.com>
2019-12-23 00:17:39 +01:00
Nick Neisen 887bc2d9c0 MF-982 - Add error when connecting empty channels or things (#985)
* Add error when connecting empty channels or things

Signed-off-by: nwneisen <nwneisen@gmail.com>

* Add more detailed test descriptions

Signed-off-by: nwneisen <nwneisen@gmail.com>

* Add to test descriptions

Signed-off-by: nwneisen <nwneisen@gmail.com>

* fix channel ids

Signed-off-by: nwneisen <nwneisen@gmail.com>
2019-12-17 19:53:45 +01:00
Nikola Marčetić 2080ee8afa Added changelog for 0.10.0 release (#984)
Signed-off-by: Nikola Marcetic <n.marcetic86@gmail.com>
2019-12-17 17:32:27 +01:00
Dušan Borovčanin 9c1743c080 Fix MQTT protobuf file name (#981)
Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>
2019-12-16 18:05:59 +01:00
Dušan Borovčanin 9f37927dec MF-932 - User API keys (#941)
* Add inital Auth implementation

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Extract IssuedAt on transport layer

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Add token type

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Fix Auth service URL in Things service

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Add User Keys revocation check

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Update tests

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Remove unused tracing methods

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Fix Key retrival and parsing

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Remove unused code

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Increase test coverage

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Fix compose files

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Fix typos

Simplify tests.

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Fix typos and remove useless comments

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Rename Auth to Authn

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Rename database.go to tracin.go

A new name (`tracing.go`) describes better the purpose of the file.

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Increase test coverage

Fix typo.

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Increase test coverage

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Remove token from Users service

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Fix identify login keys

Rename token parsing method.

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Extract tokenizer to interface

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Remove pointer time

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Use pointer for expiration time in response

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Use uppercase N

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Remove unnecessary email check

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Cleanup unused code and env vars

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Rename tokenizer field

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Use slices and named fields in test cases

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Update AuthN keys naming

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Remove package-lock.json changes

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Remove Secret from issuing request

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>
2019-12-16 16:22:09 +01:00
Manuel Imperiale bdeb7711ce NOISSUE - Use opcua server timestamp in opcua-adapter messages (#980)
* NOISSUE - Use opcua server timestamp in opcua-adapter messages

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Rename pubsub -> subscribe

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Typo

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2019-12-16 16:06:37 +01:00
Dušan Borovčanin e960004b44 Simplify CI script (#979)
Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>
2019-12-10 18:53:14 +01:00
Manuel Imperiale 76b68e10a8 NOISSUE - Add opcua-adapter conn route-map, use ServerURI and NodeID (#975)
* NOISSUE - Add opcua-adapter conn route-map, use ServerURI and NodeID

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* NOISSUE - Add dynamic subscription

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2019-12-09 23:16:18 +01:00
Drasko DRASKOVIC 5120a71595 Move docs to a separate repo (#976)
Signed-off-by: Drasko DRASKOVIC <drasko.draskovic@gmail.com>
2019-12-04 13:31:23 +01:00
Dušan Borovčanin 8c4da85039 MF-950 - Runtime error in normalizer - CBOR SenML (#974)
* Add Transformer tests

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Fix readers and writers

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Fix README typo

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Tidy vendor

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Remove link field from docs

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>
2019-11-29 20:47:28 +01:00
Manuel Imperiale 237514ee33 NOISSUE - Support multiple types values in opcua-adapter (#973)
Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2019-11-28 18:08:43 +01:00
Aleksandar Novaković 263108cc85 Migrate from dep to go modules (#971)
Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>
2019-11-27 15:29:34 +01:00
Manuel Imperiale 277342b8ca NOISSUE - Add Node IdentifierType config in opcua-adapter (#967)
* NOISSUE - Add Node IdentifierType config in opcua-adapter

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Rm Read from subscribeToOpcServers

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2019-11-26 20:07:04 +01:00
Manuel Imperiale 87dd91e328 NOISSUE - Remove messages limit in influxdb-reader (#968)
* NOISSUE - Remove messages limit in influxdb-reader

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Rm too large limit test

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2019-11-26 18:58:45 +01:00
Manuel Imperiale 6da650645f NOISSUE - Fix opcua-adapter events warnings (#965)
Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2019-11-25 17:53:18 +01:00
Manuel Imperiale 8602185b74 NOISSUE - Fix opcua-adapter events decode (#951)
* NOISSUE - Enable multi OPC-UA Subscriptions

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* NOISSUE - Fix opcua-adapter events decode

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Use a config file to subscribe to multiple nodes

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Add subscription config file

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Use Mainflux errors package

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2019-11-25 14:14:57 +01:00
Drasko DRASKOVIC b8ac6ca2bf Fix subtopic handling in VerneMQ (#962)
Signed-off-by: Drasko DRASKOVIC <drasko.draskovic@gmail.com>
2019-11-24 18:21:00 +01:00
Nick Neisen 76908d7c8b MF-898 - Add bulk connect to CLI and SDK (#956)
* Add bulk connect to CLI and SDK

Signed-off-by: Nick Neisen <nwneisen@gmail.com>

* Add test for bulk connect

Signed-off-by: Nick Neisen <nwneisen@gmail.com>

* Update docs

Signed-off-by: Nick Neisen <nwneisen@gmail.com>

* Add file example

Signed-off-by: Nick Neisen <nwneisen@gmail.com>

* Add JSON example to CLI docs

Signed-off-by: Nick Neisen <nwneisen@gmail.com>

* Change some value datatypes back

Signed-off-by: Nick Neisen <nwneisen@gmail.com>

* Use constants for file extensions

Signed-off-by: Nick Neisen <nwneisen@gmail.com>
2019-11-21 12:29:37 +01:00
Ivan Milošević 0ab627730f MF-538 - Improve logging and API errors (#866)
* user service - wraping errors

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* wrapping more errors
unwrap only wrapped errors

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Add internal database error
Wrap internal database error

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Wrap user not found error

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Wrapping errors in idp and hasher

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Use error.Is for testing errors in Identify test

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Get wraper from wrapped errors

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Switch order of wrapping errors
Remove dead code (comments)

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* assert true in tests

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Change comparing errors in tests (assert.True)

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Add errorRes structure to API responses in body in things service

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* resolve conflicts after rebasing

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Create errors package

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* implement new errors package

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Modify tests

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* return copyright comments

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* remove changes from .gitignore

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Move logging to encode errors
Comment exported vars and methods
Formatting

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Login function returns errors.Error

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Modify login tests to meet login returning errors.Error

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Error interface

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Change parameter in Wrapper to interface Error

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* implement new error interface

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Modify tests to use new Error interface

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Fix Login

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Remove unnecessary errir casting

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* new error interface implementation

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* check if Error is empty in registrationEndpoint

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Add Empty factory function
Use new Empty factory function
Use isEmpty method

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Lose else in encodeError

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Modify tests

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Remove *json.UnmarshalTypeError and *json.SyntaxError types from encodeError type switch

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Fix nil error value in jwtIdentityProvider

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Fix gprc to use new error package

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* rename receiver in errors package
grpc errors

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* remove debugging code

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Resolving conflicts after rebase

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Remove comment

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Remove Empty from custom error
Implement custom error on new methods

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* WIP tests

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* remove wrap from Error interface

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* password-change related tests
remove debug code

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* remove dead code

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Move all errors casting to errors package

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Fix comment in error package

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Change struct pointer to interface in package methods

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* resolving reviews

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* fix return in database.go

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Fix copyright header

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Fix comment in hasher

Signed-off-by: Ivan Milošević <iva@blokovi.com>
2019-11-20 14:43:41 +01:00
Manuel Imperiale 1b427f4396 NOISSUE - Fix Update User (#959)
* NOISSUE - Fix Update User

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Rm duplicated test

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix typo

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2019-11-20 14:03:17 +01:00
Manuel Imperiale 52c4d4a824 NOISSUE - Fix make dockers (#957)
Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2019-11-20 09:24:58 +01:00
Nick Neisen ced90b21a4 Add dev_ back to make dockers_dev (#955)
Signed-off-by: Nick Neisen <nwneisen@gmail.com>
2019-11-19 17:21:13 +01:00
Manuel Imperiale ac3ff5221a NOISSUE - Remove Elm UI (#953)
Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2019-11-19 09:31:41 +01:00
Mirko Teodorovic 5e35cbe06b NOISSUE - Fix docs (#952)
* fix missing timeout

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* upd tools readme.md

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* upd tools readme.md

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add mising Referer header required for pass reset request

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>
2019-11-16 16:20:29 +01:00
Nick Neisen 894e1b83ec MF-898 - Add bulk connections endpoint (#948)
* Change connect endpoint to use bulk connections

Signed-off-by: nwneisen <nwneisen@gmail.com>

* Add documentation for bulk connect

Signed-off-by: nwneisen <nwneisen@gmail.com>

* Update service and postgres for multiple channels

Signed-off-by: Nick Neisen <nwneisen@gmail.com>

* Update bulk connect endpoint

Signed-off-by: Nick Neisen <nwneisen@gmail.com>

* Fix missed test

Signed-off-by: Nick Neisen <nwneisen@gmail.com>

* Improve test code coverage

Signed-off-by: Nick Neisen <nwneisen@gmail.com>

* Change methods to use shared data type

Signed-off-by: Nick Neisen <nwneisen@gmail.com>

* Fix provisioning example

Signed-off-by: Nick Neisen <nwneisen@gmail.com>

* Change depreciation messages to specify v1.0.0

Signed-off-by: nwneisen <nwneisen@gmail.com>

* Switch singluar log to plural

Signed-off-by: nwneisen <nwneisen@gmail.com>
2019-11-15 18:44:31 +01:00
Manuel Imperiale afeec81a5e MF-916 - Fix Things and Channels counters (#947)
* MF-916 - Fix Things and Channels counters

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix reviews

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Use NamedQueryContext to count rows

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix reviews

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Create single helper total() func

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix reviews

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Rm useless LOWER

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Revert previous commit

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2019-11-13 15:26:57 +01:00
Nick Neisen 67d518821a MF-898 - Change thing's service to use bulk connect (#946)
* Change thing's service to use bulk connect

Signed-off-by: Nick Neisen <nwneisen@gmail.com>

* Move owner check outside of loop

Signed-off-by: Nick Neisen <nwneisen@gmail.com>

* Change invalid user to unauthorized access error

Signed-off-by: Nick Neisen <nwneisen@gmail.com>
2019-11-12 01:24:29 +01:00
Nick Neisen 5446d88abf MF-898 - Add transactions to postgres connect (#940)
* Add transactions to postgres connect

Signed-off-by: Nick Neisen <nwneisen@gmail.com>

* Change to use shorthand notation

Signed-off-by: Nick Neisen <nwneisen@gmail.com>
2019-11-11 14:22:30 +01:00
Mirko Teodorovic 9e8dd4b149 MF-942 - Fix email template logic (#944)
* fix email template

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* rename resetPasswEmail.tmpl

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add env var

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* rename resetPasswEmail.tmpl

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* rename resetPasswEmail.tmpl

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* upd docs

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* upd docs

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* upd docs

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* remove not needed file

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* move email tmpl

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* move email tmpl

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* move email tmpl

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* variable for tmpl

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix ident and caps

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>
2019-11-11 12:13:48 +01:00
Mirko Teodorovic e735e5c01b add missing user service tests (#945)
Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>
2019-11-10 02:47:14 +01:00
Manuel Imperiale 841b943718 NOISSUE - Fix HTTP header for Things and Channels creation (#939)
Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2019-11-07 13:57:47 +01:00
Manuel Imperiale 3bfdcfe83c NOISSUE - Fix docker ui image name (#938)
Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2019-11-06 13:44:58 +01:00
Dušan Borovčanin 66f2332674 Remove Normalizer service from compose (#937)
Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>
2019-11-05 12:34:36 +01:00
Dušan Borovčanin f50335ab33 MF-919 - Mainflux message updates (#924)
* Remove RawMessage

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Remove Normalizer

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Update tests

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Replace normalizer with senml-transformer

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Rename Transformer interface and package

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Update docs

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Remove SenML transformer service

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Remove SenML Protobuf support

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Fix readers

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Fix writers tests

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Refactor tests and remove normalizer

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Update docs

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Reanme Service interface to Transformer

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Use msg instead of rawmsg

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Remove rawMsg from Aedes code

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Fix VerneMQ files

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Remove RawMessage code

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Fix missing subtopic return

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Remove remaining RawMessage reference

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Fix formatting

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Fix readers and writers tests

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Rename SenML transformer variables

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Fix readers and writers tests constants

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>
2019-11-05 11:57:16 +01:00
Manuel Imperiale d7dc836714 NOISSUE - Fix lora-adapter (#936)
Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2019-11-05 11:49:39 +01:00
Manuel Imperiale 887542aab3 NOISSUE - Fix lora creation events (#933)
Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2019-11-05 10:23:30 +01:00
Drasko DRASKOVIC 1d2c8b25a1 NOISSUE - Remove ARM multi-arch images (#929)
* emove ARM multi-arch

Signed-off-by: Drasko DRASKOVIC <drasko.draskovic@gmail.com>

* Fix tabs

Signed-off-by: Drasko DRASKOVIC <drasko.draskovic@gmail.com>

* Remove arch tagging

Signed-off-by: Drasko DRASKOVIC <drasko.draskovic@gmail.com>

* Fix MQTT build

Signed-off-by: Drasko DRASKOVIC <drasko.draskovic@gmail.com>
2019-11-05 10:12:49 +01:00
Nick Neisen 1a31ecd044 MF-906 - Change single creation endpoints to use bulk service calls (#927)
* Change single endpoints to use bulk creation

Signed-off-by: Nick Neisen <nwneisen@gmail.com>

* Remove single creation from thing's service

Signed-off-by: Nick Neisen <nwneisen@gmail.com>

* Remove single save from thing's postgres

Signed-off-by: Nick Neisen <nwneisen@gmail.com>

* Change BulkSave to Save

Signed-off-by: Nick Neisen <nwneisen@gmail.com>

* Change service calls to use variadic parameters

Signed-off-by: Nick Neisen <nwneisen@gmail.com>

* Change postgres things to use variadic functions

Signed-off-by: Nick Neisen <nwneisen@gmail.com>

* Add depreciation warnings

Signed-off-by: Nick Neisen <nwneisen@gmail.com>
2019-11-04 21:14:17 +01:00
Manuel Imperiale 28a176ac1d MF-922 - Add UpdateUser endpoint (#923)
* MF-922 - Add User Update endpoint

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix reviews

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Revert Update function

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix typo

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Update swagger

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix Things swagger

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix swagger

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* update Things swagger

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix users swagger

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix mocks

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix method name

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix swagger

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix swagger

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix swagger

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix typo

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Use User instead of metadata

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix reviews

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix reviews

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix reviews

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix reviews

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2019-10-31 16:36:19 +01:00
Dušan Borovčanin 8be2516321 MF-780 - Use Normalizer as a lib (#915)
* Use Normalizer as a lib

To normalize messages on the consumer side, Normalizer is moved
to the internal pkgs. Writers being message consumers are modified to
do message normalization instead of subscribing to normalized messages
subject.

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Fix logging middleware for readers and writers

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Remove normalizer interface

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Use Normalizer in writers

As we agreed on #919, we'll use normalizer as an interface and provide
the default SenML implementation. Because of that, Normalizer is removed
from `internal` and we'll use the project structure proposed in the
aforementioned issue.

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Fix tests

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Remove unused batch settings from influxDB reader

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Update docs

Move Normalizer service to `addons`.

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Rename channels input topic

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Update Noramlizer docs

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Remove commented code

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Update readers logging

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Update addons docker-compose files

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Update topcis explanations

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>
2019-10-31 14:04:47 +01:00
Drasko DRASKOVIC 380af878d2 Fix doc for ENV vars in README (#920)
Signed-off-by: Drasko DRASKOVIC <drasko.draskovic@gmail.com>
2019-10-29 21:28:59 +01:00
Drasko DRASKOVIC da4fc62440 NOISSUE - Switch to grpcbox for VerneMQ (#914)
* Switch to grpcbox

Use grpcbox as a gRPC client library, because
it is much better maintained.

Shows good performance and generation of protobuf
files is simple and automatic.

Signed-off-by: Drasko DRASKOVIC <drasko.draskovic@gmail.com>

* Fix typo

Signed-off-by: Drasko DRASKOVIC <drasko.draskovic@gmail.com>
2019-10-29 20:35:09 +01:00
Nick Neisen 426813cec4 Change channels to chs (#918)
Signed-off-by: nwneisen <nwneisen@gmail.com>
2019-10-29 19:06:48 +01:00
Nick Neisen 8f3dff837b MF-484 - Add bulk provisioning for things and channels (#889)
* Add provisioning to postgres

Signed-off-by: Nick Neisen <nwneisen@gmail.com>

* Add provisioning to things and channels service

Signed-off-by: Nick Neisen <nwneisen@gmail.com>

* Add JSON provisioning endpoint to things API

Signed-off-by: Nick Neisen <nwneisen@gmail.com>

* Add provisioning to SDK and CLI

Signed-off-by: Nick Neisen <nwneisen@gmail.com>

* Update docs

Signed-off-by: Nick Neisen <nwneisen@gmail.com>

* Remove response location and adjust channelsFromFile

Signed-off-by: Nick Neisen <nwneisen@gmail.com>

* Change naming from 'provison' to 'bulkCreate'

Signed-off-by: Nick Neisen <nwneisen@gmail.com>

* Add gocsv to vendors folder

Signed-off-by: Nick Neisen <nwneisen@gmail.com>

* Update "bulk" naming

Signed-off-by: Nick Neisen <nwneisen@gmail.com>

* Move CSV to CLI and remove gocsv dependancy

Signed-off-by: Nick Neisen <nwneisen@gmail.com>

* Update docs and responses

Signed-off-by: Nick Neisen <nwneisen@gmail.com>

* Change AddThings to be CreateThings

Signed-off-by: Nick Neisen <nwneisen@gmail.com>

* Improve test coverage

Signed-off-by: nwneisen <nwneisen@gmail.com>

* Fixes after review

Signed-off-by: nwneisen <nwneisen@gmail.com>
2019-10-29 12:59:54 +01:00
Nick Neisen 5f5d7e4cfa MF-899 - Update README and official docs (#910)
* Update root README.md

Signed-off-by: nwneisen <nwneisen@gmail.com>

* Update official documents

Signed-off-by: nwneisen <nwneisen@gmail.com>

* Move docker template version location

Signed-off-by: nwneisen <nwneisen@gmail.com>

* Move template to dev guide and remove bold

Signed-off-by: nwneisen <nwneisen@gmail.com>

* Remove double spaces after periods

Signed-off-by: nwneisen <nwneisen@gmail.com>

* Change repo to repository

Signed-off-by: nwneisen <nwneisen@gmail.com>
2019-10-29 10:22:41 +01:00
Manuel Imperiale fec058c614 NOISSUE - Fix Redis envars (#903)
* NOISSUE - Fix Redis envars

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix url

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix typo

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2019-10-28 09:49:34 +01:00
Drasko DRASKOVIC 613e75f7ee Add disconnect on gen_server terminate() (#913)
Signed-off-by: Drasko DRASKOVIC <drasko.draskovic@gmail.com>
2019-10-27 17:57:43 +01:00
Drasko DRASKOVIC 7499f8deea Fix compilation (#911)
Signed-off-by: Drasko DRASKOVIC <drasko.draskovic@gmail.com>
2019-10-26 17:46:23 +02:00
Dušan Borovčanin 02fd492105 Revert "NOISSUE - Make event sourcing optional (#907)" (#909)
This reverts commit d309973a23.

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>
2019-10-25 13:57:41 +02:00
Aleksandar Novaković d309973a23 NOISSUE - Make event sourcing optional (#907)
* Add ES flat to the things service

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>

* Add env var for optional ES

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>
2019-10-25 00:12:31 +02:00
Manuel Imperiale e45e0ab307 NOISSUE - Fix InfluxDB env vars (#908)
Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2019-10-24 18:52:53 +02:00
Manuel Imperiale dea7c67ddb MF-890 - Add OPC-UA docs (#904)
* MF-890 - Add OPC-UA docs

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Add opcua.md in mkdocs.yml

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix reviews

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2019-10-24 09:49:11 +02:00
Dušan Borovčanin 2e664ffe10 Fix Elm version for ARM Docker images (#905)
Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>
2019-10-23 18:08:38 +02:00
Dušan Borovčanin cdf9347b01 NOISSUE - Update Protobuf version (#902)
* Update Protobuf version

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Update gRPC version

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>
2019-10-22 20:33:44 +02:00
Manuel Imperiale e16a025fba MF-886 - Add OPC-UA adapter (#878)
* NOISSUE- Add OPC-UA adapter

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* NOISSUE - Add opc-adapter PoC, docker and vendor

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Convert OPC messages to SenML

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Add gopcua package

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* lora-adapter typo

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Add OPC Reader

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Typo fix

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Typo fix

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Update copyright headers

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix reviews

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix reviews

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Add opc config

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Add all opc envars in the config

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Config typo

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Add route map

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Use opcua package instead of opc

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix OPCUA typo

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Rm MQTT sub

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Move interefaces to root

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix revieews and typo

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Update Gopkg.toml

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Add all envars into .env

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2019-10-22 17:44:19 +02:00
Dušan Borovčanin 8ab0b08c39 Fix Elm version in Dockerfile (#901)
Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>
2019-10-22 17:30:09 +02:00
Mirko Teodorovic f4f938a730 MF-532 - Password reset (#873)
* add users metadata

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add users metadata

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add users metadata

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add users metadata

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add users metadata

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add users metadata

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add users metadata

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add users metadata

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add metadata to users

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add metadata to users

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add metadata to users

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add metadata to users

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add metadata to users

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add metadata to users

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add metadata to users

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add metadata to users

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add metadata to users

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add metadata to users

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add metadata to users

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add default value for metadata

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add default value for metadata

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add default value for metadata

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add default value for metadata

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add default value for metadata

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add default value for metadata

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* when metadata is not set dont save 'null' string

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* when metadata is not set dont save 'null' string

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* when metadata is not set dont save 'null' string

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* change metadata type, add error handling

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* change metadata type, add error handling

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* change metadata type, add error handling

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* remove extra char

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* remove extra char

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* remove extra char

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* few small fixes

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* few small fixes

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* few small fixes

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix identityRes

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix identityRes

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix identityRes

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add users metadata

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add users metadata

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add users metadata

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* password reset, sketching

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* password reset, sketching

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* password reset, sketching

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* password reset, sketching

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* password reset, sketching

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* password reset, sketching

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* password reset, sketching

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* password reset, sketching

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* password reset, sketching

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* password reset, sketching

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* password reset, sketching

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* password reset, sketching

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* mail

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* mail

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* reset request endpoint

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* reset request endpoint

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* reset request endpoint

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add token

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add token

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add token

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add token methods

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add token methods

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add token methods

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* adding token endpoints

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* adding token endpoints

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* adding token endpoints

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* adding reset passw endpoint

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* adding reset passw endpoint

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* adding reset passw endpoint

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add logic for token verifying

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add logic for token verifying

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add logic for token verifying

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* remove mail from main

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* remove mail from main

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* remove mail from main

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* token and passwd  update logic

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* token and passwd  update logic

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* token and passwd  update logic

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* reorganize mailing code

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add comments for password reset flow

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add comments for password reset flow

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* change struct members to private

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* small changes

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* small changes

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* small changes

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add space

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add space

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add space

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* revert back changes used for testing

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* revert back changes used for testing

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* revert back changes used for testing

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* reorganize endpoints, extract host for link from Referer

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* reorganize endpoints, extract host for link from Referer

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* adding swagger docs for reset passw

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* adding swagger docs for reset passw

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix imports and some typos

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix imports and some typos

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* adding swagger docs for reset passw

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* reorganize endpoints, extract host for link from Referer

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* reorganize endpoints, extract host for link from Referer

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* addint test and update swagger for pass reset

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* addint test and update swagger for pass reset

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* addint test and update swagger for pass reset

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* adding test for endpoint

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* adding test for endpoint

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* adding test for endpoint

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* adding test for endpoint

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* adding endpoint test

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* adding endpoint test

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* adding endpoint test

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* change token generation

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* change token generation

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* change token generation

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* reorganize and change token gen

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* reorganize and change token gen

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* reorganize and change token gen

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* addint token and mail

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* adding token env

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* adding token env

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix error reporting

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix some comments and update readme

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix some comments and update readme

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix some comments and update readme

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* update readme

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix problmes due merge

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix problmes due merge

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix typos

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix typos

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add trusted certificates

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add endpoint for password change of currently authenticated user

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add env variable for conf reset endpoint

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add metadata to users

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add default value for metadata

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add default value for metadata

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* when metadata is not set dont save 'null' string

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* change metadata type, add error handling

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* remove extra char

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* few small fixes

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix identityRes

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add users metadata

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* password reset, sketching

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* password reset, sketching

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* password reset, sketching

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* password reset, sketching

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* reset request endpoint

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* adding token endpoints

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* adding reset passw endpoint

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add logic for token verifying

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* remove mail from main

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* small changes

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add space

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* revert back changes used for testing

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* reorganize endpoints, extract host for link from Referer

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* reorganize endpoints, extract host for link from Referer

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* addint test and update swagger for pass reset

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* adding endpoint test

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix some comments and update readme

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add env variable for conf reset endpoint

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix tests

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* resolving some style comments

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* minor fixes due to bad merge

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix problem with unsigned commits

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix problem with unsigned commits

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix call to users.New

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix call to users.New

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* rename file

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* rename to email.go

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* additional comments

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* additional comments

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* refactor email util

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* refactor email util

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* refactor email util

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* refactor email util

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* remove debug bin

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add driver conf for mail

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* refactor email

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* refactor email

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix failing tests

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add testify

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* small fix

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add token conf

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* small fix

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* small fix

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* configurable  email template

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix missing var

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add env for email template file

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add env for email template file

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix tests

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix test

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix test

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* revert to master

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* remove dev container

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* small fixes, typos, namings

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix typo

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* resolving comments

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* resolving comments

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* resolving comments

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* resolving comments

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* resolving comments

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add line

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* return err from email New()

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* change MF_TOKEN_RESET_ENDPOINT

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add and remove env vars

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* separate password logic into two methods Change and Reset

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* update comments

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* remove blank line

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* update docs

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* dont use camel case

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* small fix

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* reorganize email template for passw reset

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* reorganize email template for passw reset

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>
2019-10-22 14:53:14 +02:00
Nick Neisen 6687a738ce MF-785 - Change CanAccess to CanAccessByKey (#894)
* Change CanAccess to CanAccessByKey for things

Signed-off-by: Nick Neisen <nwneisen@gmail.com>

* Change CanAccess in remaining occurances

Signed-off-by: Nick Neisen <nwneisen@gmail.com>

* Regenerate generated files

Signed-off-by: Nick Neisen <nwneisen@gmail.com>

* Generate pb.go files with protoc 3.6.1

Signed-off-by: Nick Neisen <nwneisen@gmail.com>
2019-10-21 23:24:45 +02:00
Manuel Imperiale 1da48afc4c NOISSUE - fix security doc (#897)
Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2019-10-21 15:02:21 +02:00
Manuel Imperiale ddbf172ed5 NOISSUE - Fix typo in docs and README (#891)
Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2019-10-17 22:15:45 +02:00
Dušan Borovčanin df2cb80f12 Fix Nginx mTLS configuration (#885)
Sync mTLS and TLS config logs.

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>
2019-10-15 16:39:46 +02:00
Drasko DRASKOVIC b3ce48aca6 NOISSUE - Add MQTT UserName check on register and InstanceId in Redis (#884)
* Add InstanceId and verify UserName

Signed-off-by: drasko <drasko.draskovic@gmail.com>

* Fix thingID check (compare with UserName)

Signed-off-by: Drasko DRASKOVIC <drasko.draskovic@gmail.com>
2019-10-14 15:09:53 +02:00
Jovan Kostovski 351b04cd2e Add MQTT troubleshooting section (#882)
Signed-off-by: Jovan Kostovski <chombium@gmail.com>
2019-10-14 09:18:29 +02:00
Dušan Borovčanin 23b421bdc5 Fix provision tool connect error handling (#879)
Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>
2019-10-08 19:31:13 +02:00
Nick Neisen 93d939ea52 MF-875 - Add tracing to official documentation (#877)
* Tracing docs without example

Signed-off-by: nwneisen <nwneisen@gmail.com>

* Add tracing example using provisioning traces

Signed-off-by: Nick Neisen <nwneisen@gmail.com>

* Add link to Jaeger offical website

Signed-off-by: nwneisen <nwneisen@gmail.com>

* Remove extra http:// from UI URL

Signed-off-by: nwneisen <nwneisen@gmail.com>
2019-10-07 17:49:29 +02:00
Nick Neisen 66487eda42 MF-788 - Remove date and minimize copyright comments (#876)
* Update copyright comment for go files

Signed-off-by: nwneisen <nwneisen@gmail.com>

* Update copyright in assortment of file types

Signed-off-by: nwneisen <nwneisen@gmail.com>

* Remove missed copyright date

Signed-off-by: nwneisen <nwneisen@gmail.com>
2019-10-07 16:14:47 +02:00
Nick Neisen 23dff53a08 MF-787 - Add tags to user, thing, and channel spans (#869)
* Add span tags to tracing functions

Signed-off-by: nwneisen <nwneisen@gmail.com>
Signed-off-by: Nick Neisen <nwneisen@gmail.com>

* Add span tags to database funcions

Signed-off-by: nwneisen <nwneisen@gmail.com>
Signed-off-by: Nick Neisen <nwneisen@gmail.com>

* Add database interface for things tags

Signed-off-by: Nick Neisen <nwneisen@gmail.com>

* Add channels database interface for tags

Signed-off-by: Nick Neisen <nwneisen@gmail.com>

* Add database interface for users tags

Signed-off-by: Nick Neisen <nwneisen@gmail.com>

* Fix borken tests due to database interface

Signed-off-by: Nick Neisen <nwneisen@gmail.com>

* Update database interface naming

Signed-off-by: nwneisen <nwneisen@gmail.com>

* Adjust tests for name changes

Signed-off-by: nwneisen <nwneisen@gmail.com>

* Return previous db variable name

Signed-off-by: nwneisen <nwneisen@gmail.com>

* Add ctx after rebase

Signed-off-by: nwneisen <nwneisen@gmail.com>
2019-10-07 13:32:09 +02:00
Aleksandar Novaković 19f8922a9e Update docker-compose version for addons (#874)
Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>
2019-10-02 11:57:12 +02:00
Mirko Teodorovic ecc15b64b6 MF-859 - Channels metadata search (#867)
* add users metadata

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add users metadata

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add metadata to users

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add metadata to users

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* run.sh

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add metadata to users

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add default value for metadata

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add default value for metadata

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* when metadata is not set dont save 'null' string

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* when metadata is not set dont save 'null' string

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add metadata to channels

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add metadata to channels

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* change metadata type, add error handling

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add pause

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* remove extra char

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* retype from string to []byte

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add metadata to channels

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add metadata to channels

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add wait logic for gnatsd

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* few small fixes

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix identityRes

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add metadata to channels

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add metadata to channels

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add metadata to channels

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix waiting for gnatsd

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix waiting for gnatsd

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix waiting for gnatsd

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* changes to wait gnatsd logic

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* changes to wait gnatsd logic

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* testing query

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix query

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix query

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix merge problem

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* Update requests.go

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* Delete pwdrecovery.go

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix merge problem

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix merge problem

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* small change in comments

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* remove unused struct

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix comments

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>
2019-10-01 14:12:52 +02:00
Mirko Teodorovic 92a640f6fc MF-858 Users metadata (#861)
* add users metadata

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add users metadata

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add metadata to users

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add metadata to users

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* run.sh

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add metadata to users

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add default value for metadata

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add default value for metadata

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* when metadata is not set dont save 'null' string

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* when metadata is not set dont save 'null' string

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* change metadata type, add error handling

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add pause

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* remove extra char

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* retype from string to []byte

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add wait logic for gnatsd

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* few small fixes

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix identityRes

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add users metadata

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add users metadata

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* revert run.sh for now as gnats availability check is solved in other PR

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* revert changes

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* change metadata database/sql handling

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix commit issues

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* small change to errors handling

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* minor comment change

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>
2019-09-28 13:15:41 +02:00
Dušan Borovčanin 873ef4c96f NOISSUE - Simplify MQTT benchmarking tool (#852)
* Fix user creation

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Extract client creation to separate method

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Remove message generating in separate goroutine

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Remove runSub and runPub methods

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Refactor benchmark code

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Fix typos

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Update client message handling

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Fix microseconds typo

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Simplify client

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Don't take zero-messages clients int count

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Fix token timeout condition

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Add timeout for publisher

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Update Paho lib version

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Temporary drop SenML and subscribe support

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Calculate payload size

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Remove templates

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>
2019-09-25 19:02:29 +02:00
Ivan Milošević 5e3bb270a3 NOISSUE - Upgrade Go version to 1.13 in container images (#868)
* Change go version to 1.13 in docker images

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* update go version on CI

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* fix and add debug code in ci.sh

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Remove other Go version from PATH on semaphoreci
Add new go installation to PATH

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Create dir for golang 1.13

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* remove debug code

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Add check for current go version before upgrade in ci script

Signed-off-by: Ivan Milošević <iva@blokovi.com>
2019-09-23 21:43:21 +02:00
Parham Alvani c8cb2655c0 MF-820 - Fetch messages for a particular device (#843)
* feat: Add Support for Publisher Query in Postgres

Signed-off-by: Parham Alvani <parham.alvani@gmail.com>

* chore: Remove Redundant Case

Signed-off-by: Parham Alvani <parham.alvani@gmail.com>

* chore: Add Test for Postgres Query

Signed-off-by: Parham Alvani <parham.alvani@gmail.com>
2019-09-21 00:20:09 +02:00
Aleksandar Novaković b83439fcc2 Update gorilla websocket version (#865)
Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>
2019-09-18 18:25:18 +02:00
Aleksandar Novaković 08105205e1 NOISSUE - Update aedes version and fix Dockerfile (#863)
* Update MQTT adapter and add message TTL

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>

* Fix clean command

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>
2019-09-17 20:25:38 +02:00
Mirko Teodorovic d20dfa84bc NOISSUE - Search by metadata (#849)
* add metadata search

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add metadata search

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add metadata search

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add metadata search

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add metadata search

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add metadata search

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add metadata search

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add space

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* metadata test case

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add docs and update swagger

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add test for metadata

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add test for metadata

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* remove commented out section

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* small change to test

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* remove debug printf

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* small fix for metadata

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix tests

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* and => and/or

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add line

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix mixed func params

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* metadata will be added to channels later

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix return type

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix typings

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix typings

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add migration

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* remove var

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* respecting the order of migrations

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>
2019-09-17 15:46:24 +02:00
Ivan Milošević a6cf76709d MF-846 - Install python in docker build for aedes mqtt image (#860)
* Multi-stage docker build for aedes mqtt image

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Add copyright header

Signed-off-by: Ivan Milošević <iva@blokovi.com>
2019-09-16 18:25:34 +02:00
Drasko DRASKOVIC 5ef6aa84de NOISSUE - Clean NginX files, move .gitignores to dirs (#853)
* Remove nginx entrypoint trash, move .gitignore

Signed-off-by: drasko <drasko.draskovic@gmail.com>

* Add node_modules to Aedes .gitignore

Signed-off-by: drasko <drasko.draskovic@gmail.com>

* Move docker .gitignore to nginx

Signed-off-by: drasko <drasko.draskovic@gmail.com>
2019-09-16 11:47:34 +02:00
Drasko DRASKOVIC 3b19292966 NOISSUE - Add docker-compose for MQTT cluster (#841)
* Add MQTT cluster configs

Signed-off-by: Drasko DRASKOVIC <drasko.draskovic@gmail.com>

* Bump worker_connections value in nginx.conf

Signed-off-by: drasko <drasko.draskovic@gmail.com>

* Revert config.toml

Signed-off-by: drasko <drasko.draskovic@gmail.com>

* Fix cluster

Signed-off-by: drasko <drasko.draskovic@gmail.com>

* Move non-ssl server into the same directive

Signed-off-by: drasko <drasko.draskovic@gmail.com>

* Fix cluster

Signed-off-by: drasko <drasko.draskovic@gmail.com>

* Remove unnecessary comments

Signed-off-by: drasko <drasko.draskovic@gmail.com>

* Organize docker-compose and nginx conf files

Signed-off-by: drasko <drasko.draskovic@gmail.com>

* Correct nginx.conf for mTLS and aux changes

Signed-off-by: drasko <drasko.draskovic@gmail.com>

* Fix NginX conf trailing slash in proxy_pass

Signed-off-by: drasko <drasko.draskovic@gmail.com>

* Fix path for MQTT

Signed-off-by: drasko <drasko.draskovic@gmail.com>

* Fix minorstuff in gitignore and comments

Signed-off-by: drasko <drasko.draskovic@gmail.com>
2019-09-12 16:55:24 +02:00
Aleksandar Novaković 4870119270 Add debug logs to the WS adapter (#848)
Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>
2019-09-11 15:34:04 +02:00
Mirko Teodorovic 97327ab05f NOISSUE - Add measuring time from pub to sub (#839)
* refactor code

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* connect each thing with each channel

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* reverting - structure fields must be exported

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* reverting - structure fields must be exported

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* revert some names

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* move meausuring time start

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add pub-to-sub delivery time measure

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add pub-to-sub delivery time measure

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add pub-to-sub delivery time measure

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add pub-to-sub delivery time measure

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* improve sync between pub and sub

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* improve sync between pub and sub

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* improve sync between pub and sub

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* improve sync between pub and sub

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* improve sync between pub and sub

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* improve sync between pub and sub

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add random payload

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* revert changes for config.toml

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add random payload

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* remove printfs

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add logging

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add payload

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add payload

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* rename variable

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add payload

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* small changes

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* refactor sync

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* refactor sync

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* refactor results

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* change sync and result collecting for sub

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix comments

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>
2019-09-06 14:59:35 +02:00
Parham Alvani 115d94bd1b fix: Correct 404 and Content-Type Issues in MQTT Version Endpoint (#837)
Signed-off-by: Parham Alvani <parham.alvani@gmail.com>
2019-09-05 15:10:18 +02:00
Mirko Teodorovic 640dfb7e19 NOISSUE - update mqtt prov tool and some refactor (#831)
* refactor code

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* connect each thing with each channel

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add some comments

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* connect each thing with each channel

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* reverting - structure fields must be exported

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* reverting - structure fields must be exported

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* revert some names

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* move meausuring time start

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* revert changes to .gitignore

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* small changes

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>
2019-09-05 13:39:58 +02:00
Dušan Borovčanin e17a3de1d0 NOISSUE - Use Thing ID to update certs data (#827)
* Use ThingID to update certs

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Fix logging typo

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Update API tests and service mock

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>
2019-09-03 13:54:56 +02:00
Drasko DRASKOVIC ec4b46b669 NOISSUE - Improve VerneMQ plugin code, add configurable gRPC pool size (#836)
* Enable gRPC support

Signed-off-by: drasko <drasko.draskovic@gmail.com>

* Add poolboy process pool

Signed-off-by: drasko <drasko.draskovic@gmail.com>

* Add Verne docker-compose

Signed-off-by: drasko <drasko.draskovic@gmail.com>

* Improve code, add settings for pool size

Signed-off-by: drasko <drasko.draskovic@gmail.com>
2019-09-03 01:05:36 +02:00
Drasko DRASKOVIC 38d85ae03b NOISSUE - Use gRPC for VerneMQ (#835)
* Enable gRPC support

Signed-off-by: drasko <drasko.draskovic@gmail.com>

* Add poolboy process pool

Signed-off-by: drasko <drasko.draskovic@gmail.com>

* Add Verne docker-compose

Signed-off-by: drasko <drasko.draskovic@gmail.com>

* Clean the code in genservers

Signed-off-by: drasko <drasko.draskovic@gmail.com>
2019-09-02 23:22:46 +02:00
Drasko DRASKOVIC 655f421ca9 NOISSUE - Fix proto files in VerneMQ (#834)
* Add socket pool and fix pattern matching

Signed-off-by: drasko <drasko.draskovic@gmail.com>

* Fix hackney req

Signed-off-by: drasko <drasko.draskovic@gmail.com>

* Change docker-compose

Signed-off-by: drasko <drasko.draskovic@gmail.com>

* Correct protobuf and add MQTT loback ignore

Signed-off-by: drasko <drasko.draskovic@gmail.com>

* Bring back docker-compose

Signed-off-by: drasko <drasko.draskovic@gmail.com>
2019-09-02 21:20:18 +02:00
Drasko DRASKOVIC 9ccc37c4b1 NOISSUE - Fix hackney HTTP request (#833)
* Add socket pool and fix pattern matching

Signed-off-by: drasko <drasko.draskovic@gmail.com>

* Fix hackney req

Signed-off-by: drasko <drasko.draskovic@gmail.com>
2019-09-02 18:11:58 +02:00
Drasko DRASKOVIC a8836d67e0 Add socket pool and fix pattern matching (#830)
Signed-off-by: drasko <drasko.draskovic@gmail.com>
2019-09-01 21:53:04 +02:00
Ivan Milošević 2d1347eee3 Switch secure of WS connection according to secure of http connection of UI (#829)
Signed-off-by: Ivan Milošević <iva@blokovi.com>
2019-08-30 16:23:28 +02:00
Alexander Obukhov 1a4844cee1 NOISSUE - Use current hostname instead of localhost for a WebSocket connection in the UI (#826)
Signed-off-by: Alexander Obukhov <dev@sprql.space>
2019-08-30 14:21:29 +02:00
Drasko DRASKOVIC f090573567 NOISSUE - Improve MQTT benchmarking tools (#828)
* Refactor mqtt-bench

Signed-off-by: drasko <drasko.draskovic@gmail.com>

* Refactor mqtt-bench and provision tools

Signed-off-by: drasko <drasko.draskovic@gmail.com>

* Improve README

Signed-off-by: drasko <drasko.draskovic@gmail.com>

* Remove provision generated toml file

Signed-off-by: drasko <drasko.draskovic@gmail.com>

* Update README

Signed-off-by: drasko <drasko.draskovic@gmail.com>

* Replace `tcp` with `tcps` for mtls

Signed-off-by: drasko <drasko.draskovic@gmail.com>
2019-08-30 14:09:12 +02:00
Mirko Teodorovic bfd12304ac NOISSUE - update mqtt benchmark (#824)
* update gitignore

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* fix dates for cert

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* reorganize code

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* update docs

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* print to stdout for creating result file from script

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add script for testing

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add time measeuring

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* separate test in one file each

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* move config.toml

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* update docs

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add connection check, and prevent blocking of publisher when connection fails

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>
2019-08-27 14:21:39 +02:00
Dušan Borovčanin e7891ec6ab Add encryption key to env vars table (#823)
Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>
2019-08-15 16:26:31 +02:00
Dušan Borovčanin 5c924bd5a3 NOISSUE - Add version endpoint to MQTT adapter (#816)
* Add version endpoint to MQTT adapter

Expose CoAP version endpoint in the default docker-compose.

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Use /tcp insted of implicit protocol name

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>
2019-08-14 01:07:11 +02:00
Mirko Teodorovic 19834dfc51 MF-295 add mqtt benchmark tool (#817)
* adding mqtt benchmark tool

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* adding mqtt benchmark tool - vendoring

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add mtls support

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* update readme

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* tool for channel provision, reorganize code

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* adding config toml

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* tool for channel provision, adding ssl cert gen

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add config toml

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add readme for provision

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* update readme

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* remove some printing

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add test configs

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* remove some dead code, and sort comments

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* replace statistics lib

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* replace statistics lib

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* add cobra and viper

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* styling changes

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* styling changes

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* styling changes

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* remove statistic lib

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* change type visibility

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* remove empty line

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* update vendor

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* update deps

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* rename variable

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* small changes, adding comments

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* error handling

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>
2019-08-13 12:56:31 +02:00
Mirko Teodorovic 9a78b1111f update mqtts commands (#815)
Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>
2019-08-05 11:43:05 +02:00
Mirko Teodorovic 9c52c3816a Fix typo (#814)
Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>
2019-08-02 15:22:28 +02:00
Dušan Borovčanin f03949a003 NOISSUE - Support encrypted bootstrap (#796)
* Fix API docs

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Provide secured bootstrapping

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Fix test and mock methods signatures

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Fix tests

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Fix typos

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Add encrypte bootstrap test

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Update docs

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Remove duplicated docs

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Use secret key to encrypt bootstrap request

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Use secret key for secure bootstrapping

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Move encryption to ConfigReader

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Remove ConfigReader from Service

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Fix tests

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Add reader tests

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Update API docs

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Unset key env variable

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Add endpoint test for secure bootstrap

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>
2019-08-02 14:35:31 +02:00
Aleksandar Novaković 68e8eda92d Add config to writers docs (#812)
Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>
2019-08-01 13:13:24 +02:00
Drasko DRASKOVIC c751d0e656 NOISSUE - Add VerneMQ support (#809)
* Add VerneMQ

Signed-off-by: drasko <drasko.draskovic@gmail.com>

* Fix Aedes build

Signed-off-by: drasko <drasko.draskovic@gmail.com>

* Update VerneMQ README

Signed-off-by: drasko <drasko.draskovic@gmail.com>

* Remove unused gRPC client proto files

Signed-off-by: drasko <drasko.draskovic@gmail.com>

* Add topic rewrite for CT

Signed-off-by: drasko <drasko.draskovic@gmail.com>

* Fix CT links on NATS SUB

Signed-off-by: drasko <drasko.draskovic@gmail.com>

* Fix NATS PUB/SUB with spawned process

Signed-off-by: drasko <drasko.draskovic@gmail.com>

* Fix typos

Signed-off-by: drasko <drasko.draskovic@gmail.com>

* Fix README

Signed-off-by: drasko <drasko.draskovic@gmail.com>
2019-08-01 12:40:49 +02:00
Aleksandar Novaković 55d03ecdda NOISSUE - Add content type as part of MQTT subscription topic (#810)
* Add content type to MQTT sub topic

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>

* Remove default content type value

Remove default content type value (SenML JSON) and replace it with
an empty string.

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>

* Update docs in accordance with changes

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>
2019-07-29 17:42:04 +02:00
Nikola Marčetić 2996193a50 Updated Readme (#808)
* Updated Readme

Signed-off-by: nmarcetic <n.marcetic86@gmail.com>

* Fix typo

Signed-off-by: nmarcetic <n.marcetic86@gmail.com>

* Fix grammar

Signed-off-by: nmarcetic <n.marcetic86@gmail.com>
2019-07-25 19:20:11 +02:00
Ivan Milošević 6d2aa71ccc NOISSUE - Add rungw command to Makefile (#805)
* Add `make rungw` command to run mainflux without jaeger
Add name to jaeger container

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Add explanatory comment in Makefile

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Add new line on end file

Signed-off-by: Ivan Milošević <iva@blokovi.com>
2019-07-25 17:24:26 +02:00
Ivan Milošević 54823bed25 NOISSUE - Fix building UI docker image for arm (#806)
* Add entrypoint in arm Dockerfile
Switch from ash to dash in entrypoint script

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Add new line on file end

Signed-off-by: Ivan Milošević <iva@blokovi.com>
2019-07-25 17:15:06 +02:00
Aleksandar Novaković 649986b19f MF-757 - Add support for explicit content type (#771)
* Add format to raw message and content type mapping

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>

* Add format as part of content type header

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>

* Add format to content-type header for WS adapter

Add format as part of content-type and add support for
content-type header and query parameter.

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>

* Add support for format and content-type in MQTT adapter

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>

* Add format and content-type to CoAP adapter

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>

* Add documentation for content type and format

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>

* Add comments to MQTT code

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>

* Fix SenML JSON CoAP code type

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>

* Remove format from the adapters

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>

* Remove format from the docs

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>

* Remove format field from MQTT adapter

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>

* Update content type to be optional

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>

* Add support for CBOR content type in the normalizer

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>

* Update docs for MQTT content type

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>
2019-07-25 02:22:28 +02:00
Nikola Marčetić 99ced38229 Updated Readme (#807)
Signed-off-by: nmarcetic <n.marcetic86@gmail.com>
2019-07-24 15:02:21 +02:00
Mirko Teodorovic 8efcaee350 NOISSUE - make correct reference to MF_UI_PORT (#804)
* make correct reference to MF_UI_PORT

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* remove echo

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>
2019-07-24 12:44:39 +02:00
Aleksandar Novaković a3de6953f8 Add support for optional Jaeger (#801)
Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>
2019-07-23 14:31:02 +02:00
Dušan Borovčanin 748b7c1ee8 Fix CoAP adapter subtopics (#797)
Remove `messages` part from the subtopics.

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>
2019-07-22 13:24:50 +02:00
Nikola Marčetić 6af65ea159 Added changelog for 0.9.0 release (#799)
Signed-off-by: nmarcetic <n.marcetic86@gmail.com>
2019-07-19 17:56:23 +02:00
Aleksandar Novaković b95bb82e0b Fix users service to work in offline mode (#795)
Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>
2019-07-19 12:28:45 +02:00
Ivan Milošević 8bda799ef9 Create and push docker manifest for new release from Makefile (#794)
Signed-off-by: Ivan Milošević <iva@blokovi.com>
2019-07-18 17:31:37 +02:00
Aleksandar Novaković 61f1c2e379 MF-399 - Add open tracing support (#782)
* Add open tracing dependencies

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>

* Add open tracing to users service

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>

* Add open tracing to the things service

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>

* Add open tracing to the http adapter

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>

* Add open tracing to the ws adapter

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>

* Add open tracing to the CoAP adapter

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>

* Update LoRa adapter in accordance with changes

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>

* Update SDK tests in accordance with changes

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>

* Update bootstrap service in accordance with changes

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>

* Update reader services with accordance with changes

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>

* Update .env and docker-compose file

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>

* Add jaeger and timeout env vars

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>

* Fix broken test for can access by id endpoint

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>

* Update deps with proto empty package

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>
2019-07-18 15:01:09 +02:00
Ivan Milošević 1ad14e7fd3 fix mainflux_id parameter in bootstrap swagger (#789)
Signed-off-by: Ivan Milošević <iva@blokovi.com>
2019-07-16 16:21:37 +02:00
Aleksandar Novaković a6a8648e4f MF-783 - Allow access checking by a thing ID (#784)
* Add can access by things ID endpoint to things service

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>

* Add new auth endpoint to the swagger docs

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>

* Add test for the new endpoint of the things service

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>
2019-07-15 18:28:15 +02:00
Dušan Borovčanin 4a7af7c0b3 NOISSUE - Fix CoAP adapter (#779)
* Fix CoAP mux

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Remove prints and commented code

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>
2019-07-15 17:49:02 +02:00
Ivan Milošević b0a79436b8 Fix MF_THINGS_AUTH_GRPC_PORT in addons' docker-compose files (#781)
Signed-off-by: Ivan Milošević <iva@blokovi.com>
2019-07-11 23:19:08 +02:00
Aleksandar Novaković 4ab2e396c2 NOISSUE - Add authorization HTTP API to things service (#772)
* Add authorization HTTP API to things service

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>

* Add new tests and update existing ones

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>

* Update swagger documentation

Update swagger documentation for auth endpoints.

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>

* Update README docs for things service

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>

* Update docker-compose and fix endpoint typo

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>

* Remove commented code

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>
2019-07-04 17:06:55 +02:00
Manuel Imperiale 22a3b881fd NOISSUE - Typo fix (#777)
Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2019-07-04 09:57:03 +02:00
Aleksandar Novaković aeebf06799 Remove cli executable from repo (#776)
Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>
2019-07-04 05:19:49 +08:00
Mirko Teodorovic c396e6ded8 fix docker-compose env (#775)
Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>
2019-07-03 20:50:28 +02:00
Manuel Imperiale f5b6c63cca NOISSUE - Use .env vars in docker-compose (#770)
* NOISSUE - Use .enr vars in docker-compose

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Add README and dupllicated .env in docker repo

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Use .env for Bootstrap Docker Composition

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Use .env for Cassandra (Reader-Writer) Docker Composition

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Use .env for InfluxDB (Reader-Writer) Docker Composition

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Use .env for MongoDB (Reader-Writer) Docker Composition

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Use .env for Postgres (Reader-Writer) Docker Composition

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Use .env for LoRa adapter Docker Composition

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix Grafana port in .env file

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Update README.md

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>

* remove docker/.env and update README.md

Signed-off-by: Mirko Teodorovic <mirko.teodorovic@gmail.com>
2019-07-02 14:22:12 +02:00
Dušan Borovčanin 81cd73e277 Use errors and null packets in authorized pub/sub (#773)
Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>
2019-07-01 11:22:31 +02:00
Mirko Teodorovic 204aec6171 MF-663 - enable nginx port conf from docker env (#769)
* MF-722 - Change UUID lib (#746)

* Update uuid package and update things serivce

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>

* Update bootstrap service tests

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>

* Update existing postgres writer tests

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>
Signed-off-by: mteodor <mirko.teodorovic@gmail.com>

* MF-732 - Add Postgres reader (#740)

* NOISSUE - Fix Readers logs

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* MF-732 - Add Postgres reader

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix total count

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Rm commented code

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Add Postgres reader tests

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix editor format

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Change UUID lib

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
Signed-off-by: mteodor <mirko.teodorovic@gmail.com>

* MF-742 - Things to support single user scenario (#749)

* Add single user mode to things service

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>

* Add tests for things/users package

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>

* Update init order in main

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>
Signed-off-by: mteodor <mirko.teodorovic@gmail.com>

* Update grpc and protobuf deps in mqtt adapter (#751)

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>
Signed-off-by: mteodor <mirko.teodorovic@gmail.com>

* Fix MQTT raw message deserialization (#753)

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>
Signed-off-by: mteodor <mirko.teodorovic@gmail.com>

* NOISSUE - Add certificate fields to the Bootstrap service (#752)

* Add cert fields to the BS

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Add cert fields when creating a config

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Add update cert endpoint

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Fix key column name

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Add cert fields to db converters

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Secure cert update endpoint

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Authroize cert update methods

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Fix Bootstrap service tests

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Add cert update service tests

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Update endpoit tests

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Update API docs

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Update request tests

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Fix request tests

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Update repository tests

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Fix typo in repo tests

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>
Signed-off-by: mteodor <mirko.teodorovic@gmail.com>

* NOISSUE - Add searchable Things name (#750)

* NOISSUE - Add searchable Things name

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix reviews

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix typo

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Add postgres schema validation and tests

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Add namme tests in requests_test

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Typo fix

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Rm requests_test

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Add name in ListThings loggins

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Add invalidName var for tests

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Set maxNameSize to 1024

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix postgres test

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix total when filtering things by name

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix review

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
Signed-off-by: mteodor <mirko.teodorovic@gmail.com>

* Add missing Websocket.js into docker ui image (#755)

Signed-off-by: Ivan Milošević <iva@blokovi.com>
Signed-off-by: mteodor <mirko.teodorovic@gmail.com>

* MF-466 - ARM docker deployment (#756)

* Add arm Dockerfiles
Change version of postgres images in docker-compose to use versions with  multiarch

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* docker compose file

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* remove bin qemu file

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* remove unnecesarry comments

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Add make dockers_arm32v7 in Makefile

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Remove docker/Dockerfile.arm

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Add build for arm32v7 in ui Makefile

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Add arm32v7 tags

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* add docker-compose for arm
remove thing-db in docker compose (use same db as user)
fix dockerfile and makefile

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* fix thing database env variable

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Switch back to separate database for things and users

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* rename arm32v7 to arm

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* remove arm32v7 tag
rename arm32v7 image names

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Rename command for making mqtt and ui individual images, to match other miscroservices

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Push arm docker images

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* fix fucntion call in ci.sh

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* mergiing ui/Dockerfile

Signed-off-by: Ivan Milošević <iva@blokovi.com>
Signed-off-by: mteodor <mirko.teodorovic@gmail.com>

*  NOISSUE - Add searchable Channels name  (#754)

* NOISSUE - Add searchable Things name

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix reviews

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix typo

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Add postgres schema validation and tests

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Add namme tests in requests_test

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* NOISSUE - Add searchable Channels name

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix test description

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix bootstrap mocks

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix reviews

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
Signed-off-by: mteodor <mirko.teodorovic@gmail.com>

* NOISSUE - Remove installing non-existent package in ci (#758)

* Remove installing non-existent  package from ci

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* remove branch master condition in ci.sh to test it

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* fix syntax error

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* update apt

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* build arm images for latest release
push ui and mqtt arm images
install only qemu-user-static

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* clean docker after pushing amd64 images

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* installing all qemu app

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* remove docker images before building ui and mqtt arm-images

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* prune dockers and test only arm build

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* fix syntax error

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* fix moving qemu-arm-static

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* installing qemu with apt-get

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* add another apt-get update

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* apt install one liner

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* testing ci script

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* After installing qemu, get appropriate version

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Test ci without amd64 dockers

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* remove tests from ci for test purpose

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Uncomment commands

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Remove whitespace and change order of building images

Signed-off-by: Ivan Milošević <iva@blokovi.com>
Signed-off-by: mteodor <mirko.teodorovic@gmail.com>

* nginx

Signed-off-by: mteodor <mirko.teodorovic@gmail.com>

* change to openresty

Signed-off-by: mteodor <mirko.teodorovic@gmail.com>

* change to openresty and accept env from docker compose

Signed-off-by: mteodor <mirko.teodorovic@gmail.com>

* revert to master

Signed-off-by: mteodor <mirko.teodorovic@gmail.com>

* revert to master

Signed-off-by: mteodor <mirko.teodorovic@gmail.com>

* revert to master

Signed-off-by: mteodor <mirko.teodorovic@gmail.com>

* NOISSUE - Fix Docker for ARM (#760)

* NOISSUE - Fix Docker for ARM

Signed-off-by: drasko <drasko.draskovic@gmail.com>

* Correct ARGs

Signed-off-by: drasko <drasko.draskovic@gmail.com>

* Fix docker-compose for ARM

Signed-off-by: drasko <drasko.draskovic@gmail.com>

* Add docker manifest

Signed-off-by: drasko <drasko.draskovic@gmail.com>

* cp not mv qemu from mf root

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* fix amd64 docker names for ui and mqtt images

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* edit ci.sh for testing purposes

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* remove bachslash from make manifest
edits in ci for testing purposes

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* fix manifest call

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* add manifest on version realese
delete space before latest argument
fix for loop in manifest creation

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* include tests

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* docker system prune
remove tests for testin purposes

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Add variant in manifest file for armv7

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Remove white space

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* paralelise the compilation

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* fix place of -j$NPROC

Signed-off-by: Ivan Milošević <iva@blokovi.com>
Signed-off-by: mteodor <mirko.teodorovic@gmail.com>

* adding env variable

Signed-off-by: mteodor <mirko.teodorovic@gmail.com>

* fix variant option for manifest annotate (#765)

Signed-off-by: Ivan Milošević <iva@blokovi.com>
Signed-off-by: mteodor <mirko.teodorovic@gmail.com>

* enable port configure from env var in docker-compose

Signed-off-by: mteodor <mirko.teodorovic@gmail.com>

* enable port configure from env var in docker-compose

Signed-off-by: mteodor <mirko.teodorovic@gmail.com>

* enable port configure from env var in docker-compose

Signed-off-by: mteodor <mirko.teodorovic@gmail.com>

* use docker env to set port

Signed-off-by: mteodor <mirko.teodorovic@gmail.com>

* add env to conf port in nginx.conf

Signed-off-by: mteodor <mirko.teodorovic@gmail.com>

* replace string with docker env

Signed-off-by: mteodor <mirko.teodorovic@gmail.com>

* replace string with docker env

Signed-off-by: mteodor <mirko.teodorovic@gmail.com>

* Update docs (#766)

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>
Signed-off-by: mteodor <mirko.teodorovic@gmail.com>

* remove not needed comment

Signed-off-by: mteodor <mirko.teodorovic@gmail.com>

* adding .env file for default UI_PORT - if no enviroment UI_PORT is set value from .env file is used

Signed-off-by: mteodor <mirko.teodorovic@gmail.com>

* get default UI_PORT variable value from .env file

Signed-off-by: mteodor <mirko.teodorovic@gmail.com>

* set default port value to 3000

Signed-off-by: mteodor <mirko.teodorovic@gmail.com>
2019-06-24 20:13:02 +02:00
Dušan Borovčanin aa219b6f99 NOISSUE - Fix count when search by name is performed (#767)
* Fix total field when search by name

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Fix tests to check total count

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>
2019-06-24 14:03:48 +02:00
Dušan Borovčanin f27cb1d019 Update docs (#766)
Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>
2019-06-21 14:07:24 +02:00
Ivan Milošević 41897cee11 fix variant option for manifest annotate (#765)
Signed-off-by: Ivan Milošević <iva@blokovi.com>
2019-06-20 15:31:56 +02:00
Drasko DRASKOVIC 91274aca9b NOISSUE - Fix Docker for ARM (#760)
* NOISSUE - Fix Docker for ARM

Signed-off-by: drasko <drasko.draskovic@gmail.com>

* Correct ARGs

Signed-off-by: drasko <drasko.draskovic@gmail.com>

* Fix docker-compose for ARM

Signed-off-by: drasko <drasko.draskovic@gmail.com>

* Add docker manifest

Signed-off-by: drasko <drasko.draskovic@gmail.com>

* cp not mv qemu from mf root

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* fix amd64 docker names for ui and mqtt images

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* edit ci.sh for testing purposes

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* remove bachslash from make manifest
edits in ci for testing purposes

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* fix manifest call

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* add manifest on version realese
delete space before latest argument
fix for loop in manifest creation

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* include tests

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* docker system prune
remove tests for testin purposes

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Add variant in manifest file for armv7

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Remove white space

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* paralelise the compilation

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* fix place of -j$NPROC

Signed-off-by: Ivan Milošević <iva@blokovi.com>
2019-06-20 14:14:06 +02:00
Ivan Milošević b53979104c NOISSUE - Remove installing non-existent package in ci (#758)
* Remove installing non-existent  package from ci

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* remove branch master condition in ci.sh to test it

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* fix syntax error

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* update apt

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* build arm images for latest release
push ui and mqtt arm images
install only qemu-user-static

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* clean docker after pushing amd64 images

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* installing all qemu app

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* remove docker images before building ui and mqtt arm-images

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* prune dockers and test only arm build

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* fix syntax error

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* fix moving qemu-arm-static

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* installing qemu with apt-get

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* add another apt-get update

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* apt install one liner

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* testing ci script

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* After installing qemu, get appropriate version

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Test ci without amd64 dockers

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* remove tests from ci for test purpose

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Uncomment commands

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Remove whitespace and change order of building images

Signed-off-by: Ivan Milošević <iva@blokovi.com>
2019-06-12 21:11:45 +02:00
Manuel Imperiale 63de955a7c NOISSUE - Add searchable Channels name (#754)
* NOISSUE - Add searchable Things name

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix reviews

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix typo

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Add postgres schema validation and tests

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Add namme tests in requests_test

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* NOISSUE - Add searchable Channels name

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix test description

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix bootstrap mocks

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix reviews

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2019-06-11 10:37:25 +02:00
Ivan Milošević 44cc20b9ca MF-466 - ARM docker deployment (#756)
* Add arm Dockerfiles
Change version of postgres images in docker-compose to use versions with  multiarch

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* docker compose file

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* remove bin qemu file

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* remove unnecesarry comments

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Add make dockers_arm32v7 in Makefile

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Remove docker/Dockerfile.arm

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Add build for arm32v7 in ui Makefile

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Add arm32v7 tags

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* add docker-compose for arm
remove thing-db in docker compose (use same db as user)
fix dockerfile and makefile

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* fix thing database env variable

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Switch back to separate database for things and users

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* rename arm32v7 to arm

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* remove arm32v7 tag
rename arm32v7 image names

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Rename command for making mqtt and ui individual images, to match other miscroservices

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Push arm docker images

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* fix fucntion call in ci.sh

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* mergiing ui/Dockerfile

Signed-off-by: Ivan Milošević <iva@blokovi.com>
2019-06-11 02:14:05 +02:00
Ivan Milošević 62e7d69f7b Add missing Websocket.js into docker ui image (#755)
Signed-off-by: Ivan Milošević <iva@blokovi.com>
2019-06-10 00:15:12 +02:00
Manuel Imperiale c8979ac297 NOISSUE - Add searchable Things name (#750)
* NOISSUE - Add searchable Things name

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix reviews

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix typo

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Add postgres schema validation and tests

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Add namme tests in requests_test

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Typo fix

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Rm requests_test

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Add name in ListThings loggins

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Add invalidName var for tests

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Set maxNameSize to 1024

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix postgres test

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix total when filtering things by name

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix review

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2019-05-30 15:33:49 +02:00
Dušan Borovčanin cac1e35b58 NOISSUE - Add certificate fields to the Bootstrap service (#752)
* Add cert fields to the BS

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Add cert fields when creating a config

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Add update cert endpoint

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Fix key column name

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Add cert fields to db converters

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Secure cert update endpoint

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Authroize cert update methods

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Fix Bootstrap service tests

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Add cert update service tests

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Update endpoit tests

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Update API docs

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Update request tests

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Fix request tests

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Update repository tests

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Fix typo in repo tests

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>
2019-05-22 23:22:19 +02:00
Aleksandar Novaković 6d6f3cc858 Fix MQTT raw message deserialization (#753)
Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>
2019-05-21 11:27:26 +02:00
Aleksandar Novaković 066057d914 Update grpc and protobuf deps in mqtt adapter (#751)
Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>
2019-05-17 16:51:22 +02:00
Aleksandar Novaković f9093413ee MF-742 - Things to support single user scenario (#749)
* Add single user mode to things service

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>

* Add tests for things/users package

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>

* Update init order in main

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>
2019-05-17 16:06:21 +02:00
Manuel Imperiale 5fec0a9e92 MF-732 - Add Postgres reader (#740)
* NOISSUE - Fix Readers logs

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* MF-732 - Add Postgres reader

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix total count

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Rm commented code

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Add Postgres reader tests

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix editor format

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Change UUID lib

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2019-05-16 18:00:19 +02:00
Aleksandar Novaković 3125f0bbc2 MF-722 - Change UUID lib (#746)
* Update uuid package and update things serivce

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>

* Update bootstrap service tests

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>

* Update existing postgres writer tests

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>
2019-05-16 13:35:13 +02:00
Aleksandar Novaković 0dc4d12e73 Add performance improvement to writer filtering (#744)
Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>
2019-05-16 13:19:05 +02:00
Dušan Borovčanin 9d30006d31 NOISSUE - Update nginx version (#748)
* Update version of the Nginx image

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Add undefined check for HTTP and WS

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>
2019-05-16 11:59:23 +02:00
Aleksandar Novaković 5202d734c9 MF-574 - Add missing environment variables to Cassandra writer (#745)
* Add username, password and port env var

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>

* Update existing readme files with new env vars

Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>
2019-05-15 23:31:40 +02:00
Aleksandar Novaković cc5c66bfba Fix CI with fixed plugin versions (#747)
Signed-off-by: Aleksandar Novakovic <anovakovic01@gmail.com>
2019-05-15 17:41:18 +02:00
Dušan Borovčanin f5bb65d0e4 NOISSUE - Add compile test to CI (#743)
* Add compile tests to CI script

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Fix parallelization flag

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>
2019-05-13 14:48:54 +02:00
Mirko Teodorovic e46164f93f fix building problems (#741)
Signed-off-by: mteodor <mirko.teodorovic@gmail.com>
2019-05-11 17:16:25 +02:00
Manuel Imperiale d2fc379e3f NOISSUE - Fix Postgres logs in Things service (#734)
* NOISSUE - Fix Postgres logs in Things service

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix reviews

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix reviews

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix reviews

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix reviews

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix reviews

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix uuid package

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2019-05-10 18:55:13 +02:00
Aleksandar Novaković 5e5fe88927 MF-708 - Assign Writer(s) to a channel (#737)
* Add support for channel filtering using config

Add support for channel filtering using yaml configuration files
for writers.

Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com>

* Update writers documentation with new env var

Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com>

* Add info comment to configuration file

Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com>

* Fix configuration and update yaml dep

Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com>

* Update config from yaml to toml

Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com>
2019-05-10 14:36:27 +02:00
Manuel Imperiale 886c8b2467 MF-732 - Add PostgreSQL writer (#733)
* MF-732 - Add PostgreSQL writer

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix reviews

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Add README

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Unify writers api

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix host and port

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix reviews

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2019-05-07 19:09:28 +02:00
Manuel Imperiale 138dae9277 NOISSUE - Fix Readers logs (#735)
* NOISSUE - Fix Readers logs

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix reviews

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2019-05-07 15:59:18 +02:00
Manuel Imperiale 9f1151b8dc NOISSUE - Add readers pagination in SDK (#736)
* NOISSUE - Add readers pagination in SDK

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix reviews

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2019-05-07 15:10:02 +02:00
Darko Draskovic 700cfdf6f3 Add UI websocket open/close and send/receive (#728)
* Add websocket open/close and send/receive

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add websocket feedback/log to Elm

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Align WS open/close buttons

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add websocket query for channel list

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add websocket related encoders/decoders for port interops

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Fix .js indentation and simplify ws (dis)connect method

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Use single quotes consistently

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add HTTP Send and WS Listen

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>
2019-04-26 19:17:04 +02:00
Aleksandar Novaković dc9333237f MF-707 - Allow custom Thing key (#726)
* Add support for setting up thing key manually

Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com>

* Fix existing tests and add new ones

Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com>

* Update SQL schema for things entity

Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com>

* Add update thing key endpoint to swagger docs

Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com>

* Fix response code when handling conflicting key

Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com>
2019-04-25 14:37:51 +02:00
Aleksandar Novaković aba7d909ed MF-525 - Add pagination response to the readers (#729)
* Add pagination response to the readers

Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com>

* Remove println from influx reader

Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com>
2019-04-25 00:18:43 +02:00
Manuel Imperiale 04281f2388 NOISSUE - Rm Things type from lora-adapter (#727)
Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2019-04-22 21:31:08 +02:00
Milan Andric 08f2545df9 fix to makefile for OSX/Darwin (#724)
Signed-off-by: Milan Andric <mandric@gmail.com>
2019-04-22 21:03:05 +02:00
Darko Draskovic 64f154dcc9 Fix .dockerignore file by removing index.html (#725)
* Fix .dockerignore file by removing index.html

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add autogen main.js to .dockerignore

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>
2019-04-21 20:10:24 +02:00
Darko Draskovic 9688fc316a Fix things and channels metadata create and edit & remove thing type (#721)
* Add basic websocket support

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add separate cards for base and regular attribs

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add arbitrary json metadata enc/dec

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add JsonMF module for JSON val handling

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Rename json related func for naming consistency

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Remove Thing type from user interface

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Add arbitrary metadata json to channels

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Change json string identation

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>
2019-04-21 02:40:14 +02:00
Jovan Kostovski 5b66b6e169 skip deleting of persistent volumes by default (#723)
This commit, intruduces changes to the current behavior of the "make cleandocker" command.
From now on, an extra parameter must be given in order to delete the persistent volumes,  which up until now was done by default. The new command which represents the old behavior (deleting all artifacts created from the docker composition) is "make pv=true cleandocker".

Signed-off-by: Jovan Kostovski <chombium@gmail.com>
2019-04-20 17:56:51 +02:00
Manuel Imperiale b97deb50b2 MF-488 - Remove Thing type (app or device) (#718)
* MF-488 - Remove Thing type (app or device)

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Typo fix

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix reviews

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix reviews

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>

* Fix reviews

Signed-off-by: Manuel Imperiale <manuel.imperiale@gmail.com>
2019-04-20 14:09:11 +02:00
Dušan Borovčanin 44615c5ff0 Remove empty channels check (#720)
Remove empty list of channels check from the default Bootstrap
reader implementation.

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>
2019-04-18 18:02:20 +02:00
Jovan Kostovski a30666d999 MF-655 Proper usage of docker volumes (#657)
* added filter and a switch for volume deletion

Signed-off-by: Jovan Kostovski <chombium@gmail.com>

* added persistent volume configuration

Signed-off-by: Jovan Kostovski <chombium@gmail.com>
2019-04-18 17:31:24 +02:00
Drasko DRASKOVIC baebe8abc1 NOISSUE - Improve UI styling (#719)
* Add styling

Signed-off-by: drasko <drasko.draskovic@gmail.com>

* Fix errors

Signed-off-by: drasko <drasko.draskovic@gmail.com>

* Fix env

Signed-off-by: drasko <drasko.draskovic@gmail.com>
2019-04-18 13:29:38 +02:00
Dušan Borovčanin d9d9da49a8 MF-715 - Conflict on updating connection with a valid list of channels (#716)
* Add check if Channels already exist

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Update streams tests to use time offset

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>
2019-04-17 19:41:37 +02:00
Aleksandar Novaković 2f17a05b3e MF-711 - Create separate Redis instance for ES (#717)
* Fix Redis connection error handling in MQTT adapter

Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com>

* Add separate env vars for ES in MQTT adapter

Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com>

* Create dedicated redis service for ES

Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com>

* Update docs according to changes

Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com>
2019-04-17 12:39:47 +02:00
Dušan Borovčanin fe9f4dca8e NOISSUE - Update event fields naming (#713)
* Update event fields naming

Replace `id` with `thing_id`.
Fix typo in `success` field.

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Update docs

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>
2019-04-16 18:32:05 +02:00
Aleksandar Novaković de736a5c47 MF-698 - Add missing info and docs about sys event sourcing (#712)
* Add event sourcing section in dev guide

Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com>

* Fix spelling in the docs

Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com>

* Fix notice formatting in docs

Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com>

* Fix typos in docs

Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com>

* Add highlight for event type

Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com>
2019-04-16 17:28:43 +02:00
Aleksandar Novaković 5799356b14 MF-549 - Change metadata format from JSON string to JSON object (#706)
* Update metadata type in things service

Update things service so that metadata has map type. Update repo
implementation by adding sqlx lib.

Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com>

* Add sqlx lib to bootstrap service

Add sqlx lib to bootstrap service and update metadata field type.

Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com>

* Update metadata in redis streams consumer

Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com>

* Update tests for bootstrap service

Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com>

* Fix mongo reader logging and driver version

Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com>

* Fix mongo reader and writer

Fix mongo reader and writer by updating driver version.

Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com>

* Update SDK with new metadata format

Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com>

* Update LoRa adapter with new metadata format

Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com>

* Update users service in order to use sqlx

Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com>

* Replace anonymous struct with map

Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com>

* Update docs for LoRa adapter

Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com>

* Fix LoRa application metadata format

Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com>

* Fix metadata format in LoRa docs

Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com>

* Add metadata2 var to SDK things test

Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com>
2019-04-16 14:58:56 +02:00
Dušan Borovčanin 0bbc4f54ab Fix Bootstrap service event map keys (#705)
Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>
2019-04-11 13:55:21 +02:00
Darko Draskovic 0b89975947 NOISSUE - Replace repeating code by card gen func (#697)
* Replace repeating code by card gen func

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Remove absolute URL from Env.elm

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Fix md links in README.md

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>

* Refactor appendIF helper func

Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>
2019-04-09 16:21:26 +02:00
Dušan Borovčanin df11a20a32 Update Bootstrap service docker-compose.yml (#700)
Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>
2019-04-09 02:44:50 -07:00
Ivan Milošević 77ce0f9fab Remove Debug function (#699)
Signed-off-by: Ivan Milošević <iva@blokovi.com>
2019-04-09 01:50:23 -07:00
Dušan Borovčanin 6ebc758f89 MF-687 - Add event sourcing to Bootstrap service (#695)
* Add initial Bootstrap event producer

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Add operation name to events

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Add ES path for BS events

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Add BS event sourcing tests

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Add connections update events

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Extract event XADD to separeate method

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Add header comments

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Fix service docs typo

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Update BS service README.md

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Update Service method tests

Tests for handler methods are not added.

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>
2019-04-08 08:46:07 -07:00
Ivan Milošević 98d588e8ef NOISSUE - Remove debugging message from response of handle error function (#696)
* Remove debugging message from response of handle error function

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Print error in debug console in case of bad body HTTP response

Signed-off-by: Ivan Milošević <iva@blokovi.com>
2019-04-08 13:44:11 +02:00
Aleksandar Novaković 12a85f565c Fix logging in publish event callback (#694)
Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com>
2019-04-05 17:29:43 +02:00
Aleksandar Novaković 5fcf0aa229 Add event stream to MQTT adapter for conn status (#692)
Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com>
2019-04-05 11:25:39 +02:00
Drasko DRASKOVIC 9b41c2db3a NOISSUE - Improve UI style (#691)
* Improve UI style

Signed-off-by: drasko <drasko.draskovic@gmail.com>

* Remove unused font

Signed-off-by: drasko <drasko.draskovic@gmail.com>
2019-04-04 20:28:56 +02:00
Dušan Borovčanin e4d3b1d8c3 Fix InfluxDB time bug (#689)
Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>
2019-04-04 18:10:01 +02:00
Dušan Borovčanin 4f62f89b87 Update docs structure (#686)
Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>
2019-04-04 12:10:46 +02:00
Dušan Borovčanin 06f4c6e059 Use images instead of carousel (#685)
Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>
2019-04-04 11:19:43 +02:00
Dušan Borovčanin 25d9783514 NOISSUE - Update docs (#683)
* Add authorization section to the docs

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Slow down Bootstrap gif

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Use heading 4 instead of italic

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Replace gif with carousel

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>
2019-04-04 10:22:09 +02:00
Ivan Milošević 56a50962b6 MF-662 - Change menu style (#678)
* env variable for local dev

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Change menu style

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Revert "env variable for local dev"

This reverts commit 205e13be6cdde930f02e7bda2019bb615267ed14.

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Move font awesome class names to record in Helper

Signed-off-by: Ivan Milošević <iva@blokovi.com>
2019-04-02 19:17:28 +02:00
Dušan Borovčanin f9b17d5f24 MF-651 - X509 Mutual TLS authentication (#676)
* Use NginX njs module for mutual authentication

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Add Makefile for cert management

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Move certificates make context to scripts dir

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Move nginx.conf to separate directory

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Choose between two NginX configurations

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Move certs Makefile to docker/ssl/

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Use default key-based authentication

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Add mTLS docs

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Update Makefile

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Add check if Authorization is present

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Add check if Will Flag is 1

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Return MQTT over WS

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Fix docker-compose.yml volume mapping

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Rename security section in docs

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Add message type check before message parsing

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Remove double comments

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Remove s.AGAIN in return

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Update Makefile

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Remove CSR and key from the root

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Drop TLS version below 1.2

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>

* Add comments for cert and key paths

Signed-off-by: Dušan Borovčanin <dusan.borovcanin@mainflux.com>
2019-04-02 17:54:24 +02:00
Ivan Milošević bc49f6a543 Fix offset calculation after deleting thing/channel, not to go to negative offset after deleting last thing/channel (#679)
Signed-off-by: Ivan Milošević <iva@blokovi.com>
2019-04-01 17:49:10 +02:00
Aleksandar Novaković 83cafeb6be Update Aedes version for MQTT adapter (#677)
Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com>
2019-03-29 14:04:02 +01:00
Ivan Milošević c634bf8c7d MF-661 - Bootstrap pagination in UI (#672)
* Add bootstrap pagination

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Fix enviroment variable in readme

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Change style
Remove pagination from channels and things lists if total less then limit

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Hide pagination when not needed on connection and message page
Code refacotoring

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Remove debug module and local dev variable.

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Remove debug logging

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* Add trailing slash note in README

Signed-off-by: Ivan Milošević <iva@blokovi.com>

* proto

Signed-off-by: Ivan Milošević <iva@blokovi.com>
2019-03-28 10:20:15 +01:00
Aleksandar Novaković 86a3115804 Update subtopics section in documentation (#670)
Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com>
2019-03-22 16:47:30 +01:00
Darko Draskovic 4a31f11615 Remove default base URL value (#671)
Signed-off-by: Darko Draskovic <darko.draskovic@gmail.com>
2019-03-22 14:53:03 +01:00
4374 changed files with 606452 additions and 344908 deletions
-4
View File
@@ -1,10 +1,6 @@
.git
.github
build
ui
docker
docs
k8s
load-test
metrics
scripts
+10
View File
@@ -28,3 +28,13 @@ the following locations:
5. In what environment did you encounter the issue?
6. Additional information you deem important:
**ENHANCEMENT**
1. Describe the enhancement you are requesting. Enhancements include:
- tests
- code refactor
- documentation
- research
- tooling
2. Indicate the importance of this enhancement to you (must-have, should-have, nice-to-have).
+6 -1
View File
@@ -1,3 +1,8 @@
# Copyright (c) Mainflux
# SPDX-License-Identifier: Apache-2.0
# Set your private global .gitignore:
# https://digitalfortress.tech/tricks/creating-a-global-gitignore/
build
site/
+36
View File
@@ -0,0 +1,36 @@
# Adopters
As Mainflux Community grows, we'd like to keep track of Mainflux adopters to grow the community, contact other users, share experiences and best practices.
To accomplish this, we created a public ledger. The list of organizations and users who consider themselves as Mainflux adopters and that **publicly/officially** shared information and/or details of their adoption journey(optional).
Where users themselves directly maintain the list.
## Adding yourself as an adopter
If you are using Mainflux, please consider adding yourself as an adopter with a brief description of your use case by opening a pull request to this file and adding a section describing your adoption of Mainflux technology.
**Please send PRs to add or remove organizations/users**
### Format
```
N: Name of user (company or individual)
D: Short Use Case Description (optional)
L: Link with further information (optional)
T: Type of adaptation: Evaluation, Core Technology, Production Usage (optional)
```
## Requirements
* You must represent the user or organization listed. Do NOT add entries on behalf of other organizations or individuals.
Pull request commit must be [signed](https://docs.github.com/en/github/authenticating-to-github/signing-commits) and auto-checked with [ Developer Certificate of Origin (DCO)](https://probot.github.io/apps/dco/)
* There is no minimum requirement or adaptation size, but we request to list permanent deployments only, i.e., no demo or trial deployments. Commercial or production use is not required. A well-done home lab setup can be equally impressive as a large-scale commercial deployment.
**The list of organizations/users that have publicly shared the usage of Mainflux:**
**Note**: Several other organizations/users couldn't publicly share their usage details but are active project contributors and Mainflux Community members.
## Adopters list (alphabetical)
**Note:** The list is maintained by the users themselves. If you find yourself on this list, and you think it's inappropriate. Please contact [project maintainers](https://github.com/mainflux/mainflux/blob/master/MAINTAINERS) and you will be permanently removed from the list.
+409
View File
@@ -11,6 +11,415 @@ Otherwise, whole log in a similar format can be observed via:
git log --pretty=oneline --abbrev-commit
```
## 0.12.0 - 29. MAR 2021.
### Features and Bugfixes
- MF-1394 - SDK groups (#1396)
- NOISSUE - fix response for passwd endpoints (#1393)
- NOISSUE - dont retrieve groups (#1392)
- MF-1368 - Add internal http api package for query params reading (#1384)
- MF-1390 - Fix docker-compose env_file (#1391)
- NOISSUE - put order direction in response body (#1387)
- NOISSUE - Certs service refactor (#1369)
- MF-1357 - Add new endpoint for searching things (#1383)
- NOISSUE - Add missing auth port in nginx enrypoint.sh (#1380)
- MF-1346 - Create Groups API - add grouping of entities (#1334)
- NOISSUE - Fix certs and vault deployment, reorganize and remove unnecessary vars (#1368)
- MF-1317 - Configurable regexp rule for password (#1355)
- Fix CoAP Adapter README (#1376)
- NOISSUE - Fix default values for port and x509 provision (#1367)
- NOISSUE - Added missing endpoints for users service (#1372)
- MF-1365 - Add ADOPTERS.md file (#1371)
- Fix grpc endpoint parameter permutation (#1370)
- NOISSUE - Add IsChannelOwner grpc endpoint (#1366)
- MF-1362 - Sort Things and Channels connections by name (#1363)
- MF-1314 - Add value comparison filters for readers (#1353)
- Fix env configuration and documentation (#1360)
- NOISSUE - Support disabling Email Agent authentication (#1356)
- NOISSUE - Upgrade Mongo, Cassandra and Influx docker images (#1354)
- NOISSUE - Add READMEs to pkg packages (#1352)
- NOISSUE - Correct README (#1349)
- MF-1342 - Use environment variables in docker-compose to use tagged version of image (#1343)
- MF-1311 - Add Notifications service (#1324)
- MF-1344 - Fix links to API documentations #1345
- NOISSUE - Upgrade influxdb and postgres docker images (#1341)
- NOISSUE - Revert cli to use user token from command args (#1339)
- MF-1276 - Fix openapi IDs and Keys format (#1338)
- MF-1061 - Add PageMetadata to readers (#1333)
- NOISSUE - Fix run script and compiler warnings (#1336)
- Fix Postgres writer transaction handling (#1335)
- Make Transformer type configurable (#1331)
- MF-1061 - Implement v, vb, vs, vd and from/to mongodb-reader filters (#1326)
- NOISSUE - Rename package aliases uuidProvider into uuid (#1323)
- MF-1034 - Wrapping MQTT client (#1318)
- MF-1061 - Fix cassandra-reader count for json format (#1327)
- MF-1061 - Implement v, vb, vs, vd and from/to cassandra-reader filters (#1325)
- NOISSUE - Switch to Consumers interface (#1316)
- MF-1061 - Implement protocol, name, v, vb, vs, vd and from/to Postgres reader… (#1322)
- MF-1061 - Add name, protocol and publisher tests to influxdb-reader (#1320)
- NOISSUE - Fix Auth typo (#1319)
- NOISSUE - Add health check for MQTT broker (#1305)
- MF-1264 - Add support for JSON readers (#1295)
- NOISSUE - Merge authz and authn into new service auth (#1313)
- MF-1061 - Implement InfluxDB filters value, v, vb, vs, vd, from, to (#1312)
- NOISSUE - Correct readers openapi.yml (#1310)
- NOISSUE - Fix MQTT Forwarder client id (#1309)
- NOISSUE - Fix dates not being init properly on save, change path construction, replace UUID with ULID for group ID (#1300)
- NOISSUE - Remove authz from docker comp (#1307)
- Shorten descriptions and add formats (#1306)
- NOISSUE - remove owner id from user table and object (#1303)
- NOISSUE - Add missing fields to openapi specs and enclose http codes in single quotes (#1302)
- MF-1290 - Sort Things and Channels by name (#1293)
- MF-1248 - Add access policies for users (#1246)
- Fixes, without spaces. (#1296)
- Add different CNs for CA and certs (#1292)
- MF-397 - Introduce Thing Groups (#1259)
- Add Enhancement section to the issue template (#1284)
- Fix hardcoded env var values (#1283)
- NOISSUE - Improve AuthN service docs (#1282)
- MF-1268 - CLI improvements (#1274)
- NOISSSUE - Vault integration as an addon. (#1266)
- Fix naming in Authn API tests (#1275)
- MF-1244 - Return UserID alongside with user Email in Identify response (#1245)
- NOISSUE - Fix ViewGroup and UpdateGroup (#1269)
- NOISSUE - Add ListUsers, ViewUser and ViewProfile methods (#1262)
- NOISSUE - Rm users http package (#1256)
- NOISSUE - Remove content-type check from decodeListUserGroupsRequest (#1255)
- NOISSUE - Migrate swaggers to openapi 3 spec (#1250)
- Update MQTT Broker Docker scripts (#1253)
- update mproxy version (#1251)
- NOISSUE - Fix group retrieval when parent id is not specified (#1247)
- NOISSUE - Add new endpoint to retrieve configuration to be used as a template. (#1242)
- NOISSUE - Add user groups (#1228)
- MF-1237 - Return to transport only things service errors (#1236)
- MF-928 - Change CoAP lib (#1233)
- NOISSUE - Simplify make cleandocker (#1230)
- NOISSUE - Fix malformed Swagger API specs (#1229)
- MF-435 - Add support for env file loading (#1223)
- update certs docs (#1227)
- NOISSUE - Fix certs update in bootstrap config and make content handling in config.toml user friendly (#1221)
- NOISSUE - Fix typo in authorization.js (#1226)
- MF-983 - Add HTTP query param to connections list endpoints to fetch disconnected Things or Channels (#1217)
- MF-1179 - Add a certificate service and certs endpoint to SDK (#1188)
- NOISUE - Fix cache error when key is not in Redis (#1220)
- MF-1199 - Add NATS messaging tests (#1209)
- NOISSUE: Fix emailer (#1219)
- NOISSUE - Update dependencies (#1218)
- NOISSUE - Add subtopic wildcard for twin attribute's definition (#1214)
- fix envs for nginx (#1215)
- Remove twin mqtt related obsolete var and fix es-redis address (#1213)
- NOISSUE - Remove unused `MF_THINGS_SECRET` env var (#1211)
- NOISSUE - Fix some typos (#1212)
- NOISSUE - Remove unknown Bootstrap requests (#1210)
- NOISSUE - Use `pgcrypto` instead `uuid-ossp` for UUIDs generation (version 4) (#1208)
- MF-1198 - Add errors package tests (#1207)
- MF-1025 - timeout env in sec, use parseduration (#1206)
- MF-1201 - Fix MF_THINGS_AUTH_GRPC_URL mongo reader ENVAR (#1203)
- NOISSUE - Fix CI (#1204)
- MF-1180 - Add redis based twins and states cache (#1184)
- MF-739 - Add ID to the User entity (#1152)
- NOISSUE - Fix default db name for storage databases (#1194)
- NOISSUE - Add `MF_DOCKER_IMAGE_NAME_PREFIX` to Makefile (#1173)
- MF-1154 - Move UUID provider to project root (#1172)
- Fix typo in error messages (#1193)
- MF-1190 - Add pkg for library packages (#1191)
- MF-1177 - Implement caching in MQTT adapter (#1187)
- NOISSUE - Refactor provision tool (#1189)
## 0.11.0 - 29. MAY 2020.
### Features and Bugfixes
- Add VerneMQ docker image build from source (#1178)
- MF-994 - Add tracing middleware for twins and states repos (#1181)
- MF-995 - Add Twins tests for endpoint list twins and list states (#1174)
- NOISSUE - Update dependencies (#1176)
- MF-1163 - Fix influxdb-reader to use nanoseconds precision (#1171)
- Rename environment variable MF_MQTT_ADAPTER_PORT to MF_MQTT_ADAPTER_MQTT_PORT in docker environment (#1170)
- Remove thing related code from twins service (#1169)
- MF-997 - Add twins service swagger file (#1167)
- MF-1079 - Add MQTT forwarder (#1164)
- MF-1159 - add gateway metadata update in provision method (#1160)
- MF-1055 - rollback/release transaction on error (#1166)
- NOISSUE - Use log level error for VermeMQ docker (#1162)
- NOISSUE - Fix default nats pubsub subject (#1153)
- MF-1125 - Document Provision service (#1143)
- NOISSUE - Fix bootstrap SDK args naming (#1151)
- Use VerneMQ default log level (#1150)
- NOISSUE - Update provision service (#1133)
- NOISSUE - Refactor messaging (#1141)
- Add JSON tags to SDK entities (#1146)
- NOISSUE - Update CLI README.md (#1139)
- NOISSUE - Update mProxy version (#1137)
- fix nginx, channel connect (#1136)
- Remove concurrency flag for golangci-lint (#1134)
- MF-1088 - Remove message payload content type (#1121)
- MF-1129 - Use snake_case for Lora and OPC-UA metadata fields (#1130)
- MF-1128 - Add golangci-linter to a CI script (#1131)
- MF-1123 - Move Provision service to monorepo (#1132)
- MF-845 - Add FOSSA badge for licensing (#1127)
- MF-1087 - Remove WebSocket adapter (#1120)
- NOISSUE - Use HTTP Status in SDK error messages (#1119)
- NOISSUE - Fix bootstrap token naming and interfaces named args (#1117)
- MF-1115 - Improve the SDK error encoding (#1118)
- MF-862 - Add boostrap CRUD to SDK and CLI (#1114)
- NOISSUE - Update coding style in Things service (#1116)
- NOISSUE - Remove defers from TestMain (#1111)
- NOISSUE - Create func to encode SDK errors (#1110)
- MF-1078 - Add timestamp to published messages and use it in Transformer (#1106)
- Fix prometheus namespace in postgres reader & writer (#1109)
- NOISSUE - Implement errors package in senml transformer, readers and writers (#1108)
- NOISSUE - Implement errors package in Authentication service (#1105)
- MF-1103 - API key should ignore empty expiration time (#1104)
- MF-1096 - Fix AuthN and Things Auth ENVARS (#1066)
- fix Contains function for nil arguments (#1102)
- MF-1099 - Add email subdomain validator (#1101)
- MF-1091 - Use channels. as broker prefix (#1098)
- MF-1090 - Use named Interfaces args (#1097)
- NOISSUE - Create broker package for NATS (#1080)
- NOISSUE - Implement errors package in bootstrap service (#1093)
- NOISSUE - Fix writers loadSubjectsConfig if file is missing (#1094)
- NOISSUE - Adding subtopics filtering in writer services (#1072)
- NOISSUE - Improve errors package (#1086)
- NOISSUE - Enable MQTT over WS in docker composition (#1085)
- NOISSUE - Rm unused opc-ua envars (#1083)
- MF-798 - Add utf8 support for email validation (#1082)
- Remove unused Tokenizer interface (#1084)
- Update mqtt adapter imports (#1081)
- NOISSUE - Update state based on SenML time value (#1075)
- NOISSUE - Fix StatusBadDecodingError for opc-ua browse (#1074)
- Save senml array msg to multiple states (#1073)
- NOISSUE - Fix opc-ua message type handling (#1071)
- NOISSUE - Add Publisher field to MQTT adapter (#1067)
- NOISSUE - Fix users CLI (#1062)
- NOISSUE - Fix SDK Messages response (#1064)
- Merged MQTT docker compose in core composition file (#1060)
- MF-1016 - Add UserUpdate and UpdatePassword to sdk and CLI (#1057)
- Update mProxy (#1058)
- MF-1053 - Add disconnect event to MQTT adapter (#1056)
- Fix data type for data_value in databases (#1054)
- NOISSUE - Fix opc-ua subscriptions store (#1052)
- NOISSUE - Fix connect CLI command and remove ConnectThing func from SDK (#1051)
- NOISSUE - Update Vernemq image repository (#1050)
- Removed VerneMQ auth plugin, Aedes impl. Added mproxy support in docker (#1049)
- NOISSUE - Add default subscription nodeID and Interval ENVAR (#1046)
- MF-415 - Merge mProxy support (#1045)
- NOISSUE - Remove twins-service mqtt dependency and publish notifs to nats (#1042)
- Add arbitrary SenML value type saving to twin state (#1039)
- Fixed Aedes dependencies (#1036)
- MF-998 - Add Twins service to Makefile and docker-compose.yml (#1035)
- MF-1032 - Fix redis docker volume of opcua-adapter (#1033)
- NOISSUE - add nats conf (#1031)
- MF-442 - Add SSL encryption to the MongoDB, InfluxDB and Cassanda readers (#1024)
- NOISSUE - Add opc-ua type handling and unsubscription (#1029)
- NOISSUE - Add aggregate attribute-based search for twin retrieval (#1027)
- NOISSUE - Fix metadata in add Things endpoint (#1028)
- NOISSUE - Fix minimal password length (#1023)
- MF-1020 - Change default password for CLI provision test (#1021)
- NOISSUE - Add subtopic to opcua messages (#1022)
- NOISSUE - Add details to browsed OPC-UA nodes (#1019)
- NOISSUE Fix obsolete attribute persistance (#1018)
- Fix twins update revision counter (#1011)
- Fixed docs instructions in README (#1010)
- Fix copyright year (#1009)
- Fix issuing recovery key (#1007)
- Removed gatling load-test (#1005)
- Removed old k8s manifests (#1004)
- NOISSUE - Remove UI from docker-compose (#1001)
- NOISSUE - Store successfull OPC-UA subscriptions (#999)
- MF-730 - Add digital twin service for things (#855)
- Fix Redis event naming (#996)
- NOISSUE - Add a Browse endpoint in opcua-adapter (#988)
- NOISSUE - Add Redis ES Username/Pass for VerneMQ (#991)
- MF-982 - Add error when connecting empty channels or things (#985)
## 0.10.0 - 17. DEC 2019.
### Features
- MF-932 - User API keys (#941)
- NOISSUE - Use opcua server timestamp in opcua-adapter messages (#980)
- Simplify CI script (#979)
- NOISSUE - Add opcua-adapter conn route-map, use ServerURI and NodeID (#975)
- Move docs to a separate repo (#976)
- NOISSUE - Support multiple types values in opcua-adapter (#973)
- Migrate from dep to go modules (#971)
- NOISSUE - Add Node IdentifierType config in opcua-adapter (#967)
- NOISSUE - Remove messages limit in influxdb-reader (#968)
- MF-898 - Add bulk connect to CLI and SDK (#956)
- MF-538 - Improve logging and API errors (#866)
- NOISSUE - Remove Elm UI (#953)
- MF-898 - Add bulk connections endpoint (#948)
- MF-898 - Change thing's service to use bulk connect (#946)
- MF-898 - Add transactions to postgres connect (#940)
- Add missing user service tests (#945)
- Remove Normalizer service from compose (#937)
- MF-919 - Mainflux message updates (#924)
- NOISSUE - Remove ARM multi-arch images (#929)
- MF-906 - Change single creation endpoints to use bulk service calls (#927)
- MF-922 - Add UpdateUser endpoint (#923)
- MF-780 - Use Normalizer as a lib (#915)
- NOISSUE - Switch to grpcbox for VerneMQ (#914)
- Change channels to chs (#918)
- MF-484 - Add bulk provisioning for things and channels (#889)
- MF-899 - Update README and official docs (#910)
- NOISSUE - Fix Redis envars (#903)
- Add disconnect on gen_server terminate() (#913)
- MF-890 - Add OPC-UA docs (#904)
- NOISSUE - Update Protobuf version (#902)
- MF-886 - Add OPC-UA adapter (#878)
- MF-532 - Password reset (#873)
- MF-785 - Change CanAccess to CanAccessByKey (#894)
- NOISSUE - Add MQTT UserName check on register and InstanceId in Redis (#884)
- Add MQTT troubleshooting section (#882)
- MF-875 - Add tracing to official documentation (#877)
- MF-788 - Remove date and minimize copyright comments (#876)
- MF-787 - Add tags to user, thing, and channel spans (#869)
- Update docker-compose version for addons (#874)
- MF-859 - Channels metadata search (#867)
- MF-858 Users metadata (#861)
- NOISSUE - Simplify MQTT benchmarking tool (#852)
- NOISSUE - Upgrade Go version to 1.13 in container images (#868)
- MF-820 - Fetch messages for a particular device (#843)
- Update gorilla websocket version (#865)
- NOISSUE - Update aedes version and fix Dockerfile (#863)
- NOISSUE - Search by metadata (#849)
- MF-846 - Install python in docker build for aedes mqtt image (#860)
- NOISSUE - Clean NginX files, move .gitignores to dirs (#853)
- NOISSUE - Add docker-compose for MQTT cluster (#841)
- Add debug logs to the WS adapter (#848)
- NOISSUE - Add measuring time from pub to sub (#839)
- NOISSUE - update mqtt prov tool and some refactor (#831)
- NOISSUE - Use Thing ID to update certs data (#827)
- NOISSUE - Improve VerneMQ plugin code, add configurable gRPC pool size (#836)
- NOISSUE - Use gRPC for VerneMQ (#835)
- Switch secure of WS connection according to secure of http connection of UI (#829)
- NOISSUE - Use current hostname instead of localhost for a WebSocket connection in the UI (#826)
- NOISSUE - Improve MQTT benchmarking tools (#828)
- NOISSUE - update mqtt benchmark (#824)
- Add encryption key to env vars table (#823)
- NOISSUE - Add version endpoint to MQTT adapter (#816)
- MF-295 add mqtt benchmark tool (#817)
- update mqtts commands (#815)
- NOISSUE - Support encrypted bootstrap (#796)
- Add config to writers docs (#812)
- NOISSUE - Add VerneMQ support (#809)
- NOISSUE - Add content type as part of MQTT subscription topic (#810)
### Bugfixes
- Fix MQTT protobuf filename(#981)
- MF-950 - Runtime error in normalizer - CBOR SenML (#974)
- NOISSUE - Fix opcua-adapter events warnings (#965)
- NOISSUE - Fix opcua-adapter events decode (#951)
- Fix subtopic handling in VerneMQ (#962)
- NOISSUE - Fix Update User (#959)
- NOISSUE - Fix make dockers (#957)
- Add dev_ back to make dockers_dev (#955)
- NOISSUE - Fix docs (#952)
- MF-916 - Fix Things and Channels counters (#947)
- MF-942 - Fix email template logic (#944)
- NOISSUE - Fix HTTP header for Things and Channels creation (#939)
- NOISSUE - Fix docker ui image name (#938)
- NOISSUE - Fix lora-adapter (#936)
- NOISSUE - Fix lora creation events (#933)
- Fix doc for ENV vars in README (#920)
- Fix compilation (#911)
- Revert "NOISSUE - Make event sourcing optional (#907)" (#909)
- NOISSUE - Make event sourcing optional (#907)
- NOISSUE - Fix InfluxDB env vars (#908)
- Fix Elm version for ARM Docker images (#905)
- Fix Elm version in Dockerfile (#901)
- NOISSUE - fix security doc (#897)
- NOISSUE - Fix typo in docs and README (#891)
- Fix Nginx mTLS configuration (#885)
- Fix provision tool connect error handling (#879)
- Fix: Correct 404 and Content-Type Issues in MQTT Version Endpoint (#837)
- NOISSUE - Fix proto files in VerneMQ (#834)
- NOISSUE - Fix hackney HTTP request (#833)
- Add socket pool and fix pattern matching (#830)
- Fix typo (#814)
## 0.9.0 - 19. JUL 2019.
### Features
- Create and push docker manifest for new release from Makefile (#794)
- MF-399 - Add open tracing support (#782)
- MF-783 - Allow access checking by a thing ID (#784)
- NOISSUE - Add authorization HTTP API to things service (#772)
- Remove cli executable from repo (#776)
- NOISSUE - Use .env vars in docker-compose (#770)
- MF-663 - enable nginx port conf from docker env (#769)
- Update docs (#766)
- NOISSUE - Remove installing non-existent package in ci (#758)
- NOISSUE - Add searchable Channels name (#754)
- MF-466 - ARM docker deployment (#756)
- Add missing Websocket.js into docker ui image (#755)
- NOISSUE - Add searchable Things name (#750)
- NOISSUE - Add certificate fields to the Bootstrap service (#752)
- Update grpc and protobuf deps in mqtt adapter (#751)
- MF-742 - Things to support single user scenario (#749)
- MF-732 - Add Postgres reader (#740)
- MF-722 - Change UUID lib (#746)
- Add performance improvement to writer filtering (#744)
- NOISSUE - Update nginx version (#748)
- MF-574 - Add missing environment variables to Cassandra writer (#745)
- NOISSUE - Add compile test to CI (#743)
- MF-708 - Assign Writer(s) to a channel (#737)
- MF-732 - Add PostgreSQL writer (#733)
- NOISSUE - Add readers pagination in SDK (#736)
- Add UI websocket open/close and send/receive (#728)
- MF-707 - Allow custom Thing key (#726)
- MF-525 - Add pagination response to the readers (#729)
- NOISSUE - Rm Things type from lora-adapter (#727)
- skip deleting of persistent volumes by default (#723)
- MF-488 - Remove Thing type (app or device) (#718)
- Remove empty channels check (#720)
- MF-655 Proper usage of docker volumes (#657)
- NOISSUE - Improve UI styling (#719)
- MF-715 - Conflict on updating connection with a valid list of channels (#716)
- MF-711 - Create separate Redis instance for ES (#717)
- NOISSUE - Update event fields naming (#713)
- MF-698 - Add missing info and docs about sys event sourcing (#712)
- MF-549 - Change metadata format from JSON string to JSON object (#706)
- NOISSUE - Replace repeating code by card gen func (#697)
- Update Bootstrap service docker-compose.yml (#700)
- Remove Debug function (#699)
- MF-687 - Add event sourcing to Bootstrap service (#695)
- NOISSUE - Remove debugging message from response of handle error function (#696)
- Add event stream to MQTT adapter for conn status (#692)
- NOISSUE - Improve UI style (#691)
- Update docs structure (#686)
- Use images instead of carousel (#685)
- NOISSUE - Update docs (#683)
- MF-662 - Change menu style (#678)
- MF-651 - X509 Mutual TLS authentication (#676)
- Update Aedes version for MQTT adapter (#677)
- MF-661 - Bootstrap pagination in UI (#672)
- Update subtopics section in documentation (#670)
- Remove default base URL value (#671)
### Bugfixes
- NOISSUE - Fix Readers logs (#735)
- NOISSUE - Fix Docker for ARM (#760)
- NOISSUE - Fix count when search by name is performed (#767)
- NOISSUE - Typo fix (#777)
- NOISSUE - Fix Postgres logs in Things service (#734)
- Fix CI with fixed plugin versions (#747)
- fix building problems (#741)
- fix docker-compose env (#775)
- Fix MF_THINGS_AUTH_GRPC_PORT in addons' docker-compose files (#781)
- Fix MQTT raw message deserialization (#753)
- fix variant option for manifest annotate (#765)
- fix to makefile for OSX/Darwin (#724)
- Fix .dockerignore file by removing index.html (#725)
- Fix things and channels metadata create and edit & remove thing type (#721)
- Fix Bootstrap service event map keys (#705)
- Fix logging in publish event callback (#694)
- Fix InfluxDB time bug (#689)
- Fix users service to work in offline mode (#795)
- fix mainflux_id parameter in bootstrap swagger (#789)
- Fix offset calculation after deleting thing/channel, not to go to negative offset after deleting last thing/channel (#679)
- Use errors and null packets in authorized pub/sub (#773)
- NOISSUE - Fix CoAP adapter (#779)
### Summary
https://github.com/mainflux/mainflux/milestone/10?closed=1
## 0.8.0 - 20. MAR 2019.
### Features
- MF-571 - Add Env.elm to set custom base URL (#654)
Generated
-791
View File
@@ -1,791 +0,0 @@
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
[[projects]]
branch = "master"
digest = "1:6da51e5ec493ad2b44cb04129e2d0a068c8fb9bd6cb5739d199573558696bb94"
name = "github.com/Azure/go-ansiterm"
packages = [
".",
"winterm",
]
pruneopts = "UT"
revision = "d6e3b3328b783f23731bc4d058875b0371ff8109"
[[projects]]
digest = "1:bf42be3cb1519bf8018dfd99720b1005ee028d947124cab3ccf965da59381df6"
name = "github.com/Microsoft/go-winio"
packages = ["."]
pruneopts = "UT"
revision = "7da180ee92d8bd8bb8c37fc560e673e6557c392f"
version = "v0.4.7"
[[projects]]
branch = "master"
digest = "1:3721a10686511b80c052323423f0de17a8c06d417dbdd3b392b1578432a33aae"
name = "github.com/Nvveen/Gotty"
packages = ["."]
pruneopts = "UT"
revision = "cd527374f1e5bff4938207604a14f2e38a9cf512"
[[projects]]
digest = "1:320e7ead93de9fd2b0e59b50fd92a4d50c1f8ab455d96bc2eb083267453a9709"
name = "github.com/asaskevich/govalidator"
packages = ["."]
pruneopts = "UT"
revision = "ccb8e960c48f04d6935e72476ae4a51028f9e22f"
version = "v9"
[[projects]]
branch = "master"
digest = "1:d6afaeed1502aa28e80a4ed0981d570ad91b2579193404256ce672ed0a609e0d"
name = "github.com/beorn7/perks"
packages = ["quantile"]
pruneopts = "UT"
revision = "3a771d992973f24aa725d07868b467d1ddfceafb"
[[projects]]
digest = "1:2209584c0f7c9b68c23374e659357ab546e1b70eec2761f03280f69a8fd23d77"
name = "github.com/cenkalti/backoff"
packages = ["."]
pruneopts = "UT"
revision = "2ea60e5f094469f9e65adb9cd103795b73ae743e"
version = "v2.0.0"
[[projects]]
branch = "master"
digest = "1:bb300c7f4b52deb1fcc65f6be624ae3dc289453824c2f1f184963dc41d941a68"
name = "github.com/cisco/senml"
packages = ["."]
pruneopts = "UT"
revision = "910a55054e168c1122b905e3f8acdc5ff97cbec1"
[[projects]]
branch = "master"
digest = "1:fc8dbcc2a5de7c093e167828ebbdf551641761d2ad75431d3a167d467a264115"
name = "github.com/containerd/continuity"
packages = ["pathdriver"]
pruneopts = "UT"
revision = "c6cef34830231743494fe2969284df7b82cc0ad0"
[[projects]]
digest = "1:a2c1d0e43bd3baaa071d1b9ed72c27d78169b2b269f71c105ac4ba34b1be4a39"
name = "github.com/davecgh/go-spew"
packages = ["spew"]
pruneopts = "UT"
revision = "346938d642f2ec3594ed81d874461961cd0faa76"
version = "v1.1.0"
[[projects]]
branch = "master"
digest = "1:ca71cdedcb487a361fe2588afeb3d373c0dfe21feaac229270fd6de234bd3c5e"
name = "github.com/dereulenspiegel/coap-mux"
packages = ["."]
pruneopts = "UT"
revision = "e137e310a99955be01d60b2237da89153ea40987"
[[projects]]
digest = "1:76dc72490af7174349349838f2fe118996381b31ea83243812a97e5a0fd5ed55"
name = "github.com/dgrijalva/jwt-go"
packages = ["."]
pruneopts = "UT"
revision = "06ea1031745cb8b3dab3f6a236daf2b0aa468b7e"
version = "v3.2.0"
[[projects]]
digest = "1:a5036bc703c3994f70ced9b34e9941381aa6bd03e69a0ce538f43bf14dc8dc98"
name = "github.com/docker/docker"
packages = [
"pkg/namesgenerator",
"pkg/random",
]
pruneopts = "UT"
revision = "092cba3727bb9b4a2f0e922cd6c0f93ea270e363"
version = "v1.13.1"
[[projects]]
digest = "1:87dcb59127512b84097086504c16595cf8fef35b9e0bfca565dfc06e198158d7"
name = "github.com/docker/go-connections"
packages = ["nat"]
pruneopts = "UT"
revision = "3ede32e2033de7505e6500d6c868c2b9ed9f169d"
version = "v0.3.0"
[[projects]]
digest = "1:6f82cacd0af5921e99bf3f46748705239b36489464f4529a1589bc895764fb18"
name = "github.com/docker/go-units"
packages = ["."]
pruneopts = "UT"
revision = "47565b4f722fb6ceae66b95f853feed578a4a51c"
version = "v0.3.3"
[[projects]]
branch = "master"
digest = "1:f77ee3cd73cbf60434b4f72ec46a66773c555d46764399735b5f9a28505f3ece"
name = "github.com/dustin/go-coap"
packages = ["."]
pruneopts = "UT"
revision = "ddcc80675fa42611359d91a6dfa5aa57fb90e72b"
[[projects]]
digest = "1:184008c955d6a3226b700c13ed0e875a7a051a759618f9bccba9b5c99f17faa5"
name = "github.com/eclipse/paho.mqtt.golang"
packages = [
".",
"packets",
]
pruneopts = "UT"
revision = "36d01c2b4cbeb3d2a12063e4880ce30800af9560"
version = "v1.1.1"
[[projects]]
digest = "1:865079840386857c809b72ce300be7580cb50d3d3129ce11bf9aa6ca2bc1934a"
name = "github.com/fatih/color"
packages = ["."]
pruneopts = "UT"
revision = "5b77d2a35fb0ede96d138fc9a99f5c9b6aef11b4"
version = "v1.7.0"
[[projects]]
digest = "1:90aaa664c659918dd0055e29eb5fd6b96a17a779d423db3dd3f5764c5dbbbd4f"
name = "github.com/go-kit/kit"
packages = [
"endpoint",
"log",
"metrics",
"metrics/internal/lv",
"metrics/prometheus",
"transport/grpc",
"transport/http",
]
pruneopts = "UT"
revision = "ca4112baa34cb55091301bdc13b1420a122b1b9e"
version = "v0.7.0"
[[projects]]
digest = "1:31a18dae27a29aa074515e43a443abfd2ba6deb6d69309d8d7ce789c45f34659"
name = "github.com/go-logfmt/logfmt"
packages = ["."]
pruneopts = "UT"
revision = "390ab7935ee28ec6b286364bba9b4dd6410cb3d5"
version = "v0.3.0"
[[projects]]
digest = "1:424f6593024cdf0f6f90cba81bc69ca98df3758525e6fb248198ef15ead603a9"
name = "github.com/go-redis/redis"
packages = [
".",
"internal",
"internal/consistenthash",
"internal/hashtag",
"internal/pool",
"internal/proto",
"internal/singleflight",
"internal/util",
]
pruneopts = "UT"
revision = "7f89fbac80bcc62ce920b6dbc6ca60238d7725d1"
version = "v6.15.0"
[[projects]]
digest = "1:c4a2528ccbcabf90f9f3c464a5fc9e302d592861bbfd0b7135a7de8a943d0406"
name = "github.com/go-stack/stack"
packages = ["."]
pruneopts = "UT"
revision = "259ab82a6cad3992b4e21ff5cac294ccb06474bc"
version = "v1.7.0"
[[projects]]
digest = "1:8dfa0b1b7c4b7398d46340aef0f2a58aad5a75959eeeb79e023ab826f0c7f7f0"
name = "github.com/go-zoo/bone"
packages = ["."]
pruneopts = "UT"
revision = "fd0aebc74e908868b09ac140fb5a53cb363884c1"
version = "1.2"
[[projects]]
branch = "master"
digest = "1:348bbd07b4a983b2f811f81948c78142de09bb9fcf25284bee5e2ab3bdd0f6f9"
name = "github.com/gocql/gocql"
packages = [
".",
"internal/lru",
"internal/murmur",
"internal/streams",
]
pruneopts = "UT"
revision = "68ae1e384be4d7cd7df0f5b77f331759d806308e"
[[projects]]
digest = "1:bbadccf3d3317ea03c0dac0b45b673b4b397c8f91a1d2eff550a3c51c4ad770e"
name = "github.com/gogo/protobuf"
packages = ["proto"]
pruneopts = "UT"
revision = "636bf0302bc95575d69441b25a2603156ffdddf1"
version = "v1.1.1"
[[projects]]
digest = "1:4c0989ca0bcd10799064318923b9bc2db6b4d6338dd75f3f2d86c3511aaaf5cf"
name = "github.com/golang/protobuf"
packages = [
"proto",
"ptypes",
"ptypes/any",
"ptypes/duration",
"ptypes/timestamp",
]
pruneopts = "UT"
revision = "aa810b61a9c79d51363740d207bb46cf8e620ed5"
version = "v1.2.0"
[[projects]]
branch = "master"
digest = "1:4a0c6bb4805508a6287675fac876be2ac1182539ca8a32468d8128882e9d5009"
name = "github.com/golang/snappy"
packages = ["."]
pruneopts = "UT"
revision = "2e65f85255dbc3072edf28d6b5b8efc472979f5a"
[[projects]]
digest = "1:7b5c6e2eeaa9ae5907c391a91c132abfd5c9e8a784a341b5625e750c67e6825d"
name = "github.com/gorilla/websocket"
packages = ["."]
pruneopts = "UT"
revision = "66b9c49e59c6c48f0ffce28c2d8b8a5678502c6d"
version = "v1.4.0"
[[projects]]
digest = "1:364b908b9b27b97ab838f2f6f1b1f46281fa29b978a037d72a9b1d4f6d940190"
name = "github.com/hailocab/go-hostpool"
packages = ["."]
pruneopts = "UT"
revision = "e80d13ce29ede4452c43dea11e79b9bc8a15b478"
[[projects]]
branch = "master"
digest = "1:6727359c2ab1efe714cc87acb36be6cf15a3aae078d8ae736a17fec787dab307"
name = "github.com/hokaccha/go-prettyjson"
packages = ["."]
pruneopts = "UT"
revision = "f579f869bbfea48a61fbea09207cadd1aaeceb83"
[[projects]]
digest = "1:870d441fe217b8e689d7949fef6e43efbc787e50f200cb1e70dbca9204a1d6be"
name = "github.com/inconshreveable/mousetrap"
packages = ["."]
pruneopts = "UT"
revision = "76626ae9c91c4f2a10f34cad8ce83ea42c93bb75"
version = "v1.0"
[[projects]]
digest = "1:65b0a07f85f7b5cc019c26775efc278a155a5dea0a8aa617c980e8308d16bc55"
name = "github.com/influxdata/influxdb"
packages = [
"client/v2",
"models",
"pkg/escape",
]
pruneopts = "UT"
revision = "a2ba6e7654fb078f8a3f5add1f8d935df38161bd"
version = "v1.6.4"
[[projects]]
branch = "master"
digest = "1:a64e323dc06b73892e5bb5d040ced475c4645d456038333883f58934abbf6f72"
name = "github.com/kr/logfmt"
packages = ["."]
pruneopts = "UT"
revision = "b84e30acd515aadc4b783ad4ff83aff3299bdfe0"
[[projects]]
branch = "master"
digest = "1:1e537dfb7b4d543e25efa2c812084dd65736f8010979c1b7e167a5b876563199"
name = "github.com/lib/pq"
packages = [
".",
"oid",
]
pruneopts = "UT"
revision = "d34b9ff171c21ad295489235aec8b6626023cd04"
[[projects]]
digest = "1:c658e84ad3916da105a761660dcaeb01e63416c8ec7bc62256a9b411a05fcd67"
name = "github.com/mattn/go-colorable"
packages = ["."]
pruneopts = "UT"
revision = "167de6bfdfba052fa6b2d3664c8f5272e23c9072"
version = "v0.0.9"
[[projects]]
digest = "1:d4d17353dbd05cb52a2a52b7fe1771883b682806f68db442b436294926bbfafb"
name = "github.com/mattn/go-isatty"
packages = ["."]
pruneopts = "UT"
revision = "0360b2af4f38e8d38c7fce2a9f4e702702d73a39"
version = "v0.0.3"
[[projects]]
digest = "1:5985ef4caf91ece5d54817c11ea25f182697534f8ae6521eadcd628c142ac4b6"
name = "github.com/matttproud/golang_protobuf_extensions"
packages = ["pbutil"]
pruneopts = "UT"
revision = "3247c84500bff8d9fb6d579d800f20b3e091582c"
version = "v1.0.0"
[[projects]]
digest = "1:27fa1b385644151ee1fc0c8b04f1f86963d9701335491b94488fd3912e370354"
name = "github.com/mongodb/mongo-go-driver"
packages = [
"bson",
"bson/bsoncodec",
"bson/bsonrw",
"bson/bsontype",
"bson/primitive",
"event",
"internal",
"mongo",
"mongo/options",
"mongo/readconcern",
"mongo/readpref",
"mongo/writeconcern",
"tag",
"version",
"x/bsonx",
"x/bsonx/bsoncore",
"x/mongo/driver",
"x/mongo/driver/auth",
"x/mongo/driver/auth/internal/gssapi",
"x/mongo/driver/session",
"x/mongo/driver/topology",
"x/mongo/driver/uuid",
"x/network/address",
"x/network/command",
"x/network/compressor",
"x/network/connection",
"x/network/connstring",
"x/network/description",
"x/network/result",
"x/network/wiremessage",
]
pruneopts = "UT"
revision = "3cfdc9ffcf780eede57d27ec26a3edad4c9ab6a9"
version = "v0.2.0"
[[projects]]
digest = "1:4ce94f3d2074662c04677db471456feb56078244ebcfe3789e6e95901c085bc5"
name = "github.com/nats-io/go-nats"
packages = [
".",
"encoders/builtin",
"util",
]
pruneopts = "UT"
revision = "fb0396ee0bdb8018b0fef30d6d1de798ce99cd05"
version = "v1.6.0"
[[projects]]
digest = "1:c3cd663f2f30b92536b9f290ac85c6310dae36a14cb8961553ae9ccf0d85ae41"
name = "github.com/nats-io/nuid"
packages = ["."]
pruneopts = "UT"
revision = "289cccf02c178dc782430d534e3c1f5b72af807f"
version = "v1.0.0"
[[projects]]
digest = "1:ee4d4af67d93cc7644157882329023ce9a7bcfce956a079069a9405521c7cc8d"
name = "github.com/opencontainers/go-digest"
packages = ["."]
pruneopts = "UT"
revision = "279bed98673dd5bef374d3b6e4b09e2af76183bf"
version = "v1.0.0-rc1"
[[projects]]
digest = "1:11db38d694c130c800d0aefb502fb02519e514dc53d9804ce51d1ad25ec27db6"
name = "github.com/opencontainers/image-spec"
packages = [
"specs-go",
"specs-go/v1",
]
pruneopts = "UT"
revision = "d60099175f88c47cd379c4738d158884749ed235"
version = "v1.0.1"
[[projects]]
digest = "1:1869683e323ebff2bdf8adcb560f82bf6f8d94019d35099e3403f7df12e9c07e"
name = "github.com/opencontainers/runc"
packages = [
"libcontainer/system",
"libcontainer/user",
]
pruneopts = "UT"
revision = "baf6536d6259209c3edfa2b22237af82942d3dfa"
version = "v0.1.1"
[[projects]]
digest = "1:3b830e06971be8448e7c9e57c9cc15ce53ab7e8846e7b7c39110a177c7fae466"
name = "github.com/ory/dockertest"
packages = [
"docker",
"docker/opts",
"docker/pkg/archive",
"docker/pkg/fileutils",
"docker/pkg/homedir",
"docker/pkg/idtools",
"docker/pkg/ioutils",
"docker/pkg/jsonmessage",
"docker/pkg/longpath",
"docker/pkg/mount",
"docker/pkg/pools",
"docker/pkg/stdcopy",
"docker/pkg/system",
"docker/pkg/term",
"docker/pkg/term/windows",
"docker/types",
"docker/types/blkiodev",
"docker/types/container",
"docker/types/filters",
"docker/types/mount",
"docker/types/network",
"docker/types/registry",
"docker/types/strslice",
"docker/types/versions",
]
pruneopts = "UT"
revision = "2e92e7784b6fb199fd168aa46269a2f1b34f299e"
version = "v3.3.0"
[[projects]]
digest = "1:40e195917a951a8bf867cd05de2a46aaf1806c50cf92eebf4c16f78cd196f747"
name = "github.com/pkg/errors"
packages = ["."]
pruneopts = "UT"
revision = "645ef00459ed84a119197bfb8d8205042c6df63d"
version = "v0.8.0"
[[projects]]
digest = "1:0028cb19b2e4c3112225cd871870f2d9cf49b9b4276531f03438a88e94be86fe"
name = "github.com/pmezard/go-difflib"
packages = ["difflib"]
pruneopts = "UT"
revision = "792786c7400a136282c1664665ae0a8db921c6c2"
version = "v1.0.0"
[[projects]]
digest = "1:26663fafdea73a38075b07e8e9d82fc0056379d2be8bb4e13899e8fda7c7dd23"
name = "github.com/prometheus/client_golang"
packages = [
"prometheus",
"prometheus/internal",
"prometheus/promhttp",
]
pruneopts = "UT"
revision = "abad2d1bd44235a26707c172eab6bca5bf2dbad3"
version = "v0.9.1"
[[projects]]
branch = "master"
digest = "1:32d10bdfa8f09ecf13598324dba86ab891f11db3c538b6a34d1c3b5b99d7c36b"
name = "github.com/prometheus/client_model"
packages = ["go"]
pruneopts = "UT"
revision = "99fa1f4be8e564e8a6b613da7fa6f46c9edafc6c"
[[projects]]
branch = "master"
digest = "1:e469cd65badf7694aeb44874518606d93c1d59e7735d3754ad442782437d3cc3"
name = "github.com/prometheus/common"
packages = [
"expfmt",
"internal/bitbucket.org/ww/goautoneg",
"model",
]
pruneopts = "UT"
revision = "d811d2e9bf898806ecfb6ef6296774b13ffc314c"
[[projects]]
branch = "master"
digest = "1:570784e0ddbf67f14087c6d8cfb7b999b9c55e75518a755e88cb202566ea8a17"
name = "github.com/prometheus/procfs"
packages = [
".",
"internal/util",
"nfs",
"xfs",
]
pruneopts = "UT"
revision = "8b1c2da0d56deffdbb9e48d4414b4e674bd8083e"
[[projects]]
branch = "master"
digest = "1:89cf776c0a621a34017c819b66b9e0f0bb076d3c5b9ca74763fb89570e140038"
name = "github.com/rubenv/sql-migrate"
packages = [
".",
"sqlparse",
]
pruneopts = "UT"
revision = "ba2c6a7295c59448dbc195cef2f41df5163b3892"
[[projects]]
digest = "1:274f67cb6fed9588ea2521ecdac05a6d62a8c51c074c1fccc6a49a40ba80e925"
name = "github.com/satori/go.uuid"
packages = ["."]
pruneopts = "UT"
revision = "f58768cc1a7a7e77a3bd49e98cdd21419399b6a3"
version = "v1.2.0"
[[projects]]
digest = "1:9e9193aa51197513b3abcb108970d831fbcf40ef96aa845c4f03276e1fa316d2"
name = "github.com/sirupsen/logrus"
packages = ["."]
pruneopts = "UT"
revision = "c155da19408a8799da419ed3eeb0cb5db0ad5dbc"
version = "v1.0.5"
[[projects]]
digest = "1:d3ffbc45dfe6929a4ba313464c2d761d96325d306eda29512da56c034db2a4dd"
name = "github.com/sony/gobreaker"
packages = ["."]
pruneopts = "UT"
revision = "b2a34562d02c4d5fd6645399c69e00141fb24e5a"
version = "0.4.0"
[[projects]]
digest = "1:645cabccbb4fa8aab25a956cbcbdf6a6845ca736b2c64e197ca7cbb9d210b939"
name = "github.com/spf13/cobra"
packages = ["."]
pruneopts = "UT"
revision = "ef82de70bb3f60c65fb8eebacbb2d122ef517385"
version = "v0.0.3"
[[projects]]
digest = "1:9424f440bba8f7508b69414634aef3b2b3a877e522d8a4624692412805407bb7"
name = "github.com/spf13/pflag"
packages = ["."]
pruneopts = "UT"
revision = "583c0c0531f06d5278b7d917446061adc344b5cd"
version = "v1.0.1"
[[projects]]
digest = "1:c40d65817cdd41fac9aa7af8bed56927bb2d6d47e4fea566a74880f5c2b1c41e"
name = "github.com/stretchr/testify"
packages = [
"assert",
"require",
]
pruneopts = "UT"
revision = "f35b8ab0b5a2cef36673838d662e249dd9c94686"
version = "v1.2.2"
[[projects]]
digest = "1:03aa6e485e528acb119fb32901cf99582c380225fc7d5a02758e08b180cb56c3"
name = "github.com/ugorji/go"
packages = ["codec"]
pruneopts = "UT"
revision = "b4c50a2b199d93b13dc15e78929cfb23bfdf21ab"
version = "v1.1.1"
[[projects]]
branch = "master"
digest = "1:40fdfd6ab85ca32b6935853bbba35935dcb1d796c8135efd85947566c76e662e"
name = "github.com/xdg/scram"
packages = ["."]
pruneopts = "UT"
revision = "7eeb5667e42c09cb51bf7b7c28aea8c56767da90"
[[projects]]
branch = "master"
digest = "1:f5c1d04bc09c644c592b45b9f0bad4030521b1a7d11c7dadbb272d9439fa6e8e"
name = "github.com/xdg/stringprep"
packages = ["."]
pruneopts = "UT"
revision = "73f8eece6fdcd902c185bf651de50f3828bed5ed"
[[projects]]
branch = "master"
digest = "1:7310f5459b88177d24e26c5ec7deb100b78ec03c0437d7a7aaec3c7eac333a55"
name = "golang.org/x/crypto"
packages = [
"bcrypt",
"blowfish",
"pbkdf2",
"ssh/terminal",
]
pruneopts = "UT"
revision = "4d3f4d9ffa16a13f451c3b2999e9c49e9750bf06"
[[projects]]
branch = "master"
digest = "1:70e155783b8dfd4f7697243384e2f361930ff0751459580769ce44d511f38b05"
name = "golang.org/x/net"
packages = [
"context",
"http/httpguts",
"http2",
"http2/hpack",
"idna",
"internal/socks",
"internal/timeseries",
"proxy",
"trace",
"websocket",
]
pruneopts = "UT"
revision = "f73e4c9ed3b7ebdd5f699a16a880c2b1994e50dd"
[[projects]]
branch = "master"
digest = "1:e0140c0c868c6e0f01c0380865194592c011fe521d6e12d78bfd33e756fe018a"
name = "golang.org/x/sync"
packages = ["semaphore"]
pruneopts = "UT"
revision = "1d60e4601c6fd243af51cc01ddf169918a5407ca"
[[projects]]
branch = "master"
digest = "1:66e34aa65b83061616603ba83517a4523c7261e18afd01c769dd215614bf7d70"
name = "golang.org/x/sys"
packages = [
"unix",
"windows",
]
pruneopts = "UT"
revision = "7dfd1290c7917b7ba22824b9d24954ab3002fe24"
[[projects]]
digest = "1:a2ab62866c75542dd18d2b069fec854577a20211d7c0ea6ae746072a1dccdd18"
name = "golang.org/x/text"
packages = [
"collate",
"collate/build",
"internal/colltab",
"internal/gen",
"internal/tag",
"internal/triegen",
"internal/ucd",
"language",
"secure/bidirule",
"transform",
"unicode/bidi",
"unicode/cldr",
"unicode/norm",
"unicode/rangetable",
]
pruneopts = "UT"
revision = "f21a4dfb5e38f5895301dc265a8def02365cc3d0"
version = "v0.3.0"
[[projects]]
branch = "master"
digest = "1:cd018653a358d4b743a9d3bee89e825521f2ab2f2ec0770164bf7632d8d73ab7"
name = "google.golang.org/genproto"
packages = ["googleapis/rpc/status"]
pruneopts = "UT"
revision = "86e600f69ee4704c6efbf6a2a40a5c10700e76c2"
[[projects]]
digest = "1:c3ad9841823db6da420a5625b367913b4ff54bbe60e8e3c98bd20e243e62e2d2"
name = "google.golang.org/grpc"
packages = [
".",
"balancer",
"balancer/base",
"balancer/roundrobin",
"codes",
"connectivity",
"credentials",
"encoding",
"encoding/proto",
"grpclog",
"internal",
"internal/backoff",
"internal/channelz",
"internal/envconfig",
"internal/grpcrand",
"internal/transport",
"keepalive",
"metadata",
"naming",
"peer",
"resolver",
"resolver/dns",
"resolver/passthrough",
"stats",
"status",
"tap",
]
pruneopts = "UT"
revision = "2e463a05d100327ca47ac218281906921038fd95"
version = "v1.16.0"
[[projects]]
digest = "1:fa9a7c0ef59217bd22f32eb7cc027894d73f340a5633258cc079dec025db6a7f"
name = "gopkg.in/gorp.v1"
packages = ["."]
pruneopts = "UT"
revision = "c87af80f3cc5036b55b83d77171e156791085e2e"
version = "v1.7.1"
[[projects]]
digest = "1:2d1fbdc6777e5408cabeb02bf336305e724b925ff4546ded0fa8715a7267922a"
name = "gopkg.in/inf.v0"
packages = ["."]
pruneopts = "UT"
revision = "d2d2541c53f18d2a059457998ce2876cc8e67cbf"
version = "v0.9.1"
[[projects]]
digest = "1:de4bc452b049bde272d99e944aabcf218dc22df0a478a42a7107932f82430ae2"
name = "gopkg.in/ory-am/dockertest.v3"
packages = ["."]
pruneopts = "UT"
revision = "9f1141b78507d23603f6be530587664ebb63a8d2"
version = "v3.3.2"
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
input-imports = [
"github.com/asaskevich/govalidator",
"github.com/cisco/senml",
"github.com/dereulenspiegel/coap-mux",
"github.com/dgrijalva/jwt-go",
"github.com/docker/docker/pkg/namesgenerator",
"github.com/dustin/go-coap",
"github.com/eclipse/paho.mqtt.golang",
"github.com/fatih/color",
"github.com/go-kit/kit/endpoint",
"github.com/go-kit/kit/log",
"github.com/go-kit/kit/metrics",
"github.com/go-kit/kit/metrics/prometheus",
"github.com/go-kit/kit/transport/grpc",
"github.com/go-kit/kit/transport/http",
"github.com/go-redis/redis",
"github.com/go-zoo/bone",
"github.com/gocql/gocql",
"github.com/gogo/protobuf/proto",
"github.com/golang/protobuf/proto",
"github.com/gorilla/websocket",
"github.com/hokaccha/go-prettyjson",
"github.com/influxdata/influxdb/client/v2",
"github.com/lib/pq",
"github.com/mongodb/mongo-go-driver/mongo",
"github.com/nats-io/go-nats",
"github.com/prometheus/client_golang/prometheus",
"github.com/prometheus/client_golang/prometheus/promhttp",
"github.com/rubenv/sql-migrate",
"github.com/satori/go.uuid",
"github.com/sony/gobreaker",
"github.com/spf13/cobra",
"github.com/stretchr/testify/assert",
"github.com/stretchr/testify/require",
"golang.org/x/crypto/bcrypt",
"golang.org/x/net/context",
"google.golang.org/grpc",
"google.golang.org/grpc/codes",
"google.golang.org/grpc/credentials",
"google.golang.org/grpc/status",
"gopkg.in/ory-am/dockertest.v3",
]
solver-name = "gps-cdcl"
solver-version = 1
-115
View File
@@ -1,115 +0,0 @@
[[constraint]]
name = "github.com/asaskevich/govalidator"
version = "9.0.0"
[[constraint]]
name = "github.com/cisco/senml"
branch = "master"
[[constraint]]
name = "github.com/dgrijalva/jwt-go"
version = "3.2.0"
[[constraint]]
name = "github.com/eclipse/paho.mqtt.golang"
version = "1.1.1"
[[constraint]]
name = "github.com/fatih/color"
version = "1.7.0"
[[constraint]]
name = "github.com/go-kit/kit"
version = "0.7.0"
[[constraint]]
name = "github.com/go-zoo/bone"
version = "1.2.0"
[[constraint]]
name = "github.com/golang/protobuf"
version = "1.2.0"
[[constraint]]
name = "github.com/hokaccha/go-prettyjson"
branch = "master"
[[constraint]]
name = "github.com/lib/pq"
branch = "master"
[[constraint]]
name = "github.com/nats-io/go-nats"
version = "1.6.0"
[[constraint]]
name = "github.com/prometheus/client_golang"
version = "0.9.1"
[[constraint]]
name = "github.com/satori/go.uuid"
version = "1.2.0"
[[constraint]]
name = "github.com/sony/gobreaker"
version = "0.4.0"
[[constraint]]
name = "github.com/spf13/cobra"
version = "0.0.3"
[[constraint]]
name = "github.com/stretchr/testify"
version = "1.2.2"
[[constraint]]
name = "golang.org/x/crypto"
branch = "master"
[[constraint]]
name = "gopkg.in/ory-am/dockertest.v3"
version = "3.3.2"
[[constraint]]
name = "github.com/gorilla/websocket"
version = "1.4.0"
[[constraint]]
name = "github.com/rubenv/sql-migrate"
branch = "master"
[[constraint]]
name = "github.com/influxdata/influxdb"
version = "1.6.4"
[[constraint]]
name = "github.com/mongodb/mongo-go-driver"
version = "0.0.5"
[[constraint]]
name = "github.com/gocql/gocql"
branch = "master"
[[constraint]]
name = "github.com/dereulenspiegel/coap-mux"
branch = "master"
[[constraint]]
name = "github.com/dustin/go-coap"
branch = "master"
[[constraint]]
name = "github.com/gogo/protobuf"
version = "1.1.1"
[[constraint]]
name = "google.golang.org/grpc"
version = "1.16.0"
[[constraint]]
name = "github.com/go-redis/redis"
version = "v6.15.0"
[prune]
go-tests = true
unused-packages = true
+1 -1
View File
@@ -176,7 +176,7 @@
END OF TERMS AND CONDITIONS
Copyright 2015-2019 Mainflux
Copyright 2015-2020 Mainflux
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
+46 -55
View File
@@ -1,93 +1,84 @@
## Copyright (c) 2015-2019
## Mainflux
##
## SPDX-License-Identifier: Apache-2.0
# Copyright (c) Mainflux
# SPDX-License-Identifier: Apache-2.0
MF_DOCKER_IMAGE_NAME_PREFIX ?= mainflux
BUILD_DIR = build
SERVICES = users things http normalizer ws coap lora influxdb-writer influxdb-reader mongodb-writer mongodb-reader cassandra-writer cassandra-reader cli bootstrap
SERVICES = users things http coap lora influxdb-writer influxdb-reader mongodb-writer \
mongodb-reader cassandra-writer cassandra-reader postgres-writer postgres-reader cli \
bootstrap opcua auth twins mqtt provision certs smtp-notifier
DOCKERS = $(addprefix docker_,$(SERVICES))
DOCKERS_DEV = $(addprefix docker_dev_,$(SERVICES))
CGO_ENABLED ?= 0
GOOS ?= linux
GOARCH ?= amd64
define compile_service
CGO_ENABLED=$(CGO_ENABLED) GOOS=$(GOOS) GOARCH=$(GOARCH) GOARM=$(GOARM) go build -ldflags "-s -w" -o ${BUILD_DIR}/mainflux-$(1) cmd/$(1)/main.go
CGO_ENABLED=$(CGO_ENABLED) GOOS=$(GOOS) GOARCH=$(GOARCH) GOARM=$(GOARM) go build -mod=vendor -ldflags "-s -w" -o ${BUILD_DIR}/mainflux-$(1) cmd/$(1)/main.go
endef
define make_docker
docker build --no-cache --build-arg SVC_NAME=$(subst docker_,,$(1)) --tag=mainflux/$(subst docker_,,$(1)) -f docker/Dockerfile .
$(eval svc=$(subst docker_,,$(1)))
docker build \
--no-cache \
--build-arg SVC=$(svc) \
--build-arg GOARCH=$(GOARCH) \
--build-arg GOARM=$(GOARM) \
--tag=$(MF_DOCKER_IMAGE_NAME_PREFIX)/$(svc) \
-f docker/Dockerfile .
endef
define make_docker_dev
docker build --build-arg SVC_NAME=$(subst docker_dev_,,$(1)) --tag=mainflux/$(subst docker_dev_,,$(1)) -f docker/Dockerfile.dev ./build
$(eval svc=$(subst docker_dev_,,$(1)))
docker build \
--no-cache \
--build-arg SVC=$(svc) \
--tag=$(MF_DOCKER_IMAGE_NAME_PREFIX)/$(svc) \
-f docker/Dockerfile.dev ./build
endef
all: $(SERVICES) mqtt
all: $(SERVICES)
.PHONY: all $(SERVICES) dockers dockers_dev latest release mqtt ui
.PHONY: all $(SERVICES) dockers dockers_dev latest release
clean:
rm -rf ${BUILD_DIR}
rm -rf mqtt/node_modules
cleandocker: cleanghost
# Stop all containers (if running)
docker-compose -f docker/docker-compose.yml stop
# Remove mainflux containers
docker ps -f name=mainflux -aq | xargs -r docker rm
# Remove old mainflux images
docker images -q mainflux\/* | xargs -r docker rmi
cleandocker:
# Stops containers and removes containers, networks, volumes, and images created by up
docker-compose -f docker/docker-compose.yml down --rmi all -v --remove-orphans
# Clean ghost docker images
cleanghost:
# Remove exited containers
docker ps -f status=dead -f status=exited -aq | xargs -r docker rm -v
# Remove unused images
docker images -f dangling=true -q | xargs -r docker rmi
ifdef pv
# Remove unused volumes
docker volume ls -f dangling=true -q | xargs -r docker volume rm
docker volume ls -f name=$(MF_DOCKER_IMAGE_NAME_PREFIX) -f dangling=true -q | xargs -r docker volume rm
endif
install:
cp ${BUILD_DIR}/* $(GOBIN)
test:
go test -v -race -count 1 -tags test $(shell go list ./... | grep -v 'vendor\|cmd')
go test -mod=vendor -v -race -count 1 -tags test $(shell go list ./... | grep -v 'vendor\|cmd')
proto:
protoc --gofast_out=plugins=grpc:. *.proto
protoc --gofast_out=plugins=grpc:. pkg/messaging/*.proto
$(SERVICES):
$(call compile_service,$(@))
$(DOCKERS):
$(call make_docker,$(@))
docker_ui:
$(MAKE) -C ui docker
docker_mqtt:
# MQTT Docker build must be done from root dir because it copies .proto files
docker build --tag=mainflux/mqtt -f mqtt/Dockerfile .
dockers: $(DOCKERS) docker_ui docker_mqtt
$(call make_docker,$(@),$(GOARCH))
$(DOCKERS_DEV):
$(call make_docker_dev,$(@))
dockers: $(DOCKERS)
dockers_dev: $(DOCKERS_DEV)
ui:
$(MAKE) -C ui
mqtt:
cd mqtt && npm install
define docker_push
for svc in $(SERVICES); do \
docker push mainflux/$$svc:$(1); \
docker push $(MF_DOCKER_IMAGE_NAME_PREFIX)/$$svc:$(1); \
done
docker push mainflux/ui:$(1)
docker push mainflux/mqtt:$(1)
endef
changelog:
@@ -101,10 +92,8 @@ release:
git checkout $(version)
$(MAKE) dockers
for svc in $(SERVICES); do \
docker tag mainflux/$$svc mainflux/$$svc:$(version); \
docker tag $(MF_DOCKER_IMAGE_NAME_PREFIX)/$$svc $(MF_DOCKER_IMAGE_NAME_PREFIX)/$$svc:$(version); \
done
docker tag mainflux/ui mainflux/ui:$(version)
docker tag mainflux/mqtt mainflux/mqtt:$(version)
$(call docker_push,$(version))
rundev:
@@ -113,10 +102,12 @@ rundev:
run:
docker-compose -f docker/docker-compose.yml up
runui:
$(MAKE) -C ui run
runlora:
docker-compose -f docker/docker-compose.yml up -d
docker-compose -f docker/addons/influxdb-writer/docker-compose.yml up -d
docker-compose -f docker/addons/lora-adapter/docker-compose.yml up
docker-compose \
-f docker/docker-compose.yml \
-f docker/addons/influxdb-writer/docker-compose.yml \
-f docker/addons/lora-adapter/docker-compose.yml up \
# Run all Mainflux core services except distributed tracing system - Jaeger. Recommended on gateways:
rungw:
MF_JAEGER_URL= docker-compose -f docker/docker-compose.yml up --scale jaeger=0
+77 -32
View File
@@ -8,104 +8,145 @@
![banner][banner]
Mainflux is modern, scalable, secure open source and patent-free IoT cloud platform written in Go.
Mainflux is modern, scalable, secure, open-source, and patent-free IoT cloud platform written in Go.
It accepts user, device, and application connections over various network protocols (i.e. HTTP,
It accepts user and thing (sensor, actuator, application) connections over various network protocols (i.e. HTTP,
MQTT, WebSocket, CoAP), thus making a seamless bridge between them. It is used as the IoT middleware
for building complex IoT solutions.
For more details, check out the [official documentation][docs].
Mainflux is member of the [Linux Foundation][lf] and an active contributor
to the [EdgeX Foundry][edgex] project. It has been made with :heart: by [Mainflux company][company],
to the [EdgeX Foundry][edgex] project. It has been made with :heart: by [Mainflux Labs][company],
which maintains the project and offers professional services around it.
## Features
- Multi-protocol connectivity and protocol bridging (HTTP, MQTT, WebSocket and CoAP)
- Device management and provisioning
- Fine-grained access control
- Storage support (Cassandra, InfluxDB and MongoDB)
- Platform logging and instrumentation support
- Multi-protocol connectivity and bridging (HTTP, MQTT, WebSocket and CoAP)
- Device management and provisioning (Zero Touch provisioning)
- Mutual TLS Authentication (mTLS) using X.509 Certificates
- Fine-grained access control (policies, ABAC/RBAC)
- Message persistence (Cassandra, InfluxDB, MongoDB and PostgresSQL)
- Platform logging and instrumentation support (Grafana, Prometheus and OpenTracing)
- Event sourcing
- Container-based deployment using [Docker][docker] and [Kubernetes][kubernetes]
- [LoRaWAN][lora] network integration
- [OPC UA](opcua) integration
- Edge [Agent](agent) and [Export](export) services for remote IoT gateway management and edge computing
- SDK
- CLI
- Small memory footprint and fast execution
- Domain-driven design architecture, high-quality code and test coverage
## Prerequisites
The following are needed to run Mainflux:
- [Docker](https://docs.docker.com/install/) (version 20.10)
- [Docker compose](https://docs.docker.com/compose/install/) (version 1.28)
Developing Mainflux will also require:
- [Go](https://golang.org/doc/install) (version 1.13.3)
- [Protobuf](https://github.com/protocolbuffers/protobuf#protocol-compiler-installation) (version 3.6.1)
## Install
Before proceeding, install the following prerequisites:
- [Docker](https://docs.docker.com/install/)
- [Docker compose](https://docs.docker.com/compose/install/)
Once everything is installed, execute the following commands from project root:
Once the prerequisites are installed, execute the following commands from the project's root:
```bash
docker-compose -f docker/docker-compose.yml up -d
docker-compose -f docker/docker-compose.yml up
```
This will bring up all Mainflux dockers and inter-connect them in the composition.
This will bring up the Mainflux docker services and interconnect them. This command can also be executed using the project's included Makefile:
```bash
make run
```
If you want to run services from specific release checkout code from github and make sure that
`MF_RELEASE_TAG` in [.env](.env) is being set to match the release version
```bash
git checkout tags/<release_number> -b <release_number>
# e.g. `git checkout tags/0.12.0 -b 0.12.0`
```
Check that `.env` file contains:
```bash
MF_RELEASE_TAG=<release_number>
```
>`docker-compose` should be used for development and testing deployments. For production we suggest using [Kubernetes](https://mainflux.readthedocs.io/en/latest/kubernetes/).
## Usage
Best way to quickstart using Mainflux is via CLI:
```
The quickest way to start using Mainflux is via the CLI. The latest version can be downloaded from the [official releases page][rel].
It can also be built and used from the project's root directory:
```bash
make cli
./build/mainflux-cli version
```
> Mainflux CLI can also be downloaded as a tarball from [offical release page][rel]
If this works, head to [official documentation][docs] to understand Mainflux provisioning and messaging.
Additional details on using the CLI can be found in the [CLI documentation](https://mainflux.readthedocs.io/en/latest/cli/).
## Documentation
Official documentation is hosted at [Mainflux Read The Docs page][docs].
Documentation is auto-generated from Markdown files in `./docs` directory.
If you spot an error or need for corrections, please let us know - or even better: send us a PR.
Official documentation is hosted at [Mainflux Read The Docs page][docs]. Documentation is auto-generated, checkout the instructions on [official docs repository](https://github.com/mainflux/docs):
If you spot an error or a need for corrections, please let us know - or even better: send us a PR.
Additional practical information, news and tutorials can be found on the [Mainflux blog][blog].
## Authors
Main architect and BDFL of Mainflux project is [@drasko][drasko].
Additionally, [@nmarcetic][nikola] and [@janko-isidorovic][janko] assured
overall architecture and design, while [@manuio][manu] and [@darkodraskovic][darko]
helped with crafting initial implementation and continiusly work on the project evolutions.
helped with crafting initial implementation and continuously worked on the project evolutions.
Besides them, Mainflux is constantly improved and actively
developed by [@anovakovic01][alex], [@dusanb94][dusan], [@srados][sava],
[@gsaleh][george], [@blokovi][iva], [@chombium][kole] and a large set of contributors.
[@gsaleh][george], [@blokovi][iva], [@chombium][kole], [@mteodor][mirko] and a large set of contributors.
Maintainers are listed in [MAINTAINERS](MAINTAINERS) file.
Mainflux team would like to give special thanks to [@mijicd][dejan] for his monumental work
on designing and implementing highly improved and optimized version of the platform,
and [@malidukica][dusanm] for his effort on implementing initial user interface.
The Mainflux team would like to give special thanks to [@mijicd][dejan] for his monumental work
on designing and implementing a highly improved and optimized version of the platform,
and [@malidukica][dusanm] for his effort on implementing the initial user interface.
## Contributing
Thank you for your interest in Mainflux and wish to contribute!
1. Take a look at our [open issues](https://github.com/mainflux/mainflux/issues).
Thank you for your interest in Mainflux and the desire to contribute!
1. Take a look at our [open issues](https://github.com/mainflux/mainflux/issues). The [good-first-issue](https://github.com/mainflux/mainflux/labels/good-first-issue) label is specifically for issues that are great for getting started.
2. Checkout the [contribution guide](CONTRIBUTING.md) to learn more about our style and conventions.
3. Make your changes compatible to our workflow.
### We're Hiring
If you are interested in working professionally on Mainflux,
please head to company's [careers page][careers] or shoot us an e-mail at <careers@mainflux.com>.
Note that the best way to grab our attention is by sending PRs :sunglasses:.
>The best way to grab our attention is by sending PRs :sunglasses:.
## Community
- [Google group][forum]
- [Gitter][gitter]
- [Twitter][twitter]
## License
[Apache-2.0](LICENSE)
[banner]: https://github.com/mainflux/mainflux/blob/master/docs/img/gopherBanner.jpg
[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fmainflux%2Fmainflux.svg?type=large)](https://app.fossa.com/projects/git%2Bgithub.com%2Fmainflux%2Fmainflux?ref=badge_large)
[banner]: https://github.com/mainflux/docs/blob/master/docs/img/gopherBanner.jpg
[ci-badge]: https://semaphoreci.com/api/v1/mainflux/mainflux/branches/master/badge.svg
[ci-url]: https://semaphoreci.com/mainflux/mainflux
[docs]: http://mainflux.readthedocs.io
@@ -120,6 +161,9 @@ Note that the best way to grab our attention is by sending PRs :sunglasses:.
[license]: https://img.shields.io/badge/license-Apache%20v2.0-blue.svg
[twitter]: https://twitter.com/mainflux
[lora]: https://lora-alliance.org/
[opcua]: https://opcfoundation.org/about/opc-technologies/opc-ua/
[agent]: https://github.com/mainflux/agent
[export]: https://github.com/mainflux/export
[kubernetes]: https://kubernetes.io/
[rel]: https://github.com/mainflux/mainflux/releases
[careers]: https://www.mainflux.com/careers.html
@@ -140,3 +184,4 @@ Note that the best way to grab our attention is by sending PRs :sunglasses:.
[iva]: https://github.com/blokovi
[kole]: https://github.com/chombium
[dusanm]: https://github.com/malidukica
[mirko]: https://github.com/mteodor
+1 -5
View File
@@ -1,9 +1,5 @@
//
// Copyright (c) 2018
// Mainflux
//
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
//
package mainflux
+3794
View File
File diff suppressed because it is too large Load Diff
+96
View File
@@ -0,0 +1,96 @@
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
syntax = "proto3";
package mainflux;
import "google/protobuf/empty.proto";
service ThingsService {
rpc CanAccessByKey(AccessByKeyReq) returns (ThingID) {}
rpc IsChannelOwner(ChannelOwnerReq) returns (google.protobuf.Empty) {}
rpc CanAccessByID(AccessByIDReq) returns (google.protobuf.Empty) {}
rpc Identify(Token) returns (ThingID) {}
}
service AuthService {
rpc Issue(IssueReq) returns (Token) {}
rpc Identify(Token) returns (UserIdentity) {}
rpc Authorize(AuthorizeReq) returns (AuthorizeRes) {}
rpc Assign(Assignment) returns(google.protobuf.Empty) {}
rpc Members(MembersReq) returns (MembersRes) {}
}
message AccessByKeyReq {
string token = 1;
string chanID = 2;
}
message ChannelOwnerReq {
string owner = 1;
string chanID = 2;
}
message ThingID {
string value = 1;
}
message ChannelID {
string value = 1;
}
message AccessByIDReq {
string thingID = 1;
string chanID = 2;
}
// If a token is not carrying any information itself, the type
// field can be used to determine how to validate the token.
// Also, different tokens can be encoded in different ways.
message Token {
string value = 1;
}
message UserIdentity {
string id = 1;
string email = 2;
}
message IssueReq {
string id = 1;
string email = 2;
uint32 type = 3;
}
message AuthorizeReq {
string sub = 1;
string obj = 2;
string act = 3;
}
message AuthorizeRes {
bool authorized = 1;
}
message Assignment {
string token = 1;
string groupID = 2;
string memberID = 3;
}
message MembersReq {
string token = 1;
string groupID = 2;
uint64 offset = 3;
uint64 limit = 4;
string type = 5;
}
message MembersRes {
uint64 total = 1;
uint64 offset = 2;
uint64 limit = 3;
string type = 4;
repeated string members = 5;
}
+108
View File
@@ -0,0 +1,108 @@
# Auth - Authentication and Authorization service
Auth service provides authentication features as an API for managing authentication keys as well as administering groups of entities - `things` and `users`.
# Authentication
User service is using Auth service gRPC API to obtain login token or password reset token. Authentication key consists of the following fields:
- ID - key ID
- Type - one of the three types described below
- IssuerID - an ID of the Mainflux User who issued the key
- Subject - user email
- IssuedAt - the timestamp when the key is issued
- ExpiresAt - the timestamp after which the key is invalid
There are *three types of authentication keys*:
- User key - keys issued to the user upon login request
- API key - keys issued upon the user request
- Recovery key - password recovery key
Authentication keys are represented and distributed by the corresponding [JWT](jwt.io).
User keys are issued when user logs in. Each user request (other than `registration` and `login`) contains user key that is used to authenticate the user.
API keys are similar to the User keys. The main difference is that API keys have configurable expiration time. If no time is set, the key will never expire. For that reason, API keys are _the only key type that can be revoked_. This also means that, despite being used as a JWT, it requires a query to the database to validate the API key. The user with API key can perform all the same actions as the user with login key (can act on behalf of the user for Thing, Channel, or user profile management), *except issuing new API keys*.
Recovery key is the password recovery key. It's short-lived token used for password recovery process.
For in-depth explanation of the aforementioned scenarios, as well as thorough
understanding of Mainflux, please check out the [official documentation][doc].
The following actions are supported:
- create (all key types)
- verify (all key types)
- obtain (API keys only)
- revoke (API keys only)
# Groups
User and Things service are using Auth gRPC API to get the list of ids that are part of a group. Groups can be organized as tree structure.
Group consists of the following fields:
- ID - ULID id uniquely representing group
- Name - name of the group, name of the group is unique at the same level of tree hierarchy for a given tree.
- ParentID - id of the parent group
- OwnerID - id of the user that created a group
- Description - free form text, up to 1024 characters
- Metadata - Arbitrary, object-encoded group's data
- Path - tree path consisting of group ids
- CreatedAt - timestamp at which the group is created
- UpdatedAt - timestamp at which the group is updated
## Configuration
The service is configured using the environment variables presented in the
following table. Note that any unset variables will be replaced with their
default values.
| Variable | Description | Default |
|---------------------------|--------------------------------------------------------------------------|---------------|
| MF_AUTH_LOG_LEVEL | Service level (debug, info, warn, error) | error |
| MF_AUTH_DB_HOST | Database host address | localhost |
| MF_AUTH_DB_PORT | Database host port | 5432 |
| MF_AUTH_DB_USER | Database user | mainflux |
| MF_AUTH_DB_PASSWORD | Database password | mainflux |
| MF_AUTH_DB | Name of the database used by the service | auth |
| MF_AUTH_DB_SSL_MODE | Database connection SSL mode (disable, require, verify-ca, verify-full) | disable |
| MF_AUTH_DB_SSL_CERT | Path to the PEM encoded certificate file | |
| MF_AUTH_DB_SSL_KEY | Path to the PEM encoded key file | |
| MF_AUTH_DB_SSL_ROOT_CERT | Path to the PEM encoded root certificate file | |
| MF_AUTH_HTTP_PORT | Auth service HTTP port | 8180 |
| MF_AUTH_GRPC_PORT | Auth service gRPC port | 8181 |
| MF_AUTH_SERVER_CERT | Path to server certificate in pem format | |
| MF_AUTH_SERVER_KEY | Path to server key in pem format | |
| MF_AUTH_SECRET | String used for signing tokens | auth |
| MF_JAEGER_URL | Jaeger server URL | localhost:6831|
## Deployment
The service itself is distributed as Docker container. Check the [`auth`](https://github.com/mainflux/mainflux/blob/master/docker/docker-compose.yml#L71-L94) service section in
docker-compose to see how service is deployed.
To start the service outside of the container, execute the following shell script:
```bash
# download the latest version of the service
go get github.com/mainflux/mainflux
cd $GOPATH/src/github.com/mainflux/mainflux
# compile the service
make auth
# copy binary to bin
make install
# set the environment variables and run the service
MF_AUTH_LOG_LEVEL=[Service log level] MF_AUTH_DB_HOST=[Database host address] MF_AUTH_DB_PORT=[Database host port] MF_AUTH_DB_USER=[Database user] MF_AUTH_DB_PASS=[Database password] MF_AUTH_DB=[Name of the database used by the service] MF_AUTH_DB_SSL_MODE=[SSL mode to connect to the database with] MF_AUTH_DB_SSL_CERT=[Path to the PEM encoded certificate file] MF_AUTH_DB_SSL_KEY=[Path to the PEM encoded key file] MF_AUTH_DB_SSL_ROOT_CERT=[Path to the PEM encoded root certificate file] MF_AUTH_HTTP_PORT=[Service HTTP port] MF_AUTH_GRPC_PORT=[Service gRPC port] MF_AUTH_SECRET=[String used for signing tokens] MF_AUTH_SERVER_CERT=[Path to server certificate] MF_AUTH_SERVER_KEY=[Path to server key] MF_JAEGER_URL=[Jaeger server URL] $GOBIN/mainflux-auth
```
If `MF_EMAIL_TEMPLATE` doesn't point to any file service will function but password reset functionality will not work.
## Usage
For more information about service capabilities and its usage, please check out
the [API documentation](openapi.yml).
[doc]: http://mainflux.readthedocs.io
+5
View File
@@ -0,0 +1,5 @@
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
// Package api contains implementation of Auth service HTTP API.
package api
+226
View File
@@ -0,0 +1,226 @@
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
package grpc
import (
"context"
"time"
"github.com/go-kit/kit/endpoint"
kitot "github.com/go-kit/kit/tracing/opentracing"
kitgrpc "github.com/go-kit/kit/transport/grpc"
"github.com/golang/protobuf/ptypes/empty"
"github.com/mainflux/mainflux"
opentracing "github.com/opentracing/opentracing-go"
"google.golang.org/grpc"
)
const (
svcName = "mainflux.AuthService"
)
var _ mainflux.AuthServiceClient = (*grpcClient)(nil)
type grpcClient struct {
issue endpoint.Endpoint
identify endpoint.Endpoint
authorize endpoint.Endpoint
assign endpoint.Endpoint
members endpoint.Endpoint
timeout time.Duration
}
// NewClient returns new gRPC client instance.
func NewClient(tracer opentracing.Tracer, conn *grpc.ClientConn, timeout time.Duration) mainflux.AuthServiceClient {
return &grpcClient{
issue: kitot.TraceClient(tracer, "issue")(kitgrpc.NewClient(
conn,
svcName,
"Issue",
encodeIssueRequest,
decodeIssueResponse,
mainflux.UserIdentity{},
).Endpoint()),
identify: kitot.TraceClient(tracer, "identify")(kitgrpc.NewClient(
conn,
svcName,
"Identify",
encodeIdentifyRequest,
decodeIdentifyResponse,
mainflux.UserIdentity{},
).Endpoint()),
authorize: kitot.TraceClient(tracer, "authorize")(kitgrpc.NewClient(
conn,
svcName,
"Authorize",
encodeAuthorizeRequest,
decodeAuthorizeResponse,
mainflux.AuthorizeRes{},
).Endpoint()),
assign: kitot.TraceClient(tracer, "assign")(kitgrpc.NewClient(
conn,
svcName,
"Assign",
encodeAssignRequest,
decodeAssignResponse,
mainflux.AuthorizeRes{},
).Endpoint()),
members: kitot.TraceClient(tracer, "members")(kitgrpc.NewClient(
conn,
svcName,
"Members",
encodeMembersRequest,
decodeMembersResponse,
mainflux.MembersRes{},
).Endpoint()),
timeout: timeout,
}
}
func (client grpcClient) Issue(ctx context.Context, req *mainflux.IssueReq, _ ...grpc.CallOption) (*mainflux.Token, error) {
ctx, close := context.WithTimeout(ctx, client.timeout)
defer close()
res, err := client.issue(ctx, issueReq{id: req.GetId(), email: req.GetEmail(), keyType: req.Type})
if err != nil {
return nil, err
}
ir := res.(identityRes)
return &mainflux.Token{Value: ir.id}, nil
}
func encodeIssueRequest(_ context.Context, grpcReq interface{}) (interface{}, error) {
req := grpcReq.(issueReq)
return &mainflux.IssueReq{Id: req.id, Email: req.email, Type: req.keyType}, nil
}
func decodeIssueResponse(_ context.Context, grpcRes interface{}) (interface{}, error) {
res := grpcRes.(*mainflux.UserIdentity)
return identityRes{id: res.GetId(), email: res.GetEmail()}, nil
}
func (client grpcClient) Identify(ctx context.Context, token *mainflux.Token, _ ...grpc.CallOption) (*mainflux.UserIdentity, error) {
ctx, close := context.WithTimeout(ctx, client.timeout)
defer close()
res, err := client.identify(ctx, identityReq{token: token.GetValue()})
if err != nil {
return nil, err
}
ir := res.(identityRes)
return &mainflux.UserIdentity{Id: ir.id, Email: ir.email}, nil
}
func encodeIdentifyRequest(_ context.Context, grpcReq interface{}) (interface{}, error) {
req := grpcReq.(identityReq)
return &mainflux.Token{Value: req.token}, nil
}
func decodeIdentifyResponse(_ context.Context, grpcRes interface{}) (interface{}, error) {
res := grpcRes.(*mainflux.UserIdentity)
return identityRes{id: res.GetId(), email: res.GetEmail()}, nil
}
func (client grpcClient) Authorize(ctx context.Context, req *mainflux.AuthorizeReq, _ ...grpc.CallOption) (r *mainflux.AuthorizeRes, err error) {
ctx, close := context.WithTimeout(ctx, client.timeout)
defer close()
res, err := client.authorize(ctx, authReq{Act: req.Act, Obj: req.Obj, Sub: req.Sub})
if err != nil {
return &mainflux.AuthorizeRes{Authorized: false}, err
}
ar := res.(authorizeRes)
return &mainflux.AuthorizeRes{Authorized: ar.authorized}, err
}
func decodeAuthorizeResponse(_ context.Context, grpcRes interface{}) (interface{}, error) {
res := grpcRes.(*mainflux.AuthorizeRes)
return authorizeRes{authorized: res.Authorized}, nil
}
func encodeAuthorizeRequest(_ context.Context, grpcReq interface{}) (interface{}, error) {
req := grpcReq.(authReq)
return &mainflux.AuthorizeReq{
Sub: req.Sub,
Obj: req.Obj,
Act: req.Act,
}, nil
}
func (client grpcClient) Members(ctx context.Context, req *mainflux.MembersReq, _ ...grpc.CallOption) (r *mainflux.MembersRes, err error) {
ctx, close := context.WithTimeout(ctx, client.timeout)
defer close()
res, err := client.members(ctx, membersReq{
token: req.GetToken(),
groupID: req.GetGroupID(),
memberType: req.GetType(),
offset: req.GetOffset(),
limit: req.GetLimit(),
})
if err != nil {
return &mainflux.MembersRes{}, err
}
mr := res.(membersRes)
return &mainflux.MembersRes{
Offset: mr.offset,
Limit: mr.limit,
Total: mr.total,
Type: mr.groupType,
Members: mr.members,
}, err
}
func encodeMembersRequest(_ context.Context, grpcReq interface{}) (interface{}, error) {
req := grpcReq.(membersReq)
return &mainflux.MembersReq{
Token: req.token,
Offset: req.offset,
Limit: req.limit,
GroupID: req.groupID,
Type: req.memberType,
}, nil
}
func decodeMembersResponse(_ context.Context, grpcRes interface{}) (interface{}, error) {
res := grpcRes.(*mainflux.MembersRes)
return membersRes{
offset: res.Offset,
limit: res.Limit,
total: res.Total,
members: res.Members,
}, nil
}
func (client grpcClient) Assign(ctx context.Context, req *mainflux.Assignment, _ ...grpc.CallOption) (r *empty.Empty, err error) {
ctx, close := context.WithTimeout(ctx, client.timeout)
defer close()
_, err = client.assign(ctx, assignReq{token: req.GetToken(), groupID: req.GetGroupID(), memberID: req.GetMemberID()})
if err != nil {
return &empty.Empty{}, err
}
return &empty.Empty{}, err
}
func encodeAssignRequest(_ context.Context, grpcRes interface{}) (interface{}, error) {
res := grpcRes.(*mainflux.AuthorizeRes)
return authorizeRes{authorized: res.Authorized}, nil
}
func decodeAssignResponse(_ context.Context, grpcReq interface{}) (interface{}, error) {
req := grpcReq.(authReq)
return &mainflux.AuthorizeReq{
Sub: req.Sub,
Obj: req.Obj,
Act: req.Act,
}, nil
}
+5
View File
@@ -0,0 +1,5 @@
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
// Package grpc contains implementation of Auth service gRPC API.
package grpc
+127
View File
@@ -0,0 +1,127 @@
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
package grpc
import (
"context"
"time"
"github.com/go-kit/kit/endpoint"
"github.com/mainflux/mainflux/auth"
)
func issueEndpoint(svc auth.Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
req := request.(issueReq)
if err := req.validate(); err != nil {
return issueRes{}, err
}
key := auth.Key{
Type: req.keyType,
Subject: req.email,
IssuerID: req.id,
IssuedAt: time.Now().UTC(),
}
_, secret, err := svc.Issue(ctx, "", key)
if err != nil {
return issueRes{}, err
}
return issueRes{secret}, nil
}
}
func identifyEndpoint(svc auth.Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
req := request.(identityReq)
if err := req.validate(); err != nil {
return identityRes{}, err
}
id, err := svc.Identify(ctx, req.token)
if err != nil {
return identityRes{}, err
}
ret := identityRes{
id: id.ID,
email: id.Email,
}
return ret, nil
}
}
func authorizeEndpoint(svc auth.Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
req := request.(authReq)
if err := req.validate(); err != nil {
return authorizeRes{}, err
}
_, err := svc.Identify(ctx, req.token)
if err != nil {
return authorizeRes{}, err
}
authorized, err := svc.Authorize(ctx, req.token, req.Sub, req.Obj, req.Obj)
if err != nil {
return authorizeRes{}, err
}
return authorizeRes{authorized: authorized}, err
}
}
func assignEndpoint(svc auth.Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
req := request.(assignReq)
if err := req.validate(); err != nil {
return emptyRes{}, err
}
_, err := svc.Identify(ctx, req.token)
if err != nil {
return emptyRes{}, err
}
err = svc.Assign(ctx, req.token, req.memberID, req.groupID, req.groupType)
if err != nil {
return emptyRes{}, err
}
return emptyRes{}, nil
}
}
func membersEndpoint(svc auth.Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
req := request.(membersReq)
if err := req.validate(); err != nil {
return membersRes{}, err
}
pm := auth.PageMetadata{
Offset: req.offset,
Limit: req.limit,
}
mp, err := svc.ListMembers(ctx, req.token, req.groupID, req.memberType, pm)
if err != nil {
return membersRes{}, err
}
var members []string
for _, m := range mp.Members {
members = append(members, m.ID)
}
return membersRes{
offset: req.offset,
limit: req.limit,
total: mp.PageMetadata.Total,
members: members,
}, nil
}
}
+262
View File
@@ -0,0 +1,262 @@
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
package grpc_test
import (
"context"
"fmt"
"net"
"testing"
"time"
"github.com/mainflux/mainflux"
"github.com/mainflux/mainflux/auth"
grpcapi "github.com/mainflux/mainflux/auth/api/grpc"
"github.com/mainflux/mainflux/auth/jwt"
"github.com/mainflux/mainflux/auth/mocks"
"github.com/mainflux/mainflux/pkg/uuid"
"github.com/opentracing/opentracing-go/mocktracer"
"github.com/stretchr/testify/assert"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
const (
port = 8081
secret = "secret"
email = "test@example.com"
id = "testID"
thingsType = "things"
usersType = "users"
description = "Description"
numOfThings = 5
numOfUsers = 5
)
var svc auth.Service
func newService() auth.Service {
repo := mocks.NewKeyRepository()
groupRepo := mocks.NewGroupRepository()
idProvider := uuid.NewMock()
t := jwt.New(secret)
return auth.New(repo, groupRepo, idProvider, t)
}
func startGRPCServer(svc auth.Service, port int) {
listener, _ := net.Listen("tcp", fmt.Sprintf(":%d", port))
server := grpc.NewServer()
mainflux.RegisterAuthServiceServer(server, grpcapi.NewServer(mocktracer.New(), svc))
go server.Serve(listener)
}
func TestIssue(t *testing.T) {
authAddr := fmt.Sprintf("localhost:%d", port)
conn, _ := grpc.Dial(authAddr, grpc.WithInsecure())
client := grpcapi.NewClient(mocktracer.New(), conn, time.Second)
cases := []struct {
desc string
id string
email string
kind uint32
err error
code codes.Code
}{
{
desc: "issue for user with valid token",
id: id,
email: email,
kind: auth.UserKey,
err: nil,
code: codes.OK,
},
{
desc: "issue recovery key",
id: id,
email: email,
kind: auth.RecoveryKey,
err: nil,
code: codes.OK,
},
{
desc: "issue API key unauthenticated",
id: id,
email: email,
kind: auth.APIKey,
err: nil,
code: codes.Unauthenticated,
},
{
desc: "issue for invalid key type",
id: id,
email: email,
kind: 32,
err: status.Error(codes.InvalidArgument, "received invalid token request"),
code: codes.InvalidArgument,
},
{
desc: "issue for user that exist",
id: "",
kind: auth.APIKey,
err: status.Error(codes.Unauthenticated, "unauthorized access"),
code: codes.Unauthenticated,
},
}
for _, tc := range cases {
_, err := client.Issue(context.Background(), &mainflux.IssueReq{Id: tc.id, Email: tc.email, Type: tc.kind})
e, ok := status.FromError(err)
assert.True(t, ok, "gRPC status can't be extracted from the error")
assert.Equal(t, tc.code, e.Code(), fmt.Sprintf("%s: expected %s got %s", tc.desc, tc.code, e.Code()))
}
}
func TestIdentify(t *testing.T) {
_, loginSecret, err := svc.Issue(context.Background(), "", auth.Key{Type: auth.UserKey, IssuedAt: time.Now(), IssuerID: id, Subject: email})
assert.Nil(t, err, fmt.Sprintf("Issuing user key expected to succeed: %s", err))
_, recoverySecret, err := svc.Issue(context.Background(), "", auth.Key{Type: auth.RecoveryKey, IssuedAt: time.Now(), IssuerID: id, Subject: email})
assert.Nil(t, err, fmt.Sprintf("Issuing recovery key expected to succeed: %s", err))
_, apiSecret, err := svc.Issue(context.Background(), loginSecret, auth.Key{Type: auth.APIKey, IssuedAt: time.Now(), ExpiresAt: time.Now().Add(time.Minute), IssuerID: id, Subject: email})
assert.Nil(t, err, fmt.Sprintf("Issuing API key expected to succeed: %s", err))
authAddr := fmt.Sprintf("localhost:%d", port)
conn, _ := grpc.Dial(authAddr, grpc.WithInsecure())
client := grpcapi.NewClient(mocktracer.New(), conn, time.Second)
cases := []struct {
desc string
token string
idt mainflux.UserIdentity
err error
code codes.Code
}{
{
desc: "identify user with user token",
token: loginSecret,
idt: mainflux.UserIdentity{Email: email, Id: id},
err: nil,
code: codes.OK,
},
{
desc: "identify user with recovery token",
token: recoverySecret,
idt: mainflux.UserIdentity{Email: email, Id: id},
err: nil,
code: codes.OK,
},
{
desc: "identify user with API token",
token: apiSecret,
idt: mainflux.UserIdentity{Email: email, Id: id},
err: nil,
code: codes.OK,
},
{
desc: "identify user with invalid user token",
token: "invalid",
idt: mainflux.UserIdentity{},
err: status.Error(codes.Unauthenticated, "unauthorized access"),
code: codes.Unauthenticated,
},
{
desc: "identify user that doesn't exist",
token: "",
idt: mainflux.UserIdentity{},
err: status.Error(codes.InvalidArgument, "received invalid token request"),
code: codes.InvalidArgument,
},
}
for _, tc := range cases {
idt, err := client.Identify(context.Background(), &mainflux.Token{Value: tc.token})
if idt != nil {
assert.Equal(t, tc.idt, *idt, fmt.Sprintf("%s: expected %v got %v", tc.desc, tc.idt, *idt))
}
e, ok := status.FromError(err)
assert.True(t, ok, "gRPC status can't be extracted from the error")
assert.Equal(t, tc.code, e.Code(), fmt.Sprintf("%s: expected %s got %s", tc.desc, tc.code, e.Code()))
}
}
func TestMembers(t *testing.T) {
_, token, err := svc.Issue(context.Background(), "", auth.Key{Type: auth.UserKey, IssuedAt: time.Now(), IssuerID: id, Subject: email})
assert.Nil(t, err, fmt.Sprintf("Issuing user key expected to succeed: %s", err))
group := auth.Group{
Name: "Mainflux",
Description: description,
}
var things []string
for i := 0; i < numOfThings; i++ {
id, err := uuid.New().ID()
assert.Nil(t, err, fmt.Sprintf("Generate thing id expected to succeed: %s", err))
things = append(things, id)
}
var users []string
for i := 0; i < numOfUsers; i++ {
id, err := uuid.New().ID()
assert.Nil(t, err, fmt.Sprintf("Generate thing id expected to succeed: %s", err))
users = append(users, id)
}
group, err = svc.CreateGroup(context.Background(), token, group)
assert.Nil(t, err, fmt.Sprintf("Creating group expected to succeed: %s", err))
err = svc.Assign(context.Background(), token, group.ID, thingsType, things...)
assert.Nil(t, err, fmt.Sprintf("Assign members to expected to succeed: %s", err))
err = svc.Assign(context.Background(), token, group.ID, usersType, users...)
assert.Nil(t, err, fmt.Sprintf("Assign members to group expected to succeed: %s", err))
cases := []struct {
desc string
token string
groupID string
groupType string
size int
err error
code codes.Code
}{
{
desc: "get all things with user token",
groupID: group.ID,
token: token,
groupType: thingsType,
size: numOfThings,
err: nil,
code: codes.OK,
},
{
desc: "get all users with user token",
groupID: group.ID,
token: token,
groupType: usersType,
size: numOfUsers,
err: nil,
code: codes.OK,
},
}
authAddr := fmt.Sprintf("localhost:%d", port)
conn, _ := grpc.Dial(authAddr, grpc.WithInsecure())
client := grpcapi.NewClient(mocktracer.New(), conn, time.Second)
for _, tc := range cases {
m, err := client.Members(context.Background(), &mainflux.MembersReq{Token: tc.token, GroupID: tc.groupID, Type: tc.groupType, Offset: 0, Limit: 10})
e, ok := status.FromError(err)
assert.Equal(t, tc.size, len(m.Members), fmt.Sprintf("%s: expected %d got %d", tc.desc, tc.size, len(m.Members)))
assert.Equal(t, tc.code, e.Code(), fmt.Sprintf("%s: expected %s got %s", tc.desc, tc.code, e.Code()))
assert.True(t, ok, "OK expected to be true")
}
}
+114
View File
@@ -0,0 +1,114 @@
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
package grpc
import (
"github.com/mainflux/mainflux/auth"
)
type identityReq struct {
token string
kind uint32
}
func (req identityReq) validate() error {
if req.token == "" {
return auth.ErrMalformedEntity
}
if req.kind != auth.UserKey &&
req.kind != auth.APIKey &&
req.kind != auth.RecoveryKey {
return auth.ErrMalformedEntity
}
return nil
}
type issueReq struct {
id string
email string
keyType uint32
}
func (req issueReq) validate() error {
if req.email == "" {
return auth.ErrUnauthorizedAccess
}
if req.keyType != auth.UserKey &&
req.keyType != auth.APIKey &&
req.keyType != auth.RecoveryKey {
return auth.ErrMalformedEntity
}
return nil
}
type assignReq struct {
token string
groupID string
memberID string
groupType string
}
func (req assignReq) validate() error {
if req.token == "" {
return auth.ErrUnauthorizedAccess
}
if req.groupID == "" || req.memberID == "" {
return auth.ErrMalformedEntity
}
return nil
}
type membersReq struct {
token string
groupID string
offset uint64
limit uint64
memberType string
}
func (req membersReq) validate() error {
if req.token == "" {
return auth.ErrUnauthorizedAccess
}
if req.groupID == "" {
return auth.ErrMalformedEntity
}
if req.memberType == "" {
return auth.ErrMalformedEntity
}
return nil
}
// authReq represents authorization request. It contains:
// 1. subject - an action invoker
// 2. object - an entity over which action will be executed
// 3. action - type of action that will be executed (read/write)
type authReq struct {
token string
Sub string
Obj string
Act string
}
func (req authReq) validate() error {
if req.token == "" {
return auth.ErrMalformedEntity
}
if req.Sub == "" {
return auth.ErrMalformedEntity
}
if req.Obj == "" {
return auth.ErrMalformedEntity
}
if req.Act == "" {
return auth.ErrMalformedEntity
}
return nil
}
+27
View File
@@ -0,0 +1,27 @@
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
package grpc
type identityRes struct {
id string
email string
}
type issueRes struct {
value string
}
type authorizeRes struct {
authorized bool
}
type membersRes struct {
total uint64
offset uint64
limit uint64
groupType string
members []string
}
type emptyRes struct {
err error
}
+176
View File
@@ -0,0 +1,176 @@
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
package grpc
import (
"context"
kitot "github.com/go-kit/kit/tracing/opentracing"
kitgrpc "github.com/go-kit/kit/transport/grpc"
"github.com/golang/protobuf/ptypes/empty"
mainflux "github.com/mainflux/mainflux"
"github.com/mainflux/mainflux/auth"
"github.com/mainflux/mainflux/pkg/errors"
opentracing "github.com/opentracing/opentracing-go"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
var _ mainflux.AuthServiceServer = (*grpcServer)(nil)
type grpcServer struct {
issue kitgrpc.Handler
identify kitgrpc.Handler
authorize kitgrpc.Handler
assign kitgrpc.Handler
members kitgrpc.Handler
}
// NewServer returns new AuthServiceServer instance.
func NewServer(tracer opentracing.Tracer, svc auth.Service) mainflux.AuthServiceServer {
return &grpcServer{
issue: kitgrpc.NewServer(
kitot.TraceServer(tracer, "issue")(issueEndpoint(svc)),
decodeIssueRequest,
encodeIssueResponse,
),
identify: kitgrpc.NewServer(
kitot.TraceServer(tracer, "identify")(identifyEndpoint(svc)),
decodeIdentifyRequest,
encodeIdentifyResponse,
),
authorize: kitgrpc.NewServer(
kitot.TraceServer(tracer, "authorize")(authorizeEndpoint(svc)),
decodeAuthorizeRequest,
encodeAuthorizeResponse,
),
assign: kitgrpc.NewServer(
kitot.TraceServer(tracer, "assign")(assignEndpoint(svc)),
decodeAssignRequest,
encodeEmptyResponse,
),
members: kitgrpc.NewServer(
kitot.TraceServer(tracer, "members")(membersEndpoint(svc)),
decodeMembersRequest,
encodeMembersResponse,
),
}
}
func (s *grpcServer) Issue(ctx context.Context, req *mainflux.IssueReq) (*mainflux.Token, error) {
_, res, err := s.issue.ServeGRPC(ctx, req)
if err != nil {
return nil, encodeError(err)
}
return res.(*mainflux.Token), nil
}
func (s *grpcServer) Identify(ctx context.Context, token *mainflux.Token) (*mainflux.UserIdentity, error) {
_, res, err := s.identify.ServeGRPC(ctx, token)
if err != nil {
return nil, encodeError(err)
}
return res.(*mainflux.UserIdentity), nil
}
func (s *grpcServer) Authorize(ctx context.Context, token *mainflux.AuthorizeReq) (*mainflux.AuthorizeRes, error) {
_, res, err := s.authorize.ServeGRPC(ctx, token)
if err != nil {
return nil, encodeError(err)
}
return res.(*mainflux.AuthorizeRes), nil
}
func (s *grpcServer) Assign(ctx context.Context, token *mainflux.Assignment) (*empty.Empty, error) {
_, res, err := s.assign.ServeGRPC(ctx, token)
if err != nil {
return nil, encodeError(err)
}
return res.(*empty.Empty), nil
}
func (s *grpcServer) Members(ctx context.Context, req *mainflux.MembersReq) (*mainflux.MembersRes, error) {
_, res, err := s.members.ServeGRPC(ctx, req)
if err != nil {
return nil, encodeError(err)
}
return res.(*mainflux.MembersRes), nil
}
func decodeIssueRequest(_ context.Context, grpcReq interface{}) (interface{}, error) {
req := grpcReq.(*mainflux.IssueReq)
return issueReq{id: req.GetId(), email: req.GetEmail(), keyType: req.GetType()}, nil
}
func encodeIssueResponse(_ context.Context, grpcRes interface{}) (interface{}, error) {
res := grpcRes.(issueRes)
return &mainflux.Token{Value: res.value}, nil
}
func decodeIdentifyRequest(_ context.Context, grpcReq interface{}) (interface{}, error) {
req := grpcReq.(*mainflux.Token)
return identityReq{token: req.GetValue()}, nil
}
func encodeIdentifyResponse(_ context.Context, grpcRes interface{}) (interface{}, error) {
res := grpcRes.(identityRes)
return &mainflux.UserIdentity{Id: res.id, Email: res.email}, nil
}
func decodeAuthorizeRequest(_ context.Context, grpcReq interface{}) (interface{}, error) {
req := grpcReq.(*mainflux.AuthorizeReq)
return authReq{Act: req.Act, Obj: req.Obj, Sub: req.Sub}, nil
}
func encodeAuthorizeResponse(_ context.Context, grpcRes interface{}) (interface{}, error) {
res := grpcRes.(*mainflux.AuthorizeRes)
return res, nil
}
func decodeAssignRequest(_ context.Context, grpcReq interface{}) (interface{}, error) {
req := grpcReq.(*mainflux.Token)
return assignReq{token: req.GetValue()}, nil
}
func decodeMembersRequest(_ context.Context, grpcReq interface{}) (interface{}, error) {
req := grpcReq.(*mainflux.MembersReq)
return membersReq{
token: req.GetToken(),
groupID: req.GetGroupID(),
memberType: req.GetType(),
offset: req.Offset,
limit: req.Limit,
}, nil
}
func encodeMembersResponse(_ context.Context, grpcRes interface{}) (interface{}, error) {
res := grpcRes.(membersRes)
return &mainflux.MembersRes{
Total: res.total,
Offset: res.offset,
Limit: res.limit,
Type: res.groupType,
Members: res.members,
}, nil
}
func encodeEmptyResponse(_ context.Context, grpcRes interface{}) (interface{}, error) {
res := grpcRes.(emptyRes)
return &empty.Empty{}, encodeError(res.err)
}
func encodeError(err error) error {
switch {
case errors.Contains(err, nil):
return nil
case errors.Contains(err, auth.ErrMalformedEntity):
return status.Error(codes.InvalidArgument, "received invalid token request")
case errors.Contains(err, auth.ErrUnauthorizedAccess):
return status.Error(codes.Unauthenticated, err.Error())
case errors.Contains(err, auth.ErrKeyExpired):
return status.Error(codes.Unauthenticated, err.Error())
default:
return status.Error(codes.Internal, "internal server error")
}
}
+18
View File
@@ -0,0 +1,18 @@
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
package grpc_test
import (
"os"
"testing"
)
func TestMain(m *testing.M) {
svc = newService()
startGRPCServer(svc, port)
code := m.Run()
os.Exit(code)
}
+3
View File
@@ -0,0 +1,3 @@
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
package http
+359
View File
@@ -0,0 +1,359 @@
package groups
import (
"context"
"github.com/go-kit/kit/endpoint"
"github.com/mainflux/mainflux/auth"
)
func createGroupEndpoint(svc auth.Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
req := request.(createGroupReq)
if err := req.validate(); err != nil {
return groupRes{}, err
}
group := auth.Group{
Name: req.Name,
Description: req.Description,
ParentID: req.ParentID,
Metadata: req.Metadata,
}
group, err := svc.CreateGroup(ctx, req.token, group)
if err != nil {
return groupRes{}, err
}
return groupRes{created: true, id: group.ID}, nil
}
}
func viewGroupEndpoint(svc auth.Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
req := request.(groupReq)
if err := req.validate(); err != nil {
return viewGroupRes{}, err
}
group, err := svc.ViewGroup(ctx, req.token, req.id)
if err != nil {
return viewGroupRes{}, err
}
res := viewGroupRes{
ID: group.ID,
Name: group.Name,
Description: group.Description,
Metadata: group.Metadata,
ParentID: group.ParentID,
OwnerID: group.OwnerID,
CreatedAt: group.CreatedAt,
UpdatedAt: group.UpdatedAt,
}
return res, nil
}
}
func updateGroupEndpoint(svc auth.Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
req := request.(updateGroupReq)
if err := req.validate(); err != nil {
return groupRes{}, err
}
group := auth.Group{
ID: req.id,
Name: req.Name,
Description: req.Description,
Metadata: req.Metadata,
}
_, err := svc.UpdateGroup(ctx, req.token, group)
if err != nil {
return groupRes{}, err
}
res := groupRes{created: false}
return res, nil
}
}
func deleteGroupEndpoint(svc auth.Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
req := request.(groupReq)
if err := req.validate(); err != nil {
return nil, err
}
if err := svc.RemoveGroup(ctx, req.token, req.id); err != nil {
return nil, err
}
return deleteRes{}, nil
}
}
func listGroupsEndpoint(svc auth.Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
req := request.(listGroupsReq)
if err := req.validate(); err != nil {
return groupPageRes{}, err
}
pm := auth.PageMetadata{
Level: req.level,
Metadata: req.metadata,
}
page, err := svc.ListGroups(ctx, req.token, pm)
if err != nil {
return groupPageRes{}, err
}
if req.tree {
return buildGroupsResponseTree(page), nil
}
return buildGroupsResponse(page), nil
}
}
func listMemberships(svc auth.Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
req := request.(listMembershipsReq)
if err := req.validate(); err != nil {
return memberPageRes{}, err
}
pm := auth.PageMetadata{
Offset: req.offset,
Limit: req.limit,
Metadata: req.metadata,
}
page, err := svc.ListMemberships(ctx, req.token, req.id, pm)
if err != nil {
return memberPageRes{}, err
}
if req.tree {
return buildGroupsResponseTree(page), nil
}
return buildGroupsResponse(page), nil
}
}
func listChildrenEndpoint(svc auth.Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
req := request.(listGroupsReq)
if err := req.validate(); err != nil {
return groupPageRes{}, err
}
pm := auth.PageMetadata{
Level: req.level,
Metadata: req.metadata,
}
page, err := svc.ListChildren(ctx, req.token, req.id, pm)
if err != nil {
return groupPageRes{}, err
}
if req.tree {
return buildGroupsResponseTree(page), nil
}
return buildGroupsResponse(page), nil
}
}
func listParentsEndpoint(svc auth.Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
req := request.(listGroupsReq)
if err := req.validate(); err != nil {
return groupPageRes{}, err
}
pm := auth.PageMetadata{
Level: req.level,
Metadata: req.metadata,
}
page, err := svc.ListParents(ctx, req.token, req.id, pm)
if err != nil {
return groupPageRes{}, err
}
if req.tree {
return buildGroupsResponseTree(page), nil
}
return buildGroupsResponse(page), nil
}
}
func assignEndpoint(svc auth.Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
req := request.(assignReq)
if err := req.validate(); err != nil {
return nil, err
}
if err := svc.Assign(ctx, req.token, req.groupID, req.Type, req.Members...); err != nil {
return nil, err
}
return assignRes{}, nil
}
}
func unassignEndpoint(svc auth.Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
req := request.(unassignReq)
if err := req.validate(); err != nil {
return nil, err
}
if err := svc.Unassign(ctx, req.token, req.groupID, req.Members...); err != nil {
return nil, err
}
return unassignRes{}, nil
}
}
func listMembersEndpoint(svc auth.Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
req := request.(listMembersReq)
if err := req.validate(); err != nil {
return memberPageRes{}, err
}
pm := auth.PageMetadata{
Offset: req.offset,
Limit: req.limit,
Metadata: req.metadata,
}
page, err := svc.ListMembers(ctx, req.token, req.id, req.groupType, pm)
if err != nil {
return memberPageRes{}, err
}
return buildUsersResponse(page), nil
}
}
func buildGroupsResponseTree(page auth.GroupPage) groupPageRes {
groupsMap := map[string]*auth.Group{}
// Parents' map keeps its array of children.
parentsMap := map[string][]*auth.Group{}
for i := range page.Groups {
if _, ok := groupsMap[page.Groups[i].ID]; !ok {
groupsMap[page.Groups[i].ID] = &page.Groups[i]
parentsMap[page.Groups[i].ID] = make([]*auth.Group, 0)
}
}
for _, group := range groupsMap {
if children, ok := parentsMap[group.ParentID]; ok {
children = append(children, group)
parentsMap[group.ParentID] = children
}
}
res := groupPageRes{
pageRes: pageRes{
Limit: page.Limit,
Offset: page.Offset,
Total: page.Total,
Level: page.Level,
},
Groups: []viewGroupRes{},
}
for _, group := range groupsMap {
if children, ok := parentsMap[group.ID]; ok {
group.Children = children
}
}
for _, group := range groupsMap {
view := toViewGroupRes(*group)
if children, ok := parentsMap[group.ParentID]; len(children) == 0 || !ok {
res.Groups = append(res.Groups, view)
}
}
return res
}
func toViewGroupRes(group auth.Group) viewGroupRes {
view := viewGroupRes{
ID: group.ID,
ParentID: group.ParentID,
OwnerID: group.OwnerID,
Name: group.Name,
Description: group.Description,
Metadata: group.Metadata,
Level: group.Level,
Path: group.Path,
Children: make([]*viewGroupRes, 0),
CreatedAt: group.CreatedAt,
UpdatedAt: group.UpdatedAt,
}
for _, ch := range group.Children {
child := toViewGroupRes(*ch)
view.Children = append(view.Children, &child)
}
return view
}
func buildGroupsResponse(gp auth.GroupPage) groupPageRes {
res := groupPageRes{
pageRes: pageRes{
Total: gp.Total,
Level: gp.Level,
},
Groups: []viewGroupRes{},
}
for _, group := range gp.Groups {
view := viewGroupRes{
ID: group.ID,
ParentID: group.ParentID,
OwnerID: group.OwnerID,
Name: group.Name,
Description: group.Description,
Metadata: group.Metadata,
Level: group.Level,
Path: group.Path,
CreatedAt: group.CreatedAt,
UpdatedAt: group.UpdatedAt,
}
res.Groups = append(res.Groups, view)
}
return res
}
func buildUsersResponse(mp auth.MemberPage) memberPageRes {
res := memberPageRes{
pageRes: pageRes{
Total: mp.Total,
Offset: mp.Offset,
Limit: mp.Limit,
Name: mp.Name,
},
Members: []interface{}{},
}
for _, m := range mp.Members {
res.Members = append(res.Members, m)
}
return res
}
+162
View File
@@ -0,0 +1,162 @@
package groups
import (
"github.com/mainflux/mainflux/auth"
"github.com/mainflux/mainflux/pkg/errors"
)
type createGroupReq struct {
token string
Name string `json:"name,omitempty"`
ParentID string `json:"parent_id,omitempty"`
Description string `json:"description,omitempty"`
Metadata map[string]interface{} `json:"metadata,omitempty"`
}
func (req createGroupReq) validate() error {
if req.token == "" {
return auth.ErrUnauthorizedAccess
}
if len(req.Name) > maxNameSize || req.Name == "" {
return errors.Wrap(auth.ErrMalformedEntity, auth.ErrBadGroupName)
}
return nil
}
type updateGroupReq struct {
token string
id string
Name string `json:"name,omitempty"`
Description string `json:"description,omitempty"`
Metadata map[string]interface{} `json:"metadata,omitempty"`
}
func (req updateGroupReq) validate() error {
if req.token == "" {
return auth.ErrUnauthorizedAccess
}
if req.id == "" {
return auth.ErrMalformedEntity
}
return nil
}
type listGroupsReq struct {
token string
id string
level uint64
// - `true` - result is JSON tree representing groups hierarchy,
// - `false` - result is JSON array of groups.
tree bool
metadata auth.GroupMetadata
}
func (req listGroupsReq) validate() error {
if req.token == "" {
return auth.ErrUnauthorizedAccess
}
if req.level > auth.MaxLevel || req.level < auth.MinLevel {
return auth.ErrMaxLevelExceeded
}
return nil
}
type listMembersReq struct {
token string
id string
groupType string
offset uint64
limit uint64
tree bool
metadata auth.GroupMetadata
}
func (req listMembersReq) validate() error {
if req.token == "" {
return auth.ErrUnauthorizedAccess
}
if req.id == "" {
return auth.ErrMalformedEntity
}
return nil
}
type listMembershipsReq struct {
token string
id string
offset uint64
limit uint64
tree bool
metadata auth.GroupMetadata
}
func (req listMembershipsReq) validate() error {
if req.token == "" {
return auth.ErrUnauthorizedAccess
}
if req.id == "" {
return auth.ErrMalformedEntity
}
return nil
}
type assignReq struct {
token string
groupID string
Type string `json:"type,omitempty"`
Members []string `json:"members"`
}
func (req assignReq) validate() error {
if req.token == "" {
return auth.ErrUnauthorizedAccess
}
if req.Type == "" || req.groupID == "" || len(req.Members) == 0 {
return auth.ErrMalformedEntity
}
return nil
}
type unassignReq struct {
assignReq
}
func (req unassignReq) validate() error {
if req.token == "" {
return auth.ErrUnauthorizedAccess
}
if req.groupID == "" || len(req.Members) == 0 {
return auth.ErrMalformedEntity
}
return nil
}
type groupReq struct {
token string
id string
}
func (req groupReq) validate() error {
if req.token == "" {
return auth.ErrUnauthorizedAccess
}
if req.id == "" {
return auth.ErrMalformedEntity
}
return nil
}
+162
View File
@@ -0,0 +1,162 @@
package groups
import (
"fmt"
"net/http"
"time"
"github.com/mainflux/mainflux"
)
var (
_ mainflux.Response = (*memberPageRes)(nil)
_ mainflux.Response = (*groupRes)(nil)
_ mainflux.Response = (*deleteRes)(nil)
_ mainflux.Response = (*assignRes)(nil)
_ mainflux.Response = (*unassignRes)(nil)
)
type memberPageRes struct {
pageRes
Members []interface{}
}
func (res memberPageRes) Code() int {
return http.StatusOK
}
func (res memberPageRes) Headers() map[string]string {
return map[string]string{}
}
func (res memberPageRes) Empty() bool {
return false
}
type viewGroupRes struct {
ID string `json:"id"`
Name string `json:"name"`
OwnerID string `json:"owner_id"`
ParentID string `json:"parent_id,omitempty"`
Description string `json:"description,omitempty"`
Metadata map[string]interface{} `json:"metadata,omitempty"`
// Indicates a level in tree hierarchy from first group node - root.
Level int `json:"level"`
// Path in a tree consisting of group ids
// parentID1.parentID2.childID1
// e.g. 01EXPM5Z8HRGFAEWTETR1X1441.01EXPKW2TVK74S5NWQ979VJ4PJ.01EXPKW2TVK74S5NWQ979VJ4PJ
Path string `json:"path"`
Children []*viewGroupRes `json:"children,omitempty"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
func (res viewGroupRes) Code() int {
return http.StatusOK
}
func (res viewGroupRes) Headers() map[string]string {
return map[string]string{}
}
func (res viewGroupRes) Empty() bool {
return false
}
type groupRes struct {
id string
created bool
}
func (res groupRes) Code() int {
if res.created {
return http.StatusCreated
}
return http.StatusOK
}
func (res groupRes) Headers() map[string]string {
if res.created {
return map[string]string{
"Location": fmt.Sprintf("/groups/%s", res.id),
}
}
return map[string]string{}
}
func (res groupRes) Empty() bool {
return true
}
type groupPageRes struct {
pageRes
Groups []viewGroupRes `json:"groups"`
}
type pageRes struct {
Limit uint64 `json:"limit,omitempty"`
Offset uint64 `json:"offset,omitempty"`
Total uint64 `json:"total"`
Level uint64 `json:"level"`
Name string `json:"name"`
}
func (res groupPageRes) Code() int {
return http.StatusOK
}
func (res groupPageRes) Headers() map[string]string {
return map[string]string{}
}
func (res groupPageRes) Empty() bool {
return false
}
type deleteRes struct{}
func (res deleteRes) Code() int {
return http.StatusNoContent
}
func (res deleteRes) Headers() map[string]string {
return map[string]string{}
}
func (res deleteRes) Empty() bool {
return true
}
type assignRes struct{}
func (res assignRes) Code() int {
return http.StatusOK
}
func (res assignRes) Headers() map[string]string {
return map[string]string{}
}
func (res assignRes) Empty() bool {
return true
}
type unassignRes struct{}
func (res unassignRes) Code() int {
return http.StatusNoContent
}
func (res unassignRes) Headers() map[string]string {
return map[string]string{}
}
func (res unassignRes) Empty() bool {
return true
}
type errorRes struct {
Err string `json:"error"`
}
+346
View File
@@ -0,0 +1,346 @@
package groups
import (
"context"
"encoding/json"
"io"
"net/http"
"strings"
kitot "github.com/go-kit/kit/tracing/opentracing"
kithttp "github.com/go-kit/kit/transport/http"
"github.com/go-zoo/bone"
"github.com/mainflux/mainflux"
"github.com/mainflux/mainflux/auth"
"github.com/mainflux/mainflux/internal/httputil"
"github.com/mainflux/mainflux/pkg/errors"
"github.com/opentracing/opentracing-go"
)
var (
errInvalidQueryParams = errors.New("invalid query params")
errUnsupportedContentType = errors.New("unsupported content type")
)
const (
contentType = "application/json"
maxNameSize = 254
offsetKey = "offset"
limitKey = "limit"
levelKey = "level"
metadataKey = "metadata"
treeKey = "tree"
groupType = "type"
defOffset = 0
defLimit = 10
defLevel = 1
)
// MakeHandler returns a HTTP handler for API endpoints.
func MakeHandler(svc auth.Service, mux *bone.Mux, tracer opentracing.Tracer) *bone.Mux {
opts := []kithttp.ServerOption{
kithttp.ServerErrorEncoder(encodeError),
}
mux.Post("/groups", kithttp.NewServer(
kitot.TraceServer(tracer, "create_group")(createGroupEndpoint(svc)),
decodeGroupCreate,
encodeResponse,
opts...,
))
mux.Get("/groups/:groupID", kithttp.NewServer(
kitot.TraceServer(tracer, "view_group")(viewGroupEndpoint(svc)),
decodeGroupRequest,
encodeResponse,
opts...,
))
mux.Put("/groups/:groupID", kithttp.NewServer(
kitot.TraceServer(tracer, "update_group")(updateGroupEndpoint(svc)),
decodeGroupUpdate,
encodeResponse,
opts...,
))
mux.Delete("/groups/:groupID", kithttp.NewServer(
kitot.TraceServer(tracer, "delete_group")(deleteGroupEndpoint(svc)),
decodeGroupRequest,
encodeResponse,
opts...,
))
mux.Get("/groups", kithttp.NewServer(
kitot.TraceServer(tracer, "list_groups")(listGroupsEndpoint(svc)),
decodeListGroupsRequest,
encodeResponse,
opts...,
))
mux.Get("/groups/:groupID/children", kithttp.NewServer(
kitot.TraceServer(tracer, "list_children")(listChildrenEndpoint(svc)),
decodeListGroupsRequest,
encodeResponse,
opts...,
))
mux.Get("/groups/:groupID/parents", kithttp.NewServer(
kitot.TraceServer(tracer, "list_parents_groups")(listParentsEndpoint(svc)),
decodeListGroupsRequest,
encodeResponse,
opts...,
))
mux.Post("/groups/:groupID/members", kithttp.NewServer(
kitot.TraceServer(tracer, "assign")(assignEndpoint(svc)),
decodeAssignRequest,
encodeResponse,
opts...,
))
mux.Delete("/groups/:groupID/members", kithttp.NewServer(
kitot.TraceServer(tracer, "unassign")(unassignEndpoint(svc)),
decodeUnassignRequest,
encodeResponse,
opts...,
))
mux.Get("/groups/:groupID/members", kithttp.NewServer(
kitot.TraceServer(tracer, "list_members")(listMembersEndpoint(svc)),
decodeListMembersRequest,
encodeResponse,
opts...,
))
mux.Get("/members/:memberID/groups", kithttp.NewServer(
kitot.TraceServer(tracer, "list_memberships")(listMemberships(svc)),
decodeListMembershipsRequest,
encodeResponse,
opts...,
))
return mux
}
func decodeListGroupsRequest(_ context.Context, r *http.Request) (interface{}, error) {
if !strings.Contains(r.Header.Get("Content-Type"), contentType) {
return nil, auth.ErrUnsupportedContentType
}
l, err := httputil.ReadUintQuery(r, levelKey, defLevel)
if err != nil {
return nil, err
}
m, err := httputil.ReadMetadataQuery(r, metadataKey, nil)
if err != nil {
return nil, err
}
t, err := httputil.ReadBoolQuery(r, treeKey, false)
if err != nil {
return nil, err
}
req := listGroupsReq{
token: r.Header.Get("Authorization"),
level: l,
metadata: m,
tree: t,
id: bone.GetValue(r, "groupID"),
}
return req, nil
}
func decodeListMembersRequest(_ context.Context, r *http.Request) (interface{}, error) {
if !strings.Contains(r.Header.Get("Content-Type"), contentType) {
return nil, auth.ErrUnsupportedContentType
}
o, err := httputil.ReadUintQuery(r, offsetKey, defOffset)
if err != nil {
return nil, err
}
l, err := httputil.ReadUintQuery(r, limitKey, defLimit)
if err != nil {
return nil, err
}
m, err := httputil.ReadMetadataQuery(r, metadataKey, nil)
if err != nil {
return nil, err
}
tree, err := httputil.ReadBoolQuery(r, treeKey, false)
if err != nil {
return nil, err
}
t, err := httputil.ReadStringQuery(r, groupType, "")
if err != nil {
return nil, err
}
req := listMembersReq{
token: r.Header.Get("Authorization"),
id: bone.GetValue(r, "groupID"),
groupType: t,
offset: o,
limit: l,
metadata: m,
tree: tree,
}
return req, nil
}
func decodeListMembershipsRequest(_ context.Context, r *http.Request) (interface{}, error) {
if !strings.Contains(r.Header.Get("Content-Type"), contentType) {
return nil, auth.ErrUnsupportedContentType
}
o, err := httputil.ReadUintQuery(r, offsetKey, defOffset)
if err != nil {
return nil, err
}
l, err := httputil.ReadUintQuery(r, limitKey, defLimit)
if err != nil {
return nil, err
}
m, err := httputil.ReadMetadataQuery(r, metadataKey, nil)
if err != nil {
return nil, err
}
tree, err := httputil.ReadBoolQuery(r, treeKey, false)
if err != nil {
return nil, err
}
req := listMembershipsReq{
token: r.Header.Get("Authorization"),
id: bone.GetValue(r, "memberID"),
offset: o,
limit: l,
metadata: m,
tree: tree,
}
return req, nil
}
func decodeGroupCreate(_ context.Context, r *http.Request) (interface{}, error) {
if !strings.Contains(r.Header.Get("Content-Type"), contentType) {
return nil, auth.ErrUnsupportedContentType
}
var req createGroupReq
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
return nil, errors.Wrap(auth.ErrFailedDecode, err)
}
req.token = r.Header.Get("Authorization")
return req, nil
}
func decodeGroupUpdate(_ context.Context, r *http.Request) (interface{}, error) {
if !strings.Contains(r.Header.Get("Content-Type"), contentType) {
return nil, auth.ErrUnsupportedContentType
}
var req updateGroupReq
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
return nil, errors.Wrap(auth.ErrFailedDecode, err)
}
req.id = bone.GetValue(r, "groupID")
req.token = r.Header.Get("Authorization")
return req, nil
}
func decodeGroupRequest(_ context.Context, r *http.Request) (interface{}, error) {
req := groupReq{
token: r.Header.Get("Authorization"),
id: bone.GetValue(r, "groupID"),
}
return req, nil
}
func decodeAssignRequest(_ context.Context, r *http.Request) (interface{}, error) {
req := assignReq{
token: r.Header.Get("Authorization"),
groupID: bone.GetValue(r, "groupID"),
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
return nil, errors.Wrap(auth.ErrMalformedEntity, err)
}
return req, nil
}
func decodeUnassignRequest(_ context.Context, r *http.Request) (interface{}, error) {
req := unassignReq{
assignReq{
token: r.Header.Get("Authorization"),
groupID: bone.GetValue(r, "groupID"),
},
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
return nil, errors.Wrap(auth.ErrMalformedEntity, err)
}
return req, nil
}
func encodeResponse(_ context.Context, w http.ResponseWriter, response interface{}) error {
w.Header().Set("Content-Type", contentType)
if ar, ok := response.(mainflux.Response); ok {
for k, v := range ar.Headers() {
w.Header().Set(k, v)
}
w.WriteHeader(ar.Code())
if ar.Empty() {
return nil
}
}
return json.NewEncoder(w).Encode(response)
}
func encodeError(_ context.Context, err error, w http.ResponseWriter) {
switch {
case errors.Contains(err, auth.ErrMalformedEntity):
w.WriteHeader(http.StatusBadRequest)
case errors.Contains(err, auth.ErrUnauthorizedAccess):
w.WriteHeader(http.StatusForbidden)
case errors.Contains(err, auth.ErrNotFound):
w.WriteHeader(http.StatusNotFound)
case errors.Contains(err, auth.ErrConflict):
w.WriteHeader(http.StatusConflict)
case errors.Contains(err, auth.ErrMemberAlreadyAssigned):
w.WriteHeader(http.StatusConflict)
case errors.Contains(err, io.EOF):
w.WriteHeader(http.StatusBadRequest)
case errors.Contains(err, io.ErrUnexpectedEOF):
w.WriteHeader(http.StatusBadRequest)
case errors.Contains(err, errUnsupportedContentType):
w.WriteHeader(http.StatusUnsupportedMediaType)
default:
w.WriteHeader(http.StatusInternalServerError)
}
errorVal, ok := err.(errors.Error)
if ok {
if err := json.NewEncoder(w).Encode(errorRes{Err: errorVal.Msg()}); err != nil {
w.Header().Set("Content-Type", contentType)
w.WriteHeader(http.StatusInternalServerError)
}
}
}
+92
View File
@@ -0,0 +1,92 @@
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
package keys
import (
"context"
"time"
"github.com/go-kit/kit/endpoint"
"github.com/mainflux/mainflux/auth"
)
func issueEndpoint(svc auth.Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
req := request.(issueKeyReq)
if err := req.validate(); err != nil {
return nil, err
}
now := time.Now().UTC()
newKey := auth.Key{
IssuedAt: now,
Type: req.Type,
}
duration := time.Duration(req.Duration * time.Second)
if duration != 0 {
exp := now.Add(duration)
newKey.ExpiresAt = exp
}
key, secret, err := svc.Issue(ctx, req.token, newKey)
if err != nil {
return nil, err
}
res := issueKeyRes{
ID: key.ID,
Value: secret,
IssuedAt: key.IssuedAt,
}
if !key.ExpiresAt.IsZero() {
res.ExpiresAt = &key.ExpiresAt
}
return res, nil
}
}
func retrieveEndpoint(svc auth.Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
req := request.(keyReq)
if err := req.validate(); err != nil {
return nil, err
}
key, err := svc.RetrieveKey(ctx, req.token, req.id)
if err != nil {
return nil, err
}
ret := retrieveKeyRes{
ID: key.ID,
IssuerID: key.IssuerID,
Subject: key.Subject,
Type: key.Type,
IssuedAt: key.IssuedAt,
}
if !key.ExpiresAt.IsZero() {
ret.ExpiresAt = &key.ExpiresAt
}
return ret, nil
}
}
func revokeEndpoint(svc auth.Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
req := request.(keyReq)
if err := req.validate(); err != nil {
return nil, err
}
if err := svc.Revoke(ctx, req.token, req.id); err != nil {
return nil, err
}
return revokeKeyRes{}, nil
}
}
+289
View File
@@ -0,0 +1,289 @@
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
package keys_test
import (
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"net/http/httptest"
"strings"
"testing"
"time"
"github.com/mainflux/mainflux/auth"
httpapi "github.com/mainflux/mainflux/auth/api/http"
"github.com/mainflux/mainflux/auth/jwt"
"github.com/mainflux/mainflux/auth/mocks"
"github.com/mainflux/mainflux/pkg/uuid"
"github.com/opentracing/opentracing-go/mocktracer"
"github.com/stretchr/testify/assert"
)
const (
secret = "secret"
contentType = "application/json"
id = "123e4567-e89b-12d3-a456-000000000001"
email = "user@example.com"
)
type issueRequest struct {
Duration time.Duration `json:"duration,omitempty"`
Type uint32 `json:"type,omitempty"`
}
type testRequest struct {
client *http.Client
method string
url string
contentType string
token string
body io.Reader
}
func (tr testRequest) make() (*http.Response, error) {
req, err := http.NewRequest(tr.method, tr.url, tr.body)
if err != nil {
return nil, err
}
if tr.token != "" {
req.Header.Set("Authorization", tr.token)
}
if tr.contentType != "" {
req.Header.Set("Content-Type", tr.contentType)
}
req.Header.Set("Referer", "http://localhost")
return tr.client.Do(req)
}
func newService() auth.Service {
repo := mocks.NewKeyRepository()
groupRepo := mocks.NewGroupRepository()
idProvider := uuid.NewMock()
t := jwt.New(secret)
return auth.New(repo, groupRepo, idProvider, t)
}
func newServer(svc auth.Service) *httptest.Server {
mux := httpapi.MakeHandler(svc, mocktracer.New())
return httptest.NewServer(mux)
}
func toJSON(data interface{}) string {
jsonData, _ := json.Marshal(data)
return string(jsonData)
}
func TestIssue(t *testing.T) {
svc := newService()
_, loginSecret, err := svc.Issue(context.Background(), "", auth.Key{Type: auth.UserKey, IssuedAt: time.Now(), IssuerID: id, Subject: email})
assert.Nil(t, err, fmt.Sprintf("Issuing user key expected to succeed: %s", err))
ts := newServer(svc)
defer ts.Close()
client := ts.Client()
uk := issueRequest{Type: auth.UserKey}
ak := issueRequest{Type: auth.APIKey, Duration: time.Hour}
rk := issueRequest{Type: auth.RecoveryKey}
cases := []struct {
desc string
req string
ct string
token string
status int
}{
{
desc: "issue user key",
req: toJSON(uk),
ct: contentType,
token: "",
status: http.StatusCreated,
},
{
desc: "issue API key",
req: toJSON(ak),
ct: contentType,
token: loginSecret,
status: http.StatusCreated,
},
{
desc: "issue recovery key",
req: toJSON(rk),
ct: contentType,
token: loginSecret,
status: http.StatusBadRequest,
},
{
desc: "issue user key wrong content type",
req: toJSON(uk),
ct: "",
token: loginSecret,
status: http.StatusUnsupportedMediaType,
},
{
desc: "issue recovery key wrong content type",
req: toJSON(rk),
ct: "",
token: loginSecret,
status: http.StatusUnsupportedMediaType,
},
{
desc: "issue key unauthorized",
req: toJSON(ak),
ct: contentType,
token: "wrong",
status: http.StatusForbidden,
},
{
desc: "issue recovery key with empty token",
req: toJSON(rk),
ct: contentType,
token: "",
status: http.StatusBadRequest,
},
{
desc: "issue key with invalid request",
req: "{",
ct: contentType,
token: "",
status: http.StatusBadRequest,
},
{
desc: "issue key with invalid JSON",
req: "{invalid}",
ct: contentType,
token: "",
status: http.StatusBadRequest,
},
{
desc: "issue key with invalid JSON content",
req: `{"Type":{"key":"value"}}`,
ct: contentType,
token: "",
status: http.StatusBadRequest,
},
}
for _, tc := range cases {
req := testRequest{
client: client,
method: http.MethodPost,
url: fmt.Sprintf("%s/keys", ts.URL),
contentType: tc.ct,
token: tc.token,
body: strings.NewReader(tc.req),
}
res, err := req.make()
assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err))
assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode))
}
}
func TestRetrieve(t *testing.T) {
svc := newService()
_, loginSecret, err := svc.Issue(context.Background(), "", auth.Key{Type: auth.UserKey, IssuedAt: time.Now(), IssuerID: id, Subject: email})
assert.Nil(t, err, fmt.Sprintf("Issuing login key expected to succeed: %s", err))
key := auth.Key{Type: auth.APIKey, IssuedAt: time.Now(), IssuerID: id, Subject: email}
k, _, err := svc.Issue(context.Background(), loginSecret, key)
assert.Nil(t, err, fmt.Sprintf("Issuing login key expected to succeed: %s", err))
ts := newServer(svc)
defer ts.Close()
client := ts.Client()
cases := []struct {
desc string
id string
token string
status int
}{
{
desc: "retrieve an existing key",
id: k.ID,
token: loginSecret,
status: http.StatusOK,
},
{
desc: "retrieve a non-existing key",
id: "non-existing",
token: loginSecret,
status: http.StatusNotFound,
},
{
desc: "retrieve a key unauthorized",
id: k.ID,
token: "wrong",
status: http.StatusForbidden,
},
}
for _, tc := range cases {
req := testRequest{
client: client,
method: http.MethodGet,
url: fmt.Sprintf("%s/keys/%s", ts.URL, tc.id),
token: tc.token,
}
res, err := req.make()
assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err))
assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode))
}
}
func TestRevoke(t *testing.T) {
svc := newService()
_, loginSecret, err := svc.Issue(context.Background(), "", auth.Key{Type: auth.UserKey, IssuedAt: time.Now(), IssuerID: id, Subject: email})
assert.Nil(t, err, fmt.Sprintf("Issuing user key expected to succeed: %s", err))
key := auth.Key{Type: auth.APIKey, IssuedAt: time.Now(), IssuerID: id, Subject: email}
k, _, err := svc.Issue(context.Background(), loginSecret, key)
assert.Nil(t, err, fmt.Sprintf("Issuing user key expected to succeed: %s", err))
ts := newServer(svc)
defer ts.Close()
client := ts.Client()
cases := []struct {
desc string
id string
token string
status int
}{
{
desc: "revoke an existing key",
id: k.ID,
token: loginSecret,
status: http.StatusNoContent,
},
{
desc: "revoke a non-existing key",
id: "non-existing",
token: loginSecret,
status: http.StatusNoContent,
},
{
desc: "revoke a key unauthorized",
id: k.ID,
token: "wrong",
status: http.StatusForbidden},
}
for _, tc := range cases {
req := testRequest{
client: client,
method: http.MethodDelete,
url: fmt.Sprintf("%s/keys/%s", ts.URL, tc.id),
token: tc.token,
}
res, err := req.make()
assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err))
assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode))
}
}
+39
View File
@@ -0,0 +1,39 @@
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
package keys
import (
"time"
"github.com/mainflux/mainflux/auth"
)
type issueKeyReq struct {
token string
Type uint32 `json:"type,omitempty"`
Duration time.Duration `json:"duration,omitempty"`
}
// It is not possible to issue Reset key using HTTP API.
func (req issueKeyReq) validate() error {
if req.Type == auth.UserKey {
return nil
}
if req.token == "" || (req.Type != auth.APIKey) {
return auth.ErrMalformedEntity
}
return nil
}
type keyReq struct {
token string
id string
}
func (req keyReq) validate() error {
if req.token == "" || req.id == "" {
return auth.ErrMalformedEntity
}
return nil
}
+75
View File
@@ -0,0 +1,75 @@
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
package keys
import (
"net/http"
"time"
"github.com/mainflux/mainflux"
)
var (
_ mainflux.Response = (*issueKeyRes)(nil)
_ mainflux.Response = (*revokeKeyRes)(nil)
)
type issueKeyRes struct {
ID string `json:"id,omitempty"`
Value string `json:"value,omitempty"`
IssuedAt time.Time `json:"issued_at,omitempty"`
ExpiresAt *time.Time `json:"expires_at,omitempty"`
}
func (res issueKeyRes) Code() int {
return http.StatusCreated
}
func (res issueKeyRes) Headers() map[string]string {
return map[string]string{}
}
func (res issueKeyRes) Empty() bool {
return res.Value == ""
}
type retrieveKeyRes struct {
ID string `json:"id,omitempty"`
IssuerID string `json:"issuer_id,omitempty"`
Subject string `json:"subject,omitempty"`
Type uint32 `json:"type,omitempty"`
IssuedAt time.Time `json:"issued_at,omitempty"`
ExpiresAt *time.Time `json:"expires_at,omitempty"`
}
func (res retrieveKeyRes) Code() int {
return http.StatusOK
}
func (res retrieveKeyRes) Headers() map[string]string {
return map[string]string{}
}
func (res retrieveKeyRes) Empty() bool {
return false
}
type revokeKeyRes struct {
}
func (res revokeKeyRes) Code() int {
return http.StatusNoContent
}
func (res revokeKeyRes) Headers() map[string]string {
return map[string]string{}
}
func (res revokeKeyRes) Empty() bool {
return true
}
type errorRes struct {
Err string `json:"error"`
}
+120
View File
@@ -0,0 +1,120 @@
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
package keys
import (
"context"
"encoding/json"
"io"
"net/http"
"strings"
kitot "github.com/go-kit/kit/tracing/opentracing"
kithttp "github.com/go-kit/kit/transport/http"
"github.com/go-zoo/bone"
"github.com/mainflux/mainflux"
"github.com/mainflux/mainflux/auth"
"github.com/mainflux/mainflux/pkg/errors"
"github.com/opentracing/opentracing-go"
)
const contentType = "application/json"
var errUnsupportedContentType = errors.New("unsupported content type")
func MakeHandler(svc auth.Service, mux *bone.Mux, tracer opentracing.Tracer) *bone.Mux {
opts := []kithttp.ServerOption{
kithttp.ServerErrorEncoder(encodeError),
}
mux.Post("/keys", kithttp.NewServer(
kitot.TraceServer(tracer, "issue")(issueEndpoint(svc)),
decodeIssue,
encodeResponse,
opts...,
))
mux.Get("/keys/:id", kithttp.NewServer(
kitot.TraceServer(tracer, "retrieve")(retrieveEndpoint(svc)),
decodeKeyReq,
encodeResponse,
opts...,
))
mux.Delete("/keys/:id", kithttp.NewServer(
kitot.TraceServer(tracer, "revoke")(revokeEndpoint(svc)),
decodeKeyReq,
encodeResponse,
opts...,
))
return mux
}
func decodeIssue(_ context.Context, r *http.Request) (interface{}, error) {
if !strings.Contains(r.Header.Get("Content-Type"), contentType) {
return nil, errUnsupportedContentType
}
req := issueKeyReq{
token: r.Header.Get("Authorization"),
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
return nil, errors.Wrap(auth.ErrMalformedEntity, err)
}
return req, nil
}
func decodeKeyReq(_ context.Context, r *http.Request) (interface{}, error) {
req := keyReq{
token: r.Header.Get("Authorization"),
id: bone.GetValue(r, "id"),
}
return req, nil
}
func encodeResponse(_ context.Context, w http.ResponseWriter, response interface{}) error {
w.Header().Set("Content-Type", contentType)
if ar, ok := response.(mainflux.Response); ok {
for k, v := range ar.Headers() {
w.Header().Set(k, v)
}
w.WriteHeader(ar.Code())
if ar.Empty() {
return nil
}
}
return json.NewEncoder(w).Encode(response)
}
func encodeError(_ context.Context, err error, w http.ResponseWriter) {
switch {
case errors.Contains(err, auth.ErrMalformedEntity):
w.WriteHeader(http.StatusBadRequest)
case errors.Contains(err, auth.ErrUnauthorizedAccess):
w.WriteHeader(http.StatusForbidden)
case errors.Contains(err, auth.ErrNotFound):
w.WriteHeader(http.StatusNotFound)
case errors.Contains(err, auth.ErrConflict):
w.WriteHeader(http.StatusConflict)
case errors.Contains(err, io.EOF):
w.WriteHeader(http.StatusBadRequest)
case errors.Contains(err, io.ErrUnexpectedEOF):
w.WriteHeader(http.StatusBadRequest)
case errors.Contains(err, errUnsupportedContentType):
w.WriteHeader(http.StatusUnsupportedMediaType)
default:
w.WriteHeader(http.StatusInternalServerError)
}
errorVal, ok := err.(errors.Error)
if ok {
if err := json.NewEncoder(w).Encode(errorRes{Err: errorVal.Msg()}); err != nil {
w.Header().Set("Content-Type", contentType)
w.WriteHeader(http.StatusInternalServerError)
}
}
}
+24
View File
@@ -0,0 +1,24 @@
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
package http
import (
"net/http"
"github.com/go-zoo/bone"
"github.com/mainflux/mainflux"
"github.com/mainflux/mainflux/auth"
"github.com/mainflux/mainflux/auth/api/http/groups"
"github.com/mainflux/mainflux/auth/api/http/keys"
"github.com/opentracing/opentracing-go"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
func MakeHandler(svc auth.Service, tracer opentracing.Tracer) http.Handler {
mux := bone.New()
mux = keys.MakeHandler(svc, mux, tracer)
mux = groups.MakeHandler(svc, mux, tracer)
mux.GetFunc("/version", mainflux.Version("auth"))
mux.Handle("/metrics", promhttp.Handler())
return mux
}
+239
View File
@@ -0,0 +1,239 @@
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
// +build !test
package api
import (
"context"
"fmt"
"time"
"github.com/mainflux/mainflux/auth"
log "github.com/mainflux/mainflux/logger"
)
var _ auth.Service = (*loggingMiddleware)(nil)
type loggingMiddleware struct {
logger log.Logger
svc auth.Service
}
// LoggingMiddleware adds logging facilities to the core service.
func LoggingMiddleware(svc auth.Service, logger log.Logger) auth.Service {
return &loggingMiddleware{logger, svc}
}
func (lm *loggingMiddleware) Issue(ctx context.Context, token string, newKey auth.Key) (key auth.Key, secret string, err error) {
defer func(begin time.Time) {
d := "infinite duration"
if !key.ExpiresAt.IsZero() {
d = fmt.Sprintf("the key with expiration date %v", key.ExpiresAt)
}
message := fmt.Sprintf("Method issue for %s took %s to complete", d, time.Since(begin))
if err != nil {
lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err))
return
}
lm.logger.Info(fmt.Sprintf("%s without errors.", message))
}(time.Now())
return lm.svc.Issue(ctx, token, newKey)
}
func (lm *loggingMiddleware) Revoke(ctx context.Context, token, id string) (err error) {
defer func(begin time.Time) {
message := fmt.Sprintf("Method revoke for key %s took %s to complete", id, time.Since(begin))
if err != nil {
lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err))
return
}
lm.logger.Info(fmt.Sprintf("%s without errors.", message))
}(time.Now())
return lm.svc.Revoke(ctx, token, id)
}
func (lm *loggingMiddleware) RetrieveKey(ctx context.Context, token, id string) (key auth.Key, err error) {
defer func(begin time.Time) {
message := fmt.Sprintf("Method retrieve for key %s took %s to complete", id, time.Since(begin))
if err != nil {
lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err))
return
}
lm.logger.Info(fmt.Sprintf("%s without errors.", message))
}(time.Now())
return lm.svc.RetrieveKey(ctx, token, id)
}
func (lm *loggingMiddleware) Identify(ctx context.Context, key string) (id auth.Identity, err error) {
defer func(begin time.Time) {
message := fmt.Sprintf("Method identify took %s to complete", time.Since(begin))
if err != nil {
lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err))
return
}
lm.logger.Info(fmt.Sprintf("%s without errors.", message))
}(time.Now())
return lm.svc.Identify(ctx, key)
}
func (lm *loggingMiddleware) Authorize(ctx context.Context, token, sub, obj, act string) (auth bool, err error) {
defer func(begin time.Time) {
message := fmt.Sprintf("Method authorize took %s to complete", time.Since(begin))
if err != nil {
lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err))
return
}
lm.logger.Info(fmt.Sprintf("%s without errors.", message))
}(time.Now())
return lm.svc.Authorize(ctx, token, sub, obj, act)
}
func (lm *loggingMiddleware) CreateGroup(ctx context.Context, token string, group auth.Group) (g auth.Group, err error) {
defer func(begin time.Time) {
message := fmt.Sprintf("Method create_group for token %s and name %s took %s to complete", token, group.Name, time.Since(begin))
if err != nil {
lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err))
return
}
lm.logger.Info(fmt.Sprintf("%s without errors.", message))
}(time.Now())
return lm.svc.CreateGroup(ctx, token, group)
}
func (lm *loggingMiddleware) UpdateGroup(ctx context.Context, token string, group auth.Group) (gr auth.Group, err error) {
defer func(begin time.Time) {
message := fmt.Sprintf("Method update_group for token %s and name %s took %s to complete", token, group.Name, time.Since(begin))
if err != nil {
lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err))
return
}
lm.logger.Info(fmt.Sprintf("%s without errors.", message))
}(time.Now())
return lm.svc.UpdateGroup(ctx, token, group)
}
func (lm *loggingMiddleware) RemoveGroup(ctx context.Context, token string, id string) (err error) {
defer func(begin time.Time) {
message := fmt.Sprintf("Method remove_group for token %s and id %s took %s to complete", token, id, time.Since(begin))
if err != nil {
lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err))
return
}
lm.logger.Info(fmt.Sprintf("%s without errors.", message))
}(time.Now())
return lm.svc.RemoveGroup(ctx, token, id)
}
func (lm *loggingMiddleware) ViewGroup(ctx context.Context, token, id string) (group auth.Group, err error) {
defer func(begin time.Time) {
message := fmt.Sprintf("Method view_group for token %s and id %s took %s to complete", token, id, time.Since(begin))
if err != nil {
lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err))
return
}
lm.logger.Info(fmt.Sprintf("%s without errors.", message))
}(time.Now())
return lm.svc.ViewGroup(ctx, token, id)
}
func (lm *loggingMiddleware) ListGroups(ctx context.Context, token string, pm auth.PageMetadata) (gp auth.GroupPage, err error) {
defer func(begin time.Time) {
message := fmt.Sprintf("Method list_groups for token %s took %s to complete", token, time.Since(begin))
if err != nil {
lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err))
return
}
lm.logger.Info(fmt.Sprintf("%s without errors.", message))
}(time.Now())
return lm.svc.ListGroups(ctx, token, pm)
}
func (lm *loggingMiddleware) ListChildren(ctx context.Context, token, parentID string, pm auth.PageMetadata) (gp auth.GroupPage, err error) {
defer func(begin time.Time) {
message := fmt.Sprintf("Method list_children for token %s and parent %s took %s to complete", token, parentID, time.Since(begin))
if err != nil {
lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err))
return
}
lm.logger.Info(fmt.Sprintf("%s without errors.", message))
}(time.Now())
return lm.svc.ListChildren(ctx, token, parentID, pm)
}
func (lm *loggingMiddleware) ListParents(ctx context.Context, token, childID string, pm auth.PageMetadata) (gp auth.GroupPage, err error) {
defer func(begin time.Time) {
message := fmt.Sprintf("Method list_parents for token %s and child %s took for child %s to complete", token, childID, time.Since(begin))
if err != nil {
lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err))
return
}
lm.logger.Info(fmt.Sprintf("%s without errors.", message))
}(time.Now())
return lm.svc.ListParents(ctx, token, childID, pm)
}
func (lm *loggingMiddleware) ListMembers(ctx context.Context, token, groupID, groupType string, pm auth.PageMetadata) (gp auth.MemberPage, err error) {
defer func(begin time.Time) {
message := fmt.Sprintf("Method list_members for token %s and group id %s took %s to complete", token, groupID, time.Since(begin))
if err != nil {
lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err))
return
}
lm.logger.Info(fmt.Sprintf("%s without errors.", message))
}(time.Now())
return lm.svc.ListMembers(ctx, token, groupID, groupType, pm)
}
func (lm *loggingMiddleware) ListMemberships(ctx context.Context, token, memberID string, pm auth.PageMetadata) (gp auth.GroupPage, err error) {
defer func(begin time.Time) {
message := fmt.Sprintf("Method list_memberships for token %s and member id %s took %s to complete", token, memberID, time.Since(begin))
if err != nil {
lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err))
return
}
lm.logger.Info(fmt.Sprintf("%s without errors.", message))
}(time.Now())
return lm.svc.ListMemberships(ctx, token, memberID, pm)
}
func (lm *loggingMiddleware) Assign(ctx context.Context, token, groupID, groupType string, memberIDs ...string) (err error) {
defer func(begin time.Time) {
message := fmt.Sprintf("Method assign for token %s and member %s group id %s took %s to complete", token, memberIDs, groupID, time.Since(begin))
if err != nil {
lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err))
return
}
lm.logger.Info(fmt.Sprintf("%s without errors.", message))
}(time.Now())
return lm.svc.Assign(ctx, token, groupID, groupType, memberIDs...)
}
func (lm *loggingMiddleware) Unassign(ctx context.Context, token string, groupID string, memberIDs ...string) (err error) {
defer func(begin time.Time) {
message := fmt.Sprintf("Method unassign for token %s and member %s group id %s took %s to complete", token, memberIDs, groupID, time.Since(begin))
if err != nil {
lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err))
return
}
lm.logger.Info(fmt.Sprintf("%s without errors.", message))
}(time.Now())
return lm.svc.Unassign(ctx, token, groupID, memberIDs...)
}
+170
View File
@@ -0,0 +1,170 @@
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
package api
import (
"context"
"time"
"github.com/go-kit/kit/metrics"
"github.com/mainflux/mainflux/auth"
)
var _ auth.Service = (*metricsMiddleware)(nil)
type metricsMiddleware struct {
counter metrics.Counter
latency metrics.Histogram
svc auth.Service
}
// MetricsMiddleware instruments core service by tracking request count and latency.
func MetricsMiddleware(svc auth.Service, counter metrics.Counter, latency metrics.Histogram) auth.Service {
return &metricsMiddleware{
counter: counter,
latency: latency,
svc: svc,
}
}
func (ms *metricsMiddleware) Issue(ctx context.Context, token string, key auth.Key) (auth.Key, string, error) {
defer func(begin time.Time) {
ms.counter.With("method", "issue_key").Add(1)
ms.latency.With("method", "issue_key").Observe(time.Since(begin).Seconds())
}(time.Now())
return ms.svc.Issue(ctx, token, key)
}
func (ms *metricsMiddleware) Revoke(ctx context.Context, token, id string) error {
defer func(begin time.Time) {
ms.counter.With("method", "revoke_key").Add(1)
ms.latency.With("method", "revoke_key").Observe(time.Since(begin).Seconds())
}(time.Now())
return ms.svc.Revoke(ctx, token, id)
}
func (ms *metricsMiddleware) RetrieveKey(ctx context.Context, token, id string) (auth.Key, error) {
defer func(begin time.Time) {
ms.counter.With("method", "retrieve_key").Add(1)
ms.latency.With("method", "retrieve_key").Observe(time.Since(begin).Seconds())
}(time.Now())
return ms.svc.RetrieveKey(ctx, token, id)
}
func (ms *metricsMiddleware) Identify(ctx context.Context, token string) (auth.Identity, error) {
defer func(begin time.Time) {
ms.counter.With("method", "identify").Add(1)
ms.latency.With("method", "identify").Observe(time.Since(begin).Seconds())
}(time.Now())
return ms.svc.Identify(ctx, token)
}
func (ms *metricsMiddleware) Authorize(ctx context.Context, token, sub, obj, act string) (auth bool, err error) {
defer func(begin time.Time) {
ms.counter.With("method", "authorize").Add(1)
ms.latency.With("method", "authorize").Observe(time.Since(begin).Seconds())
}(time.Now())
return ms.svc.Authorize(ctx, token, sub, obj, act)
}
func (ms *metricsMiddleware) CreateGroup(ctx context.Context, token string, group auth.Group) (gr auth.Group, err error) {
defer func(begin time.Time) {
ms.counter.With("method", "create_group").Add(1)
ms.latency.With("method", "create_group").Observe(time.Since(begin).Seconds())
}(time.Now())
return ms.svc.CreateGroup(ctx, token, group)
}
func (ms *metricsMiddleware) UpdateGroup(ctx context.Context, token string, group auth.Group) (gr auth.Group, err error) {
defer func(begin time.Time) {
ms.counter.With("method", "update_group").Add(1)
ms.latency.With("method", "update_group").Observe(time.Since(begin).Seconds())
}(time.Now())
return ms.svc.UpdateGroup(ctx, token, group)
}
func (ms *metricsMiddleware) RemoveGroup(ctx context.Context, token string, id string) (err error) {
defer func(begin time.Time) {
ms.counter.With("method", "remove_group").Add(1)
ms.latency.With("method", "remove_group").Observe(time.Since(begin).Seconds())
}(time.Now())
return ms.svc.RemoveGroup(ctx, token, id)
}
func (ms *metricsMiddleware) ViewGroup(ctx context.Context, token, id string) (group auth.Group, err error) {
defer func(begin time.Time) {
ms.counter.With("method", "view_group").Add(1)
ms.latency.With("method", "view_group").Observe(time.Since(begin).Seconds())
}(time.Now())
return ms.svc.ViewGroup(ctx, token, id)
}
func (ms *metricsMiddleware) ListGroups(ctx context.Context, token string, pm auth.PageMetadata) (gp auth.GroupPage, err error) {
defer func(begin time.Time) {
ms.counter.With("method", "list_groups").Add(1)
ms.latency.With("method", "list_groups").Observe(time.Since(begin).Seconds())
}(time.Now())
return ms.svc.ListGroups(ctx, token, pm)
}
func (ms *metricsMiddleware) ListParents(ctx context.Context, token, childID string, pm auth.PageMetadata) (gp auth.GroupPage, err error) {
defer func(begin time.Time) {
ms.counter.With("method", "parents").Add(1)
ms.latency.With("method", "parents").Observe(time.Since(begin).Seconds())
}(time.Now())
return ms.svc.ListParents(ctx, token, childID, pm)
}
func (ms *metricsMiddleware) ListChildren(ctx context.Context, token, parentID string, pm auth.PageMetadata) (gp auth.GroupPage, err error) {
defer func(begin time.Time) {
ms.counter.With("method", "list_children").Add(1)
ms.latency.With("method", "list_children").Observe(time.Since(begin).Seconds())
}(time.Now())
return ms.svc.ListChildren(ctx, token, parentID, pm)
}
func (ms *metricsMiddleware) ListMembers(ctx context.Context, token, groupID, groupType string, pm auth.PageMetadata) (gp auth.MemberPage, err error) {
defer func(begin time.Time) {
ms.counter.With("method", "list_members").Add(1)
ms.latency.With("method", "list_members").Observe(time.Since(begin).Seconds())
}(time.Now())
return ms.svc.ListMembers(ctx, token, groupID, groupType, pm)
}
func (ms *metricsMiddleware) ListMemberships(ctx context.Context, token, memberID string, pm auth.PageMetadata) (gp auth.GroupPage, err error) {
defer func(begin time.Time) {
ms.counter.With("method", "list_memberships").Add(1)
ms.latency.With("method", "list_memberships").Observe(time.Since(begin).Seconds())
}(time.Now())
return ms.svc.ListMemberships(ctx, token, memberID, pm)
}
func (ms *metricsMiddleware) Assign(ctx context.Context, token, groupID, groupType string, memberIDs ...string) (err error) {
defer func(begin time.Time) {
ms.counter.With("method", "assign").Add(1)
ms.latency.With("method", "assign").Observe(time.Since(begin).Seconds())
}(time.Now())
return ms.svc.Assign(ctx, token, groupID, groupType, memberIDs...)
}
func (ms *metricsMiddleware) Unassign(ctx context.Context, token, groupID string, memberIDs ...string) (err error) {
defer func(begin time.Time) {
ms.counter.With("method", "unassign").Add(1)
ms.latency.With("method", "unassign").Observe(time.Since(begin).Seconds())
}(time.Now())
return ms.svc.Unassign(ctx, token, groupID, memberIDs...)
}
+177
View File
@@ -0,0 +1,177 @@
package auth
import (
"context"
"errors"
"time"
)
const MaxLevel = uint64(5)
const MinLevel = uint64(1)
var (
// ErrMaxLevelExceeded malformed entity.
ErrMaxLevelExceeded = errors.New("level must be less than or equal 5")
// ErrBadGroupName malformed entity.
ErrBadGroupName = errors.New("incorrect group name")
// ErrGroupConflict group conflict.
ErrGroupConflict = errors.New("group already exists")
// ErrCreateGroup indicates failure to create group.
ErrCreateGroup = errors.New("failed to create group")
// ErrFetchGroups indicates failure to fetch groups.
ErrFetchGroups = errors.New("failed to fetch groups")
// ErrUpdateGroup indicates failure to update group.
ErrUpdateGroup = errors.New("failed to update group")
// ErrDeleteGroup indicates failure to delete group.
ErrDeleteGroup = errors.New("failed to delete group")
// ErrGroupNotFound indicates failure to find group.
ErrGroupNotFound = errors.New("failed to find group")
// ErrAssignToGroup indicates failure to assign member to a group.
ErrAssignToGroup = errors.New("failed to assign member to a group")
// ErrUnassignFromGroup indicates failure to unassign member from a group.
ErrUnassignFromGroup = errors.New("failed to unassign member from a group")
// ErrUnsupportedContentType indicates unacceptable or lack of Content-Type
ErrUnsupportedContentType = errors.New("unsupported content type")
// ErrFailedDecode indicates failed to decode request body
ErrFailedDecode = errors.New("failed to decode request body")
// ErrMissingParent indicates that parent can't be found
ErrMissingParent = errors.New("failed to retrieve parent")
// ErrGroupNotEmpty indicates group is not empty, can't be deleted.
ErrGroupNotEmpty = errors.New("group is not empty")
// ErrMemberAlreadyAssigned indicates that members is already assigned.
ErrMemberAlreadyAssigned = errors.New("member is already assigned")
// ErrSelectEntity indicates error while reading entity from database
ErrSelectEntity = errors.New("select entity from db error")
)
type GroupMetadata map[string]interface{}
type Member struct {
ID string
Type string
}
type Group struct {
ID string
OwnerID string
ParentID string
Name string
Description string
Metadata GroupMetadata
// Indicates a level in tree hierarchy.
// Root node is level 1.
Level int
// Path in a tree consisting of group ids
// parentID1.parentID2.childID1
// e.g. 01EXPM5Z8HRGFAEWTETR1X1441.01EXPKW2TVK74S5NWQ979VJ4PJ.01EXPKW2TVK74S5NWQ979VJ4PJ
Path string
Children []*Group
CreatedAt time.Time
UpdatedAt time.Time
}
type PageMetadata struct {
Total uint64
Offset uint64
Limit uint64
Size uint64
Level uint64
Name string
Type string
Metadata GroupMetadata
}
type GroupPage struct {
PageMetadata
Groups []Group
}
type MemberPage struct {
PageMetadata
Members []Member
}
type GroupService interface {
// CreateGroup creates new group.
CreateGroup(ctx context.Context, token string, g Group) (Group, error)
// UpdateGroup updates the group identified by the provided ID.
UpdateGroup(ctx context.Context, token string, g Group) (Group, error)
// ViewGroup retrieves data about the group identified by ID.
ViewGroup(ctx context.Context, token, id string) (Group, error)
// ListGroups retrieves groups.
ListGroups(ctx context.Context, token string, pm PageMetadata) (GroupPage, error)
// ListChildren retrieves groups that are children to group identified by parentID
ListChildren(ctx context.Context, token, parentID string, pm PageMetadata) (GroupPage, error)
// ListParents retrieves groups that are parent to group identified by childID.
ListParents(ctx context.Context, token, childID string, pm PageMetadata) (GroupPage, error)
// ListMembers retrieves everything that is assigned to a group identified by groupID.
ListMembers(ctx context.Context, token, groupID, groupType string, pm PageMetadata) (MemberPage, error)
// ListMemberships retrieves all groups for member that is identified with memberID belongs to.
ListMemberships(ctx context.Context, token, memberID string, pm PageMetadata) (GroupPage, error)
// RemoveGroup removes the group identified with the provided ID.
RemoveGroup(ctx context.Context, token, id string) error
// Assign adds a member with memberID into the group identified by groupID.
Assign(ctx context.Context, token, groupID, groupType string, memberIDs ...string) error
// Unassign removes member with memberID from group identified by groupID.
Unassign(ctx context.Context, token, groupID string, memberIDs ...string) error
}
type GroupRepository interface {
// Save group
Save(ctx context.Context, g Group) (Group, error)
// Update a group
Update(ctx context.Context, g Group) (Group, error)
// Delete a group
Delete(ctx context.Context, id string) error
// RetrieveByID retrieves group by its id
RetrieveByID(ctx context.Context, id string) (Group, error)
// RetrieveAll retrieves all groups.
RetrieveAll(ctx context.Context, pm PageMetadata) (GroupPage, error)
// RetrieveAllParents retrieves all groups that are ancestors to the group with given groupID.
RetrieveAllParents(ctx context.Context, groupID string, pm PageMetadata) (GroupPage, error)
// RetrieveAllChildren retrieves all children from group with given groupID up to the hierarchy level.
RetrieveAllChildren(ctx context.Context, groupID string, pm PageMetadata) (GroupPage, error)
// Retrieves list of groups that member belongs to
Memberships(ctx context.Context, memberID string, pm PageMetadata) (GroupPage, error)
// Members retrieves everything that is assigned to a group identified by groupID.
Members(ctx context.Context, groupID, groupType string, pm PageMetadata) (MemberPage, error)
// Assign adds a member to group.
Assign(ctx context.Context, groupID, groupType string, memberIDs ...string) error
// Unassign removes a member from a group
Unassign(ctx context.Context, groupID string, memberIDs ...string) error
}
+107
View File
@@ -0,0 +1,107 @@
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
package jwt_test
import (
"fmt"
"testing"
"time"
"github.com/mainflux/mainflux/auth"
"github.com/mainflux/mainflux/auth/jwt"
"github.com/mainflux/mainflux/pkg/errors"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
const secret = "test"
func key() auth.Key {
exp := time.Now().UTC().Add(10 * time.Minute).Round(time.Second)
return auth.Key{
ID: "id",
Type: auth.UserKey,
Subject: "user@email.com",
IssuerID: "",
IssuedAt: time.Now().UTC().Add(-10 * time.Second).Round(time.Second),
ExpiresAt: exp,
}
}
func TestIssue(t *testing.T) {
tokenizer := jwt.New(secret)
cases := []struct {
desc string
key auth.Key
err error
}{
{
desc: "issue new token",
key: key(),
err: nil,
},
}
for _, tc := range cases {
_, err := tokenizer.Issue(tc.key)
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s expected %s, got %s", tc.desc, tc.err, err))
}
}
func TestParse(t *testing.T) {
tokenizer := jwt.New(secret)
token, err := tokenizer.Issue(key())
require.Nil(t, err, fmt.Sprintf("issuing key expected to succeed: %s", err))
apiKey := key()
apiKey.Type = auth.APIKey
apiKey.ExpiresAt = time.Now().UTC().Add(-1 * time.Minute).Round(time.Second)
apiToken, err := tokenizer.Issue(apiKey)
require.Nil(t, err, fmt.Sprintf("issuing user key expected to succeed: %s", err))
expKey := key()
expKey.ExpiresAt = time.Now().UTC().Add(-1 * time.Minute).Round(time.Second)
expToken, err := tokenizer.Issue(expKey)
require.Nil(t, err, fmt.Sprintf("issuing expired key expected to succeed: %s", err))
cases := []struct {
desc string
key auth.Key
token string
err error
}{
{
desc: "parse valid key",
key: key(),
token: token,
err: nil,
},
{
desc: "parse ivalid key",
key: auth.Key{},
token: "invalid",
err: auth.ErrUnauthorizedAccess,
},
{
desc: "parse expired key",
key: auth.Key{},
token: expToken,
err: auth.ErrKeyExpired,
},
{
desc: "parse expired API key",
key: apiKey,
token: apiToken,
err: auth.ErrAPIKeyExpired,
},
}
for _, tc := range cases {
key, err := tokenizer.Parse(tc.token)
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s expected %s, got %s", tc.desc, tc.err, err))
assert.Equal(t, tc.key, key, fmt.Sprintf("%s expected %v, got %v", tc.desc, tc.key, key))
}
}
+101
View File
@@ -0,0 +1,101 @@
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
package jwt
import (
"time"
"github.com/dgrijalva/jwt-go"
"github.com/mainflux/mainflux/auth"
"github.com/mainflux/mainflux/pkg/errors"
)
const issuerName = "mainflux.auth"
type claims struct {
jwt.StandardClaims
IssuerID string `json:"issuer_id,omitempty"`
Type *uint32 `json:"type,omitempty"`
}
func (c claims) Valid() error {
if c.Type == nil || *c.Type > auth.APIKey || c.Issuer != issuerName {
return auth.ErrMalformedEntity
}
return c.StandardClaims.Valid()
}
type tokenizer struct {
secret string
}
// New returns new JWT Tokenizer.
func New(secret string) auth.Tokenizer {
return tokenizer{secret: secret}
}
func (svc tokenizer) Issue(key auth.Key) (string, error) {
claims := claims{
StandardClaims: jwt.StandardClaims{
Issuer: issuerName,
Subject: key.Subject,
IssuedAt: key.IssuedAt.UTC().Unix(),
},
IssuerID: key.IssuerID,
Type: &key.Type,
}
if !key.ExpiresAt.IsZero() {
claims.ExpiresAt = key.ExpiresAt.UTC().Unix()
}
if key.ID != "" {
claims.Id = key.ID
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
return token.SignedString([]byte(svc.secret))
}
func (svc tokenizer) Parse(token string) (auth.Key, error) {
c := claims{}
_, err := jwt.ParseWithClaims(token, &c, func(token *jwt.Token) (interface{}, error) {
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, auth.ErrUnauthorizedAccess
}
return []byte(svc.secret), nil
})
if err != nil {
if e, ok := err.(*jwt.ValidationError); ok && e.Errors == jwt.ValidationErrorExpired {
// Expired User key needs to be revoked.
if c.Type != nil && *c.Type == auth.APIKey {
return c.toKey(), auth.ErrAPIKeyExpired
}
return auth.Key{}, errors.Wrap(auth.ErrKeyExpired, err)
}
return auth.Key{}, errors.Wrap(auth.ErrUnauthorizedAccess, err)
}
return c.toKey(), nil
}
func (c claims) toKey() auth.Key {
key := auth.Key{
ID: c.Id,
IssuerID: c.IssuerID,
Subject: c.Subject,
IssuedAt: time.Unix(c.IssuedAt, 0).UTC(),
}
if c.ExpiresAt != 0 {
key.ExpiresAt = time.Unix(c.ExpiresAt, 0).UTC()
}
// Default type is 0.
if c.Type != nil {
key.Type = *c.Type
}
return key
}
+68
View File
@@ -0,0 +1,68 @@
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
package auth
import (
"context"
"errors"
"time"
)
var (
// ErrInvalidKeyIssuedAt indicates that the Key is being used before it's issued.
ErrInvalidKeyIssuedAt = errors.New("invalid issue time")
// ErrKeyExpired indicates that the Key is expired.
ErrKeyExpired = errors.New("use of expired key")
// ErrAPIKeyExpired indicates that the Key is expired
// and that the key type is API key.
ErrAPIKeyExpired = errors.New("use of expired API key")
)
const (
// UserKey is temporary User key received on successfull login.
UserKey uint32 = iota
// RecoveryKey represents a key for resseting password.
RecoveryKey
// APIKey enables the one to act on behalf of the user.
APIKey
)
// Key represents API key.
type Key struct {
ID string
Type uint32
IssuerID string
Subject string
IssuedAt time.Time
ExpiresAt time.Time
}
// Identity contains ID and Email.
type Identity struct {
ID string
Email string
}
// Expired verifies if the key is expired.
func (k Key) Expired() bool {
if k.Type == APIKey && k.ExpiresAt.IsZero() {
return false
}
return k.ExpiresAt.UTC().Before(time.Now().UTC())
}
// KeyRepository specifies Key persistence API.
type KeyRepository interface {
// Save persists the Key. A non-nil error is returned to indicate
// operation failure
Save(context.Context, Key) (string, error)
// Retrieve retrieves Key by its unique identifier.
Retrieve(context.Context, string, string) (Key, error)
// Remove removes Key with provided ID.
Remove(context.Context, string, string) error
}
+60
View File
@@ -0,0 +1,60 @@
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
package auth_test
import (
"fmt"
"testing"
"time"
"github.com/mainflux/mainflux/auth"
"github.com/stretchr/testify/assert"
)
func TestExpired(t *testing.T) {
exp := time.Now().Add(5 * time.Minute)
exp1 := time.Now()
cases := []struct {
desc string
key auth.Key
expired bool
}{
{
desc: "not expired key",
key: auth.Key{
IssuedAt: time.Now(),
ExpiresAt: exp,
},
expired: false,
},
{
desc: "expired key",
key: auth.Key{
IssuedAt: time.Now().UTC().Add(2 * time.Minute),
ExpiresAt: exp1,
},
expired: true,
},
{
desc: "user key with no expiration date",
key: auth.Key{
IssuedAt: time.Now(),
},
expired: true,
},
{
desc: "API key with no expiration date",
key: auth.Key{
IssuedAt: time.Now(),
Type: auth.APIKey,
},
expired: false,
},
}
for _, tc := range cases {
res := tc.key.Expired()
assert.Equal(t, tc.expired, res, fmt.Sprintf("%s: expected %t got %t\n", tc.desc, tc.expired, res))
}
}
+318
View File
@@ -0,0 +1,318 @@
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
package mocks
import (
"context"
"fmt"
"strings"
"sync"
"time"
"github.com/mainflux/mainflux/auth"
)
var _ auth.GroupRepository = (*groupRepositoryMock)(nil)
type groupRepositoryMock struct {
mu sync.Mutex
// Map of groups, group id as a key.
// groups map[GroupID]auth.Group
groups map[string]auth.Group
// Map of groups with group id as key that are
// children (i.e. has same parent id) is element
// in children's map where parent id is key.
// children map[ParentID]map[GroupID]auth.Group
children map[string]map[string]auth.Group
// Map of parents' id with child group id as key.
// Each child has one parent.
// parents map[ChildID]ParentID
parents map[string]string
// Map of groups (with group id as key) which
// represent memberships is element in
// memberships' map where member id is a key.
// memberships map[MemberID]map[GroupID]auth.Group
memberships map[string]map[string]auth.Group
// Map of group members where member id is a key
// is an element in the map members where group id is a key.
// members map[type][GroupID]map[MemberID]MemberID
members map[string]map[string]map[string]string
}
// NewGroupRepository creates in-memory user repository
func NewGroupRepository() auth.GroupRepository {
return &groupRepositoryMock{
groups: make(map[string]auth.Group),
children: make(map[string]map[string]auth.Group),
parents: make(map[string]string),
memberships: make(map[string]map[string]auth.Group),
members: make(map[string]map[string]map[string]string),
}
}
func (grm *groupRepositoryMock) Save(ctx context.Context, group auth.Group) (auth.Group, error) {
grm.mu.Lock()
defer grm.mu.Unlock()
if _, ok := grm.groups[group.ID]; ok {
return auth.Group{}, auth.ErrGroupConflict
}
path := group.ID
if group.ParentID != "" {
parent, ok := grm.groups[group.ParentID]
if !ok {
return auth.Group{}, auth.ErrCreateGroup
}
if _, ok := grm.children[group.ParentID]; !ok {
grm.children[group.ParentID] = make(map[string]auth.Group)
}
grm.children[group.ParentID][group.ID] = group
grm.parents[group.ID] = group.ParentID
path = fmt.Sprintf("%s.%s", parent.Path, path)
}
group.Path = path
group.Level = len(strings.Split(path, "."))
grm.groups[group.ID] = group
return group, nil
}
func (grm *groupRepositoryMock) Update(ctx context.Context, group auth.Group) (auth.Group, error) {
grm.mu.Lock()
defer grm.mu.Unlock()
up, ok := grm.groups[group.ID]
if !ok {
return auth.Group{}, auth.ErrNotFound
}
up.Name = group.Name
up.Description = group.Description
up.Metadata = group.Metadata
up.UpdatedAt = time.Now()
grm.groups[group.ID] = up
return up, nil
}
func (grm *groupRepositoryMock) Delete(ctx context.Context, id string) error {
grm.mu.Lock()
defer grm.mu.Unlock()
if _, ok := grm.groups[id]; !ok {
return auth.ErrGroupNotFound
}
if len(grm.members[id]) > 0 {
return auth.ErrGroupNotEmpty
}
// This is not quite exact, it should go in depth
for _, ch := range grm.children[id] {
if len(grm.members[ch.ID]) > 0 {
return auth.ErrGroupNotEmpty
}
}
// This is not quite exact, it should go in depth
delete(grm.groups, id)
for _, ch := range grm.children[id] {
delete(grm.members, ch.ID)
}
delete(grm.children, id)
return nil
}
func (grm *groupRepositoryMock) RetrieveByID(ctx context.Context, id string) (auth.Group, error) {
grm.mu.Lock()
defer grm.mu.Unlock()
val, ok := grm.groups[id]
if !ok {
return auth.Group{}, auth.ErrGroupNotFound
}
return val, nil
}
func (grm *groupRepositoryMock) RetrieveAll(ctx context.Context, pm auth.PageMetadata) (auth.GroupPage, error) {
grm.mu.Lock()
defer grm.mu.Unlock()
var items []auth.Group
for _, g := range grm.groups {
items = append(items, g)
}
return auth.GroupPage{
Groups: items,
PageMetadata: auth.PageMetadata{
Total: uint64(len(items)),
},
}, nil
}
func (grm *groupRepositoryMock) Unassign(ctx context.Context, groupID string, memberIDs ...string) error {
grm.mu.Lock()
defer grm.mu.Unlock()
if _, ok := grm.groups[groupID]; !ok {
return auth.ErrGroupNotFound
}
for _, memberID := range memberIDs {
for typ, m := range grm.members[groupID] {
_, ok := m[memberID]
if !ok {
return auth.ErrGroupNotFound
}
delete(grm.members[groupID][typ], memberID)
delete(grm.memberships[memberID], groupID)
}
}
return nil
}
func (grm *groupRepositoryMock) Assign(ctx context.Context, groupID, groupType string, memberIDs ...string) error {
grm.mu.Lock()
defer grm.mu.Unlock()
if _, ok := grm.groups[groupID]; !ok {
return auth.ErrGroupNotFound
}
if _, ok := grm.members[groupID]; !ok {
grm.members[groupID] = make(map[string]map[string]string)
}
for _, memberID := range memberIDs {
if _, ok := grm.members[groupID][groupType]; !ok {
grm.members[groupID][groupType] = make(map[string]string)
}
if _, ok := grm.memberships[memberID]; !ok {
grm.memberships[memberID] = make(map[string]auth.Group)
}
grm.members[groupID][groupType][memberID] = memberID
grm.memberships[memberID][groupID] = grm.groups[groupID]
}
return nil
}
func (grm *groupRepositoryMock) Memberships(ctx context.Context, memberID string, pm auth.PageMetadata) (auth.GroupPage, error) {
grm.mu.Lock()
defer grm.mu.Unlock()
var items []auth.Group
first := uint64(pm.Offset)
last := first + uint64(pm.Limit)
i := uint64(0)
for _, g := range grm.memberships[memberID] {
if i >= first && i < last {
items = append(items, g)
}
i++
}
return auth.GroupPage{
Groups: items,
PageMetadata: auth.PageMetadata{
Limit: pm.Limit,
Offset: pm.Offset,
Total: uint64(len(items)),
},
}, nil
}
func (grm *groupRepositoryMock) Members(ctx context.Context, groupID, groupType string, pm auth.PageMetadata) (auth.MemberPage, error) {
grm.mu.Lock()
defer grm.mu.Unlock()
var items []auth.Member
members, ok := grm.members[groupID][groupType]
if !ok {
return auth.MemberPage{}, auth.ErrGroupNotFound
}
first := uint64(pm.Offset)
last := first + uint64(pm.Limit)
i := uint64(0)
for _, g := range members {
if i >= first && i < last {
items = append(items, auth.Member{ID: g, Type: groupType})
}
i++
}
return auth.MemberPage{
Members: items,
PageMetadata: auth.PageMetadata{
Total: uint64(len(items)),
},
}, nil
}
func (grm *groupRepositoryMock) RetrieveAllParents(ctx context.Context, groupID string, pm auth.PageMetadata) (auth.GroupPage, error) {
grm.mu.Lock()
defer grm.mu.Unlock()
if groupID == "" {
return auth.GroupPage{}, nil
}
group, ok := grm.groups[groupID]
if !ok {
return auth.GroupPage{}, auth.ErrGroupNotFound
}
groups := make([]auth.Group, 0)
groups, err := grm.getParents(groups, group)
if err != nil {
return auth.GroupPage{}, err
}
return auth.GroupPage{
Groups: groups,
PageMetadata: auth.PageMetadata{
Total: uint64(len(groups)),
},
}, nil
}
func (grm *groupRepositoryMock) getParents(groups []auth.Group, group auth.Group) ([]auth.Group, error) {
groups = append(groups, group)
parentID, ok := grm.parents[group.ID]
if !ok && parentID == "" {
return groups, nil
}
parent, ok := grm.groups[parentID]
if !ok {
panic(fmt.Sprintf("parent with id: %s not found", parentID))
}
return grm.getParents(groups, parent)
}
func (grm *groupRepositoryMock) RetrieveAllChildren(ctx context.Context, groupID string, pm auth.PageMetadata) (auth.GroupPage, error) {
grm.mu.Lock()
defer grm.mu.Unlock()
group, ok := grm.groups[groupID]
if !ok {
return auth.GroupPage{}, nil
}
groups := make([]auth.Group, 0)
groups = append(groups, group)
for ch := range grm.parents {
g, ok := grm.groups[ch]
if !ok {
panic(fmt.Sprintf("child with id %s not found", ch))
}
groups = append(groups, g)
}
return auth.GroupPage{
Groups: groups,
PageMetadata: auth.PageMetadata{
Total: uint64(len(groups)),
Offset: pm.Offset,
Limit: pm.Limit,
},
}, nil
}
+55
View File
@@ -0,0 +1,55 @@
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
package mocks
import (
"context"
"sync"
"github.com/mainflux/mainflux/auth"
)
var _ auth.KeyRepository = (*keyRepositoryMock)(nil)
type keyRepositoryMock struct {
mu sync.Mutex
keys map[string]auth.Key
}
// NewKeyRepository creates in-memory user repository
func NewKeyRepository() auth.KeyRepository {
return &keyRepositoryMock{
keys: make(map[string]auth.Key),
}
}
func (krm *keyRepositoryMock) Save(ctx context.Context, key auth.Key) (string, error) {
krm.mu.Lock()
defer krm.mu.Unlock()
if _, ok := krm.keys[key.ID]; ok {
return "", auth.ErrConflict
}
krm.keys[key.ID] = key
return key.ID, nil
}
func (krm *keyRepositoryMock) Retrieve(ctx context.Context, issuerID, id string) (auth.Key, error) {
krm.mu.Lock()
defer krm.mu.Unlock()
if key, ok := krm.keys[id]; ok && key.IssuerID == issuerID {
return key, nil
}
return auth.Key{}, auth.ErrNotFound
}
func (krm *keyRepositoryMock) Remove(ctx context.Context, issuerID, id string) error {
krm.mu.Lock()
defer krm.mu.Unlock()
if key, ok := krm.keys[id]; ok && key.IssuerID == issuerID {
delete(krm.keys, id)
}
return nil
}
+594
View File
@@ -0,0 +1,594 @@
openapi: 3.0.1
info:
title: Mainflux authentication service
description: HTTP API for managing platform API keys.
version: "1.0.0"
paths:
/keys:
post:
summary: Issue API key
description: |
Generates a new API key. Thew new API key will
be uniquely identified by its ID.
tags:
- auth
requestBody:
$ref: "#/components/requestBodies/KeyRequest"
responses:
'201':
description: Issued new key.
'400':
description: Failed due to malformed JSON.
'409':
description: Failed due to using already existing ID.
'415':
description: Missing or invalid content type.
'500':
$ref: "#/components/responses/ServiceError"
/keys/{id}:
get:
summary: Gets API key details.
description: |
Gets API key details for the given key.
tags:
- auth
parameters:
- $ref: "#/components/parameters/Authorization"
- $ref: "#/components/parameters/ApiKeyId"
responses:
'200':
$ref: "#/components/responses/KeyRes"
'400':
description: Failed due to malformed query parameters.
'403':
description: Missing or invalid access token provided.
'500':
$ref: "#/components/responses/ServiceError"
delete:
summary: Revoke API key
description: |
Revoke API key identified by the given ID.
tags:
- auth
parameters:
- $ref: "#/components/parameters/Authorization"
- $ref: "#/components/parameters/ApiKeyId"
responses:
'204':
description: Key revoked.
'403':
description: Missing or invalid access token provided.
'500':
$ref: "#/components/responses/ServiceError"
/groups:
post:
summary: Creates new group
description: |
Creates new group that can be used for grouping entities - things, users.
tags:
- auth
parameters:
- $ref: "#/components/parameters/Authorization"
- $ref: "#/components/requestBodies/GroupCreateReq"
responses:
'201':
$ref: "#/components/responses/GroupCreateRes"
'400':
description: Failed due to malformed JSON.
'409':
description: Failed due to using an existing email address.
'415':
description: Missing or invalid content type.
'500':
$ref: "#/components/responses/ServiceError"
get:
summary: Gets all groups.
description: |
Gets all groups up to a max level of hierarchy that can be fetched in one
request ( max level = 5). Result can be filtered by metadata. Groups will
be returned as JSON array or JSON tree.
tags:
- auth
parameters:
- $ref: "#/components/parameters/Authorization"
- $ref: "#/components/parameters/Level"
- $ref: "#/components/parameters/Metadata"
- $ref: "#/components/parameters/Tree"
responses:
'200':
$ref: "#/components/responses/GroupsPageRes"
'400':
description: Failed due to malformed query parameters.
'403':
description: Missing or invalid access token provided.
'404':
description: Group does not exist.
'500':
$ref: "#/components/responses/ServiceError"
/groups/{groupId}:
get:
summary: Gets group info.
description: |
Gets info on a group specified by id.
tags:
- auth
parameters:
- $ref: "#/components/parameters/Authorization"
- $ref: "#/components/parameters/GroupId"
responses:
'200':
$ref: "#/components/responses/GroupRes"
'400':
description: Failed due to malformed query parameters.
'403':
description: Missing or invalid access token provided.
'404':
description: Group does not exist.
'500':
$ref: "#/components/responses/ServiceError"
put:
summary: Updates group data.
description: |
Updates Name, Description or Metadata of a group.
tags:
- auth
parameters:
- $ref: "#/components/parameters/Authorization"
- $ref: "#/components/parameters/GroupId"
requestBody:
$ref: "#/components/requestBodies/GroupUpdateReq"
responses:
'200':
description: Group updated.
'400':
description: Failed due to malformed query parameters.
'403':
description: Missing or invalid access token provided.
'404':
description: Group does not exist.
'500':
$ref: "#/components/responses/ServiceError"
delete:
summary: Deletes group.
description: |
Deletes group. If group is parent and descendant groups do not have any members
child groups will be deleted. Group cannot be deleted if has members or if
any descendant group has members.
tags:
- auth
parameters:
- $ref: "#/components/parameters/Authorization"
- $ref: "#/components/parameters/GroupId"
- $ref: "#/components/parameters/Level"
- $ref: "#/components/parameters/Metadata"
- $ref: "#/components/parameters/Tree"
responses:
'204':
description: Group removed.
'400':
description: Failed due to malformed query parameters.
'403':
description: Missing or invalid access token provided.
'404':
description: Group does not exist.
'500':
$ref: "#/components/responses/ServiceError"
/groups/{groupId}/children:
get:
summary: Gets group children.
description: |
Gets the whole tree of descendants of group for given id including itself.
For performance reason request is limited up to a given level of hierarchy
(max. 5).
tags:
- auth
parameters:
- $ref: "#/components/parameters/Authorization"
- $ref: "#/components/parameters/GroupId"
- $ref: "#/components/parameters/Level"
- $ref: "#/components/parameters/Metadata"
- $ref: "#/components/parameters/Tree"
responses:
'200':
$ref: "#/components/responses/GroupsPageRes"
'400':
description: Failed due to malformed query parameters.
'403':
description: Missing or invalid access token provided.
'404':
description: Group does not exist.
'500':
$ref: "#/components/responses/ServiceError"
/groups/{groupId}/parents:
get:
summary: Gets group info.
description: |
Gets a direct line of ancestors for a group specified by id.
Result is up to a specified hierarchy level or up to a root group.
Result can be a JSON array or a JSON tree.
tags:
- auth
parameters:
- $ref: "#/components/parameters/Authorization"
- $ref: "#/components/parameters/GroupId"
- $ref: "#/components/parameters/Level"
- $ref: "#/components/parameters/Metadata"
- $ref: "#/components/parameters/Tree"
responses:
'200':
$ref: "#/components/responses/GroupsPageRes"
'400':
description: Failed due to malformed query parameters.
'403':
description: Missing or invalid access token provided.
'404':
description: Group does not exist.
'500':
$ref: "#/components/responses/ServiceError"
/groups/{groupId}/members:
post:
summary: Assigns members to a group.
description: |
Assigns thing or user id to a group.
tags:
- auth
parameters:
- $ref: "#/components/parameters/Authorization"
- $ref: "#/components/parameters/GroupId"
requestBody:
$ref: "#/components/requestBodies/MembersReq"
responses:
'201':
$ref: "#/components/responses/GroupCreateRes"
'400':
description: Failed due to malformed JSON.
'403':
description: Missing or invalid access token provided.
'409':
description: Failed due to using an existing email address.
'415':
description: Missing or invalid content type.
'500':
$ref: "#/components/responses/ServiceError"
get:
summary: Gets members of a group.
description: |
Array of member ids that are in the group specified with groupID.
parameters:
- $ref: "#/components/parameters/Authorization"
- $ref: "#/components/parameters/MemberType"
- $ref: "#/components/parameters/GroupId"
- $ref: "#/components/parameters/Offset"
- $ref: "#/components/parameters/Limit"
responses:
'200':
$ref: "#/components/responses/MembersRes"
'403':
description: Missing or invalid access token provided.
'500':
$ref: "#/components/responses/ServiceError"
components:
schemas:
Key:
type: object
properties:
id:
type: string
format: uuid
example: "c5747f2f-2a7c-4fe1-b41a-51a5ae290945"
description: API key unique identifier
issuer_id:
type: string
format: uuid
example: "9118de62-c680-46b7-ad0a-21748a52833a"
description: In ID of the entity that issued the token.
type:
type: integer
example: 0
description: API key type. Keys of different type are processed differently.
subject:
type: string
format: string
example: "test@example.com"
description: User's email or service identifier of API key subject.
issued_at:
type: string
format: date-time
example: "2019-11-26 13:31:52"
description: Time when the key is generated.
expires_at:
type: string
format: date-time
example: "2019-11-26 13:31:52"
description: Time when the Key expires. If this field is missing,
that means that Key is valid indefinitely.
GroupReqSchema:
type: object
properties:
name:
type: string
description: |
Free-form group name. Group name is unique on the given hierarchy level.
description:
type: string
description: Group description, free form text.
parent_id:
type: string
format: ulid
description: Id of parent group, it must be existing group.
metadata:
type: object
description: Arbitrary, object-encoded group's data.
GroupUpdateSchema:
type: object
properties:
name:
type: string
description: |
Free-form group name. Group name is unique on the given hierarchy level.
description:
type: string
description: Group description, free form text.
metadata:
type: object
description: Arbitrary, object-encoded group's data.
GroupResSchema:
type: object
properties:
id:
type: string
format: ulid
description: Unique group identifier generated by the service.
name:
type: string
description: Free-form group name.
parent_id:
type: string
description: Group ID of parent group.
owner_id:
type: string
format: uuid
description: UUID of user that created the group.
metadata:
type: object
description: Arbitrary, object-encoded group's data.
level:
type: integer
description: Level in hierarchy, distance from the root group.
path:
type: string
description: Hierarchy path, concatenated ids of group ancestors.
children:
type: object
# schema: GroupResSchema
created_at:
type: string
description: Datetime of group creation.
updated_at:
type: string
description: Datetime of last group updated.
required:
- id
- name
- owner_id
- description
- level
- path
- created_at
- updated_at
MembersReqSchema:
type: object
properties:
members:
type: array
minItems: 0
uniqueItems: true
items:
type: string
format: uuid | ulid
type:
type: string
description: Type of entity
GroupsPage:
type: object
properties:
groups:
type: array
minItems: 0
uniqueItems: true
items:
$ref: "#/components/schemas/GroupResSchema"
total:
type: integer
description: Total number of items.
level:
type: integer
description: Level of hierarchy up to which groups are fetched.
required:
- groups
- total
- level
MembershipPage:
type: object
properties:
groups:
type: array
minItems: 0
uniqueItems: true
items:
$ref: "#/components/schemas/GroupResSchema"
offset:
type: integer
description: Number of items to skip during retrieval.
limit:
type: integer
description: Maximum number of items to return in one page.
total:
type: integer
description: Total number of items.
required:
- groups
parameters:
Authorization:
name: Authorization
description: User's access token.
in: header
schema:
type: string
format: jwt
required: true
ApiKeyId:
name: id
description: API Key ID.
in: path
schema:
type: string
format: uuid
required: true
GroupId:
name: groupId
description: Group ID.
in: path
schema:
type: string
format: uuid
required: true
MemberType:
name: type
description: Member type association.
in: path
schema:
type: string
enum: [users, things]
required: true
Limit:
name: limit
description: Size of the subset to retrieve.
in: query
schema:
type: integer
default: 10
maximum: 100
minimum: 1
required: false
Offset:
name: offset
description: Number of items to skip during retrieval.
in: query
schema:
type: integer
default: 0
minimum: 0
required: false
Level:
name: level
description: Level of hierarchy up to which to retrieve groups from given group id.
in: query
schema:
type: integer
minimum: 1
maximum: 5
required: false
Metadata:
name: metadata
description: Metadata filter. Filtering is performed matching the parameter with metadata on top level. Parameter is json.
in: query
required: false
schema:
type: object
additionalProperties: {}
Tree:
name: tree
description: Specify type of response, JSON array or tree.
in: query
required: false
schema:
type: boolean
default: false
requestBodies:
KeyRequest:
description: JSON-formatted document describing key request.
required: true
content:
application/json:
schema:
type: object
properties:
type:
type: integer
example: 0
description: API key type. Keys of different type are processed differently.
token:
type: string
format: jwt
example: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiZXhhbXBsZSIsImlhdCI6MTUxNjIzOTAyMn0.9UYAFWmPIn4ojss36LpIGSqABZHfADQmVuKQ4PJBMdI"
description: JWT for the entity that's sending Key request.
duration:
type: number
format: integer
example: 23456
description: Number of seconds issued token is valid for.
GroupCreateReq:
description: JSON-formatted document describing group create request.
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/GroupReqSchema"
GroupUpdateReq:
description: JSON-formatted document describing group create request.
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/GroupUpdateSchema"
MembersReq:
description: JSON array of member IDs.
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/MembersReqSchema"
responses:
ServiceError:
description: Unexpected server-side error occurred.
KeyRes:
description: Data retrieved.
content:
application/json:
schema:
$ref: "#/components/schemas/Key"
GroupCreateRes:
description: Group created.
headers:
Location:
content:
text/plain:
schema:
type: string
description: Created group's relative URL.
example: /groups/{groupId}
GroupRes:
description: Data retrieved.
content:
application/json:
schema:
$ref: "#/components/schemas/GroupResSchema"
GroupsPageRes:
description: Group data retrieved.
content:
application/json:
schema:
$ref: "#/components/schemas/GroupsPage"
MembersRes:
description: Groups data retrieved. Groups assigned to a member.
content:
application/json:
schema:
$ref: "#/components/schemas/MembershipPage"
MembershipPageRes:
description: Groups data retrieved. Groups assigned to a member.
content:
application/json:
schema:
$ref: "#/components/schemas/MembershipPage"
+6
View File
@@ -0,0 +1,6 @@
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
// Package postgres contains Key repository implementations using
// PostgreSQL as the underlying database.
package postgres
+743
View File
@@ -0,0 +1,743 @@
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
package postgres
import (
"context"
"database/sql"
"database/sql/driver"
"encoding/json"
"fmt"
"time"
"github.com/gofrs/uuid"
"github.com/jmoiron/sqlx"
"github.com/lib/pq"
"github.com/mainflux/mainflux/auth"
"github.com/mainflux/mainflux/pkg/errors"
"github.com/mainflux/mainflux/users"
)
var (
errStringToUUID = errors.New("error converting string")
errGetTotal = errors.New("failed to get total number of groups")
errCreateMetadataQuery = errors.New("failed to create query for metadata")
errTruncation = "string_data_right_truncation"
errFK = "foreign_key_violation"
groupIDFkeyy = "group_relations_group_id_fkey"
)
var _ auth.GroupRepository = (*groupRepository)(nil)
type groupRepository struct {
db Database
}
// NewGroupRepo instantiates a PostgreSQL implementation of group
// repository.
func NewGroupRepo(db Database) auth.GroupRepository {
return &groupRepository{
db: db,
}
}
func (gr groupRepository) Save(ctx context.Context, g auth.Group) (auth.Group, error) {
// For root group path is initialized with id
q := `INSERT INTO groups (name, description, id, path, owner_id, metadata, created_at, updated_at)
VALUES (:name, :description, :id, :id, :owner_id, :metadata, :created_at, :updated_at)
RETURNING id, name, owner_id, parent_id, description, metadata, path, nlevel(path) as level, created_at, updated_at`
if g.ParentID != "" {
// Path is constructed in insert_group_tr - init.go
q = `INSERT INTO groups (name, description, id, owner_id, parent_id, metadata, created_at, updated_at)
VALUES ( :name, :description, :id, :owner_id, :parent_id, :metadata, :created_at, :updated_at)
RETURNING id, name, owner_id, parent_id, description, metadata, path, nlevel(path) as level, created_at, updated_at`
}
dbg, err := toDBGroup(g)
if err != nil {
return auth.Group{}, err
}
row, err := gr.db.NamedQueryContext(ctx, q, dbg)
if err != nil {
pqErr, ok := err.(*pq.Error)
if ok {
switch pqErr.Code.Name() {
case errInvalid, errTruncation:
return auth.Group{}, errors.Wrap(auth.ErrMalformedEntity, err)
case errFK:
return auth.Group{}, errors.Wrap(auth.ErrCreateGroup, err)
case errDuplicate:
return auth.Group{}, errors.Wrap(auth.ErrGroupConflict, err)
}
}
return auth.Group{}, errors.Wrap(auth.ErrCreateGroup, errors.New(pqErr.Message))
}
defer row.Close()
row.Next()
dbg = dbGroup{}
if err := row.StructScan(&dbg); err != nil {
return auth.Group{}, err
}
return toGroup(dbg)
}
func (gr groupRepository) Update(ctx context.Context, g auth.Group) (auth.Group, error) {
q := `UPDATE groups SET name = :name, description = :description, metadata = :metadata, updated_at = :updated_at WHERE id = :id
RETURNING id, name, owner_id, parent_id, description, metadata, path, nlevel(path) as level, created_at, updated_at`
dbu, err := toDBGroup(g)
if err != nil {
return auth.Group{}, errors.Wrap(auth.ErrUpdateGroup, err)
}
row, err := gr.db.NamedQueryContext(ctx, q, dbu)
if err != nil {
pqErr, ok := err.(*pq.Error)
if ok {
switch pqErr.Code.Name() {
case errInvalid, errTruncation:
return auth.Group{}, errors.Wrap(auth.ErrMalformedEntity, err)
case errDuplicate:
return auth.Group{}, errors.Wrap(auth.ErrGroupConflict, err)
}
}
return auth.Group{}, errors.Wrap(auth.ErrUpdateGroup, errors.New(pqErr.Message))
}
defer row.Close()
row.Next()
dbu = dbGroup{}
if err := row.StructScan(&dbu); err != nil {
return g, errors.Wrap(auth.ErrUpdateGroup, err)
}
return toGroup(dbu)
}
func (gr groupRepository) Delete(ctx context.Context, groupID string) error {
qd := `DELETE FROM groups WHERE id = :id`
group := auth.Group{
ID: groupID,
}
dbg, err := toDBGroup(group)
if err != nil {
return errors.Wrap(auth.ErrUpdateGroup, err)
}
res, err := gr.db.NamedExecContext(ctx, qd, dbg)
if err != nil {
pqErr, ok := err.(*pq.Error)
if ok {
switch pqErr.Code.Name() {
case errInvalid, errTruncation:
return errors.Wrap(auth.ErrMalformedEntity, err)
case errFK:
switch pqErr.Constraint {
case groupIDFkeyy:
return errors.Wrap(auth.ErrGroupNotEmpty, err)
}
return errors.Wrap(auth.ErrGroupConflict, err)
}
}
return errors.Wrap(auth.ErrUpdateGroup, errors.New(pqErr.Message))
}
cnt, err := res.RowsAffected()
if err != nil {
return errors.Wrap(auth.ErrDeleteGroup, err)
}
if cnt != 1 {
return errors.Wrap(auth.ErrDeleteGroup, err)
}
return nil
}
func (gr groupRepository) RetrieveByID(ctx context.Context, id string) (auth.Group, error) {
dbu := dbGroup{
ID: id,
}
q := `SELECT id, name, owner_id, parent_id, description, metadata, path, nlevel(path) as level, created_at, updated_at FROM groups WHERE id = $1`
if err := gr.db.QueryRowxContext(ctx, q, id).StructScan(&dbu); err != nil {
if err == sql.ErrNoRows {
return auth.Group{}, errors.Wrap(auth.ErrGroupNotFound, err)
}
return auth.Group{}, errors.Wrap(auth.ErrSelectEntity, err)
}
return toGroup(dbu)
}
func (gr groupRepository) RetrieveAll(ctx context.Context, pm auth.PageMetadata) (auth.GroupPage, error) {
_, metaQuery, err := getGroupsMetadataQuery("groups", pm.Metadata)
if err != nil {
return auth.GroupPage{}, errors.Wrap(auth.ErrFailedToRetrieveAll, err)
}
var mq string
if metaQuery != "" {
mq = fmt.Sprintf(" AND %s", metaQuery)
}
q := fmt.Sprintf(`SELECT id, owner_id, parent_id, name, description, metadata, path, nlevel(path) as level, created_at, updated_at FROM groups
WHERE nlevel(path) <= :level %s ORDER BY path`, mq)
dbPage, err := toDBGroupPage("", "", pm)
if err != nil {
return auth.GroupPage{}, errors.Wrap(auth.ErrFailedToRetrieveAll, err)
}
rows, err := gr.db.NamedQueryContext(ctx, q, dbPage)
if err != nil {
return auth.GroupPage{}, errors.Wrap(auth.ErrFailedToRetrieveAll, err)
}
defer rows.Close()
items, err := gr.processRows(rows)
if err != nil {
return auth.GroupPage{}, errors.Wrap(auth.ErrFailedToRetrieveAll, err)
}
cq := "SELECT COUNT(*) FROM groups"
if metaQuery != "" {
cq = fmt.Sprintf(" %s WHERE %s", cq, metaQuery)
}
total, err := total(ctx, gr.db, cq, dbPage)
if err != nil {
return auth.GroupPage{}, errors.Wrap(auth.ErrFailedToRetrieveAll, err)
}
page := auth.GroupPage{
Groups: items,
PageMetadata: auth.PageMetadata{
Total: total,
Size: uint64(len(items)),
},
}
return page, nil
}
func (gr groupRepository) RetrieveAllParents(ctx context.Context, groupID string, pm auth.PageMetadata) (auth.GroupPage, error) {
q := `SELECT g.id, g.name, g.owner_id, g.parent_id, g.description, g.metadata, g.path, nlevel(g.path) as level, g.created_at, g.updated_at
FROM groups parent, groups g
WHERE parent.id = :id AND g.path @> parent.path AND nlevel(parent.path) - nlevel(g.path) <= :level`
cq := `SELECT COUNT(*) FROM groups parent, groups g WHERE parent.id = :id AND g.path @> parent.path`
gp, err := gr.retrieve(ctx, groupID, q, cq, pm)
if err != nil {
return auth.GroupPage{}, errors.Wrap(auth.ErrFailedToRetrieveParents, err)
}
return gp, nil
}
func (gr groupRepository) RetrieveAllChildren(ctx context.Context, groupID string, pm auth.PageMetadata) (auth.GroupPage, error) {
q := `SELECT g.id, g.name, g.owner_id, g.parent_id, g.description, g.metadata, g.path, nlevel(g.path) as level, g.created_at, g.updated_at
FROM groups parent, groups g
WHERE parent.id = :id AND g.path <@ parent.path AND nlevel(g.path) - nlevel(parent.path) < :level`
cq := `SELECT COUNT(*) FROM groups parent, groups g WHERE parent.id = :id AND g.path <@ parent.path `
gp, err := gr.retrieve(ctx, groupID, q, cq, pm)
if err != nil {
return auth.GroupPage{}, errors.Wrap(auth.ErrFailedToRetrieveChildren, err)
}
return gp, nil
}
func (gr groupRepository) retrieve(ctx context.Context, groupID, retQuery, cntQuery string, pm auth.PageMetadata) (auth.GroupPage, error) {
if groupID == "" {
return auth.GroupPage{}, nil
}
_, mq, err := getGroupsMetadataQuery("g", pm.Metadata)
if err != nil {
return auth.GroupPage{}, err
}
if mq != "" {
mq = fmt.Sprintf("AND %s", mq)
}
retQuery = fmt.Sprintf(`%s %s`, retQuery, mq)
cntQuery = fmt.Sprintf(`%s %s`, cntQuery, mq)
dbPage, err := toDBGroupPage(groupID, "", pm)
if err != nil {
return auth.GroupPage{}, err
}
rows, err := gr.db.NamedQueryContext(ctx, retQuery, dbPage)
if err != nil {
return auth.GroupPage{}, err
}
defer rows.Close()
items, err := gr.processRows(rows)
if err != nil {
return auth.GroupPage{}, err
}
total, err := total(ctx, gr.db, cntQuery, dbPage)
if err != nil {
return auth.GroupPage{}, err
}
page := auth.GroupPage{
Groups: items,
PageMetadata: auth.PageMetadata{
Level: pm.Level,
Total: total,
Size: uint64(len(items)),
},
}
return page, nil
}
func (gr groupRepository) Members(ctx context.Context, groupID, groupType string, pm auth.PageMetadata) (auth.MemberPage, error) {
_, mq, err := getGroupsMetadataQuery("groups", pm.Metadata)
if err != nil {
return auth.MemberPage{}, errors.Wrap(auth.ErrFailedToRetrieveMembers, err)
}
q := fmt.Sprintf(`SELECT gr.member_id, gr.group_id, gr.type, gr.created_at, gr.updated_at FROM group_relations gr
WHERE gr.group_id = :group_id AND gr.type = :type %s`, mq)
if groupType == "" {
q = fmt.Sprintf(`SELECT gr.member_id, gr.group_id, gr.type, gr.created_at, gr.updated_at FROM group_relations gr
WHERE gr.group_id = :group_id %s`, mq)
}
params, err := toDBMemberPage("", groupID, groupType, pm)
if err != nil {
return auth.MemberPage{}, err
}
rows, err := gr.db.NamedQueryContext(ctx, q, params)
if err != nil {
return auth.MemberPage{}, errors.Wrap(auth.ErrFailedToRetrieveMembers, err)
}
defer rows.Close()
var items []auth.Member
for rows.Next() {
member := dbMember{}
if err := rows.StructScan(&member); err != nil {
return auth.MemberPage{}, errors.Wrap(auth.ErrFailedToRetrieveMembers, err)
}
if err != nil {
return auth.MemberPage{}, err
}
items = append(items, auth.Member{ID: member.MemberID, Type: member.Type})
}
cq := fmt.Sprintf(`SELECT COUNT(*) FROM groups g, group_relations gr
WHERE gr.group_id = :group_id AND gr.group_id = g.id AND gr.type = :type %s;`, mq)
total, err := total(ctx, gr.db, cq, params)
if err != nil {
return auth.MemberPage{}, errors.Wrap(auth.ErrFailedToRetrieveMembers, err)
}
page := auth.MemberPage{
Members: items,
PageMetadata: auth.PageMetadata{
Total: total,
Offset: pm.Offset,
Limit: pm.Limit,
Size: uint64(len(items)),
},
}
return page, nil
}
func (gr groupRepository) Memberships(ctx context.Context, memberID string, pm auth.PageMetadata) (auth.GroupPage, error) {
_, mq, err := getGroupsMetadataQuery("groups", pm.Metadata)
if err != nil {
return auth.GroupPage{}, errors.Wrap(auth.ErrFailedToRetrieveMembership, err)
}
if mq != "" {
mq = fmt.Sprintf("AND %s", mq)
}
q := fmt.Sprintf(`SELECT g.id, g.owner_id, g.parent_id, g.name, g.description, g.metadata
FROM group_relations gr, groups g
WHERE gr.group_id = g.id and gr.member_id = :member_id
%s ORDER BY id LIMIT :limit OFFSET :offset;`, mq)
params, err := toDBMemberPage(memberID, "", "", pm)
if err != nil {
return auth.GroupPage{}, err
}
rows, err := gr.db.NamedQueryContext(ctx, q, params)
if err != nil {
return auth.GroupPage{}, errors.Wrap(auth.ErrFailedToRetrieveMembership, err)
}
defer rows.Close()
var items []auth.Group
for rows.Next() {
dbg := dbGroup{}
if err := rows.StructScan(&dbg); err != nil {
return auth.GroupPage{}, errors.Wrap(auth.ErrFailedToRetrieveMembership, err)
}
gr, err := toGroup(dbg)
if err != nil {
return auth.GroupPage{}, err
}
items = append(items, gr)
}
cq := fmt.Sprintf(`SELECT COUNT(*) FROM group_relations gr, groups g
WHERE gr.group_id = g.id and gr.member_id = :member_id %s `, mq)
total, err := total(ctx, gr.db, cq, params)
if err != nil {
return auth.GroupPage{}, errors.Wrap(auth.ErrFailedToRetrieveMembership, err)
}
page := auth.GroupPage{
Groups: items,
PageMetadata: auth.PageMetadata{
Total: total,
Offset: pm.Offset,
Limit: pm.Limit,
Size: uint64(len(items)),
},
}
return page, nil
}
func (gr groupRepository) Assign(ctx context.Context, groupID, groupType string, ids ...string) error {
tx, err := gr.db.BeginTxx(ctx, nil)
if err != nil {
return errors.Wrap(auth.ErrAssignToGroup, err)
}
qIns := `INSERT INTO group_relations (group_id, member_id, type, created_at, updated_at)
VALUES(:group_id, :member_id, :type, :created_at, :updated_at)`
for _, id := range ids {
dbg, err := toDBGroupRelation(id, groupID, groupType)
if err != nil {
return errors.Wrap(auth.ErrAssignToGroup, err)
}
created := time.Now()
dbg.CreatedAt = created
dbg.UpdatedAt = created
if _, err := tx.NamedExecContext(ctx, qIns, dbg); err != nil {
tx.Rollback()
pqErr, ok := err.(*pq.Error)
if ok {
switch pqErr.Code.Name() {
case errInvalid, errTruncation:
return errors.Wrap(auth.ErrMalformedEntity, err)
case errFK:
return errors.Wrap(auth.ErrConflict, errors.New(pqErr.Detail))
case errDuplicate:
return errors.Wrap(auth.ErrMemberAlreadyAssigned, errors.New(pqErr.Detail))
}
}
return errors.Wrap(auth.ErrAssignToGroup, err)
}
}
if err = tx.Commit(); err != nil {
return errors.Wrap(auth.ErrAssignToGroup, err)
}
return nil
}
func (gr groupRepository) Unassign(ctx context.Context, groupID string, ids ...string) error {
tx, err := gr.db.BeginTxx(ctx, nil)
if err != nil {
return errors.Wrap(auth.ErrAssignToGroup, err)
}
qDel := `DELETE from group_relations WHERE group_id = :group_id AND member_id = :member_id`
for _, id := range ids {
dbg, err := toDBGroupRelation(id, groupID, "")
if err != nil {
return errors.Wrap(auth.ErrAssignToGroup, err)
}
if _, err := tx.NamedExecContext(ctx, qDel, dbg); err != nil {
tx.Rollback()
pqErr, ok := err.(*pq.Error)
if ok {
switch pqErr.Code.Name() {
case errInvalid, errTruncation:
return errors.Wrap(auth.ErrMalformedEntity, err)
case errDuplicate:
return errors.Wrap(auth.ErrConflict, err)
}
}
return errors.Wrap(auth.ErrAssignToGroup, err)
}
}
if err = tx.Commit(); err != nil {
return errors.Wrap(auth.ErrAssignToGroup, err)
}
return nil
}
type dbMember struct {
MemberID string `db:"member_id"`
GroupID string `db:"group_id"`
Type string `db:"type"`
CreatedAt time.Time `db:"created_at"`
UpdatedAt time.Time `db:"updated_at"`
}
type dbGroup struct {
ID string `db:"id"`
ParentID sql.NullString `db:"parent_id"`
OwnerID uuid.NullUUID `db:"owner_id"`
Name string `db:"name"`
Description string `db:"description"`
Metadata dbMetadata `db:"metadata"`
Level int `db:"level"`
Path string `db:"path"`
CreatedAt time.Time `db:"created_at"`
UpdatedAt time.Time `db:"updated_at"`
}
type dbGroupPage struct {
ID string `db:"id"`
ParentID string `db:"parent_id"`
OwnerID uuid.NullUUID `db:"owner_id"`
Metadata dbMetadata `db:"metadata"`
Path string `db:"path"`
Level uint64 `db:"level"`
Total uint64 `db:"total"`
Limit uint64 `db:"limit"`
Offset uint64 `db:"offset"`
}
type dbMemberPage struct {
GroupID string `db:"group_id"`
MemberID string `db:"member_id"`
Type string `db:"type"`
Metadata dbMetadata `db:"metadata"`
Limit uint64 `db:"limit"`
Offset uint64 `db:"offset"`
Size uint64
}
func toUUID(id string) (uuid.NullUUID, error) {
var uid uuid.NullUUID
if id == "" {
return uuid.NullUUID{UUID: uuid.Nil, Valid: false}, nil
}
err := uid.Scan(id)
return uid, err
}
func toString(id uuid.NullUUID) (string, error) {
if id.Valid {
return id.UUID.String(), nil
}
if id.UUID == uuid.Nil {
return "", nil
}
return "", errStringToUUID
}
func toDBGroup(g auth.Group) (dbGroup, error) {
ownerID, err := toUUID(g.OwnerID)
if err != nil {
return dbGroup{}, err
}
var parentID sql.NullString
if g.ParentID != "" {
parentID = sql.NullString{String: g.ParentID, Valid: true}
}
meta := dbMetadata(g.Metadata)
return dbGroup{
ID: g.ID,
Name: g.Name,
ParentID: parentID,
OwnerID: ownerID,
Description: g.Description,
Metadata: meta,
Path: g.Path,
CreatedAt: g.CreatedAt,
UpdatedAt: g.UpdatedAt,
}, nil
}
func toDBGroupPage(id, path string, pm auth.PageMetadata) (dbGroupPage, error) {
level := auth.MaxLevel
if pm.Level < auth.MaxLevel {
level = pm.Level
}
return dbGroupPage{
Metadata: dbMetadata(pm.Metadata),
ID: id,
Path: path,
Level: level,
Total: pm.Total,
Offset: pm.Offset,
Limit: pm.Limit,
}, nil
}
func toDBMemberPage(memberID, groupID, groupType string, pm auth.PageMetadata) (dbMemberPage, error) {
return dbMemberPage{
GroupID: groupID,
MemberID: memberID,
Type: groupType,
Metadata: dbMetadata(pm.Metadata),
Offset: pm.Offset,
Limit: pm.Limit,
}, nil
}
func toGroup(dbu dbGroup) (auth.Group, error) {
ownerID, err := toString(dbu.OwnerID)
if err != nil {
return auth.Group{}, err
}
return auth.Group{
ID: dbu.ID,
Name: dbu.Name,
ParentID: dbu.ParentID.String,
OwnerID: ownerID,
Description: dbu.Description,
Metadata: auth.GroupMetadata(dbu.Metadata),
Level: dbu.Level,
Path: dbu.Path,
UpdatedAt: dbu.UpdatedAt,
CreatedAt: dbu.CreatedAt,
}, nil
}
type dbGroupRelation struct {
GroupID sql.NullString `db:"group_id"`
MemberID sql.NullString `db:"member_id"`
CreatedAt time.Time `db:"created_at"`
UpdatedAt time.Time `db:"updated_at"`
Type string `db:"type"`
}
func toDBGroupRelation(memberID, groupID, groupType string) (dbGroupRelation, error) {
var grID sql.NullString
if groupID != "" {
grID = sql.NullString{String: groupID, Valid: true}
}
var mID sql.NullString
if memberID != "" {
mID = sql.NullString{String: memberID, Valid: true}
}
return dbGroupRelation{
GroupID: grID,
MemberID: mID,
Type: groupType,
}, nil
}
func getGroupsMetadataQuery(db string, m auth.GroupMetadata) (mb []byte, mq string, err error) {
if len(m) > 0 {
mq = `metadata @> :metadata`
if db != "" {
mq = db + "." + mq
}
b, err := json.Marshal(m)
if err != nil {
return nil, "", errors.Wrap(err, errCreateMetadataQuery)
}
mb = b
}
return mb, mq, nil
}
func (gr groupRepository) processRows(rows *sqlx.Rows) ([]auth.Group, error) {
var items []auth.Group
for rows.Next() {
dbg := dbGroup{}
if err := rows.StructScan(&dbg); err != nil {
return items, err
}
group, err := toGroup(dbg)
if err != nil {
return items, err
}
items = append(items, group)
}
return items, nil
}
func total(ctx context.Context, db Database, query string, params interface{}) (uint64, error) {
rows, err := db.NamedQueryContext(ctx, query, params)
if err != nil {
return 0, errors.Wrap(errGetTotal, err)
}
defer rows.Close()
total := uint64(0)
if rows.Next() {
if err := rows.Scan(&total); err != nil {
return 0, errors.Wrap(errGetTotal, err)
}
}
return total, nil
}
// dbMetadata type for handling metadata properly in database/sql
type dbMetadata map[string]interface{}
// Scan - Implement the database/sql scanner interface
func (m *dbMetadata) Scan(value interface{}) error {
if value == nil {
return nil
}
b, ok := value.([]byte)
if !ok {
return users.ErrScanMetadata
}
if err := json.Unmarshal(b, m); err != nil {
return err
}
return nil
}
// Value Implements valuer
func (m dbMetadata) Value() (driver.Value, error) {
if len(m) == 0 {
return nil, nil
}
b, err := json.Marshal(m)
if err != nil {
return nil, err
}
return b, err
}
+777
View File
@@ -0,0 +1,777 @@
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
package postgres_test
import (
"context"
"fmt"
"strings"
"testing"
"time"
"github.com/mainflux/mainflux/auth"
"github.com/mainflux/mainflux/auth/postgres"
"github.com/mainflux/mainflux/pkg/errors"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
const (
maxNameSize = 254
maxDescSize = 1024
groupName = "Mainflux"
description = "description"
)
var (
invalidName = strings.Repeat("m", maxNameSize+1)
invalidDesc = strings.Repeat("m", maxDescSize+1)
metadata = auth.GroupMetadata{
"admin": "true",
}
)
func generateGroupID(t *testing.T) string {
grpID, err := ulidProvider.ID()
require.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err))
return grpID
}
func TestGroupSave(t *testing.T) {
t.Cleanup(func() { cleanUp(t) })
dbMiddleware := postgres.NewDatabase(db)
groupRepo := postgres.NewGroupRepo(dbMiddleware)
usrID, err := idProvider.ID()
require.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err))
wrongID, err := ulidProvider.ID()
require.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err))
grpID := generateGroupID(t)
cases := []struct {
desc string
group auth.Group
err error
}{
{
desc: "create new group",
group: auth.Group{
ID: grpID,
OwnerID: usrID,
Name: groupName,
},
err: nil,
},
{
desc: "create new group with existing name",
group: auth.Group{
ID: grpID,
OwnerID: usrID,
Name: groupName,
},
err: auth.ErrGroupConflict,
},
{
desc: "create group with invalid name",
group: auth.Group{
ID: generateGroupID(t),
OwnerID: usrID,
Name: invalidName,
},
err: auth.ErrMalformedEntity,
},
{
desc: "create group with invalid description",
group: auth.Group{
ID: generateGroupID(t),
OwnerID: usrID,
Name: groupName,
Description: invalidDesc,
},
err: auth.ErrMalformedEntity,
},
{
desc: "create group with parent",
group: auth.Group{
ID: generateGroupID(t),
ParentID: grpID,
OwnerID: usrID,
Name: "withParent",
},
err: nil,
},
{
desc: "create group with parent and existing name",
group: auth.Group{
ID: generateGroupID(t),
ParentID: grpID,
OwnerID: usrID,
Name: groupName,
},
err: nil,
},
{
desc: "create group with wrong parent",
group: auth.Group{
ID: generateGroupID(t),
ParentID: wrongID,
OwnerID: usrID,
Name: "wrongParent",
},
err: auth.ErrCreateGroup,
},
}
for _, tc := range cases {
_, err := groupRepo.Save(context.Background(), tc.group)
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
}
}
func TestGroupRetrieveByID(t *testing.T) {
t.Cleanup(func() { cleanUp(t) })
dbMiddleware := postgres.NewDatabase(db)
groupRepo := postgres.NewGroupRepo(dbMiddleware)
uid, err := idProvider.ID()
require.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err))
require.Nil(t, err, fmt.Sprintf("group id unexpected error: %s", err))
group1 := auth.Group{
ID: generateGroupID(t),
Name: groupName + "TestGroupRetrieveByID1",
OwnerID: uid,
}
_, err = groupRepo.Save(context.Background(), group1)
require.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err))
retrieved, err := groupRepo.RetrieveByID(context.Background(), group1.ID)
require.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err))
assert.True(t, retrieved.ID == group1.ID, fmt.Sprintf("Save group, ID: expected %s got %s\n", group1.ID, retrieved.ID))
// Round to milliseconds as otherwise saving and retriving from DB
// adds rounding error.
creationTime := time.Now().UTC().Round(time.Millisecond)
group2 := auth.Group{
ID: generateGroupID(t),
Name: groupName + "TestGroupRetrieveByID",
OwnerID: uid,
ParentID: group1.ID,
CreatedAt: creationTime,
UpdatedAt: creationTime,
Description: description,
Metadata: metadata,
}
_, err = groupRepo.Save(context.Background(), group2)
require.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err))
retrieved, err = groupRepo.RetrieveByID(context.Background(), group2.ID)
require.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err))
assert.True(t, retrieved.ID == group2.ID, fmt.Sprintf("Save group, ID: expected %s got %s\n", group2.ID, retrieved.ID))
assert.True(t, retrieved.CreatedAt.Equal(creationTime), fmt.Sprintf("Save group, CreatedAt: expected %s got %s\n", creationTime, retrieved.CreatedAt))
assert.True(t, retrieved.UpdatedAt.Equal(creationTime), fmt.Sprintf("Save group, UpdatedAt: expected %s got %s\n", creationTime, retrieved.UpdatedAt))
assert.True(t, retrieved.Level == 2, fmt.Sprintf("Save group, Level: expected %d got %d\n", retrieved.Level, 2))
assert.True(t, retrieved.ParentID == group1.ID, fmt.Sprintf("Save group, Level: expected %s got %s\n", group1.ID, retrieved.ParentID))
assert.True(t, retrieved.Description == description, fmt.Sprintf("Save group, Description: expected %v got %v\n", retrieved.Description, description))
assert.True(t, retrieved.Path == fmt.Sprintf("%s.%s", group1.ID, group2.ID), fmt.Sprintf("Save group, Path: expected %s got %s\n", fmt.Sprintf("%s.%s", group1.ID, group2.ID), retrieved.Path))
retrieved, err = groupRepo.RetrieveByID(context.Background(), generateGroupID(t))
assert.True(t, errors.Contains(err, auth.ErrGroupNotFound), fmt.Sprintf("Retrieve group: expected %s got %s\n", auth.ErrGroupNotFound, err))
}
func TestGroupUpdate(t *testing.T) {
t.Cleanup(func() { cleanUp(t) })
dbMiddleware := postgres.NewDatabase(db)
groupRepo := postgres.NewGroupRepo(dbMiddleware)
uid, err := idProvider.ID()
require.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err))
creationTime := time.Now().UTC()
updateTime := time.Now().UTC()
groupID := generateGroupID(t)
group := auth.Group{
ID: groupID,
Name: groupName + "TestGroupUpdate",
OwnerID: uid,
CreatedAt: creationTime,
UpdatedAt: creationTime,
Description: description,
Metadata: metadata,
}
_, err = groupRepo.Save(context.Background(), group)
require.Nil(t, err, fmt.Sprintf("group save got unexpected error: %s", err))
retrieved, err := groupRepo.RetrieveByID(context.Background(), group.ID)
require.Nil(t, err, fmt.Sprintf("group save got unexpected error: %s", err))
cases := []struct {
desc string
groupUpdate auth.Group
groupExpected auth.Group
err error
}{
{
desc: "update group for existing id",
groupUpdate: auth.Group{
ID: groupID,
Name: groupName + "Updated",
UpdatedAt: updateTime,
Metadata: auth.GroupMetadata{"admin": "false"},
},
groupExpected: auth.Group{
Name: groupName + "Updated",
UpdatedAt: updateTime,
Metadata: auth.GroupMetadata{"admin": "false"},
CreatedAt: retrieved.CreatedAt,
Path: retrieved.Path,
ParentID: retrieved.ParentID,
ID: retrieved.ID,
Level: retrieved.Level,
},
err: nil,
},
{
desc: "update group for non-existing id",
groupUpdate: auth.Group{
ID: "wrong",
Name: groupName + "-2",
},
err: auth.ErrUpdateGroup,
},
{
desc: "update group for invalid name",
groupUpdate: auth.Group{
ID: groupID,
Name: invalidName,
},
err: auth.ErrMalformedEntity,
},
{
desc: "update group for invalid description",
groupUpdate: auth.Group{
ID: groupID,
Description: invalidDesc,
},
err: auth.ErrMalformedEntity,
},
}
for _, tc := range cases {
updated, err := groupRepo.Update(context.Background(), tc.groupUpdate)
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
if tc.desc == "update group for existing id" {
assert.True(t, updated.Level == tc.groupExpected.Level, fmt.Sprintf("%s:Level: expected %d got %d\n", tc.desc, tc.groupExpected.Level, updated.Level))
assert.True(t, updated.Name == tc.groupExpected.Name, fmt.Sprintf("%s:Name: expected %s got %s\n", tc.desc, tc.groupExpected.Name, updated.Name))
assert.True(t, updated.Metadata["admin"] == tc.groupExpected.Metadata["admin"], fmt.Sprintf("%s:Level: expected %d got %d\n", tc.desc, tc.groupExpected.Metadata["admin"], updated.Metadata["admin"]))
}
}
}
func TestGroupDelete(t *testing.T) {
t.Cleanup(func() { cleanUp(t) })
dbMiddleware := postgres.NewDatabase(db)
groupRepo := postgres.NewGroupRepo(dbMiddleware)
uid, err := idProvider.ID()
require.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err))
creationTime := time.Now().UTC()
groupParent := auth.Group{
ID: generateGroupID(t),
Name: groupName + "Updated",
OwnerID: uid,
CreatedAt: creationTime,
UpdatedAt: creationTime,
}
groupParent, err = groupRepo.Save(context.Background(), groupParent)
require.Nil(t, err, fmt.Sprintf("group save got unexpected error: %s", err))
creationTime = time.Now().UTC()
groupChild1 := auth.Group{
ID: generateGroupID(t),
ParentID: groupParent.ID,
Name: groupName + "child1",
OwnerID: uid,
CreatedAt: creationTime,
UpdatedAt: creationTime,
}
creationTime = time.Now().UTC()
groupChild2 := auth.Group{
ID: generateGroupID(t),
ParentID: groupParent.ID,
Name: groupName + "child2",
OwnerID: uid,
CreatedAt: creationTime,
UpdatedAt: creationTime,
}
meta := auth.PageMetadata{
Level: auth.MaxLevel,
}
groupChild1, err = groupRepo.Save(context.Background(), groupChild1)
require.Nil(t, err, fmt.Sprintf("group save got unexpected error: %s", err))
groupChild2, err = groupRepo.Save(context.Background(), groupChild2)
require.Nil(t, err, fmt.Sprintf("group save got unexpected error: %s", err))
gp, err := groupRepo.RetrieveAllChildren(context.Background(), groupParent.ID, meta)
assert.True(t, errors.Contains(err, nil), fmt.Sprintf("Retrieve children for parent: expected %v got %v\n", nil, err))
assert.True(t, gp.Total == 3, fmt.Sprintf("Number of children + parent: expected %d got %d\n", 3, gp.Total))
thingID, err := idProvider.ID()
require.Nil(t, err, fmt.Sprintf("thing id create unexpected error: %s", err))
err = groupRepo.Assign(context.Background(), groupChild1.ID, "things", thingID)
require.Nil(t, err, fmt.Sprintf("thing assign got unexpected error: %s", err))
err = groupRepo.Delete(context.Background(), groupChild1.ID)
assert.True(t, errors.Contains(err, auth.ErrGroupNotEmpty), fmt.Sprintf("delete non empty group: expected %v got %v\n", auth.ErrGroupNotEmpty, err))
err = groupRepo.Delete(context.Background(), groupChild2.ID)
assert.True(t, errors.Contains(err, nil), fmt.Sprintf("delete empty group: expected %v got %v\n", nil, err))
err = groupRepo.Delete(context.Background(), groupParent.ID)
assert.True(t, errors.Contains(err, auth.ErrGroupNotEmpty), fmt.Sprintf("delete parent with children with members: expected %v got %v\n", auth.ErrGroupNotEmpty, err))
gp, err = groupRepo.RetrieveAllChildren(context.Background(), groupParent.ID, meta)
assert.True(t, errors.Contains(err, nil), fmt.Sprintf("retrieve children after one child removed: expected %v got %v\n", nil, err))
assert.True(t, gp.Total == 2, fmt.Sprintf("number of children + parent: expected %d got %d\n", 2, gp.Total))
err = groupRepo.Unassign(context.Background(), groupChild1.ID, thingID)
require.Nil(t, err, fmt.Sprintf("failed to remove thing from a group error: %s", err))
err = groupRepo.Delete(context.Background(), groupParent.ID)
assert.True(t, errors.Contains(err, nil), fmt.Sprintf("delete parent with children with no members: expected %v got %v\n", nil, err))
_, err = groupRepo.RetrieveByID(context.Background(), groupChild1.ID)
assert.True(t, errors.Contains(err, auth.ErrGroupNotFound), fmt.Sprintf("retrieve child after parent removed: expected %v got %v\n", nil, err))
}
func TestRetrieveAll(t *testing.T) {
t.Cleanup(func() { cleanUp(t) })
dbMiddleware := postgres.NewDatabase(db)
groupRepo := postgres.NewGroupRepo(dbMiddleware)
uid, err := idProvider.ID()
require.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err))
metadata := auth.PageMetadata{
Metadata: auth.GroupMetadata{
"field": "value",
},
Level: auth.MaxLevel,
}
wrongMeta := auth.PageMetadata{
Metadata: auth.GroupMetadata{
"wrong": "wrong",
},
Level: auth.MaxLevel,
}
metaNum := uint64(3)
n := uint64(auth.MaxLevel)
parentID := ""
for i := uint64(0); i < n; i++ {
creationTime := time.Now().UTC()
group := auth.Group{
ID: generateGroupID(t),
Name: fmt.Sprintf("%s-%d", groupName, i),
OwnerID: uid,
ParentID: parentID,
CreatedAt: creationTime,
UpdatedAt: creationTime,
}
// Create Groups with metadata.
if i < metaNum {
group.Metadata = metadata.Metadata
}
_, err = groupRepo.Save(context.Background(), group)
require.Nil(t, err, fmt.Sprintf("unexpected error: %s\n", err))
parentID = group.ID
}
cases := map[string]struct {
Size uint64
Metadata auth.PageMetadata
}{
"retrieve all groups": {
Metadata: auth.PageMetadata{
Total: n,
Limit: n,
Level: auth.MaxLevel,
},
Size: n,
},
"retrieve groups with existing metadata": {
Metadata: auth.PageMetadata{
Total: metaNum,
Limit: n,
Level: auth.MaxLevel,
Metadata: metadata.Metadata,
},
Size: metaNum,
},
"retrieve groups with non-existing metadata": {
Metadata: auth.PageMetadata{
Total: uint64(0),
Limit: n,
Level: auth.MaxLevel,
Metadata: wrongMeta.Metadata,
},
Size: uint64(0),
},
"retrieve groups with hierarchy level depth": {
Metadata: auth.PageMetadata{
Total: uint64(metaNum),
Limit: n,
Level: auth.MaxLevel,
Metadata: metadata.Metadata,
},
Size: uint64(metaNum),
},
"retrieve groups with hierarchy level depth and existing metadata": {
Metadata: auth.PageMetadata{
Total: uint64(metaNum),
Limit: n,
Level: auth.MaxLevel,
Metadata: metadata.Metadata,
},
Size: uint64(metaNum),
},
}
for desc, tc := range cases {
page, err := groupRepo.RetrieveAll(context.Background(), tc.Metadata)
size := len(page.Groups)
assert.Equal(t, tc.Size, uint64(size), fmt.Sprintf("%s: expected size %d got %d\n", desc, tc.Size, size))
assert.Equal(t, tc.Metadata.Total, page.Total, fmt.Sprintf("%s: expected total %d got %d\n", desc, tc.Metadata.Total, page.Total))
assert.Nil(t, err, fmt.Sprintf("%s: expected no error got %d\n", desc, err))
}
}
func TestRetrieveAllParents(t *testing.T) {
t.Cleanup(func() { cleanUp(t) })
dbMiddleware := postgres.NewDatabase(db)
groupRepo := postgres.NewGroupRepo(dbMiddleware)
uid, err := idProvider.ID()
require.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err))
metadata := auth.GroupMetadata{
"field": "value",
}
wrongMeta := auth.GroupMetadata{
"wrong": "wrong",
}
p, err := groupRepo.RetrieveAll(context.Background(), auth.PageMetadata{Level: auth.MaxLevel})
require.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err))
assert.Equal(t, uint64(0), p.Total, fmt.Sprintf("expected total %d got %d\n", 0, p.Total))
metaNum := uint64(3)
n := uint64(10)
parentID := ""
parentMiddle := ""
for i := uint64(0); i < n; i++ {
creationTime := time.Now().UTC()
group := auth.Group{
ID: generateGroupID(t),
Name: fmt.Sprintf("%s-%d", groupName, i),
OwnerID: uid,
ParentID: parentID,
CreatedAt: creationTime,
UpdatedAt: creationTime,
}
// Create Groups with metadata.
if n-i <= metaNum {
group.Metadata = metadata
}
if i == n/2 {
parentMiddle = group.ID
}
_, err = groupRepo.Save(context.Background(), group)
require.Nil(t, err, fmt.Sprintf("unexpected error: %s\n", err))
parentID = group.ID
}
cases := map[string]struct {
level uint64
parentID string
Size uint64
Total uint64
Metadata auth.GroupMetadata
}{
"retrieve all parents": {
Total: n,
Size: auth.MaxLevel + 1,
level: auth.MaxLevel,
parentID: parentID,
},
"retrieve groups with existing metadata": {
Total: metaNum,
Size: metaNum,
Metadata: metadata,
parentID: parentID,
level: auth.MaxLevel,
},
"retrieve groups with non-existing metadata": {
Total: uint64(0),
Metadata: wrongMeta,
Size: uint64(0),
level: auth.MaxLevel,
parentID: parentID,
},
"retrieve groups with hierarchy level depth": {
Total: n,
Size: 2 + 1,
level: uint64(2),
parentID: parentID,
},
"retrieve groups with hierarchy level depth and existing metadata": {
Total: metaNum,
Size: metaNum,
level: 3,
Metadata: metadata,
parentID: parentID,
},
"retrieve parent groups from children in the middle": {
Total: n/2 + 1,
Size: n/2 + 1,
level: auth.MaxLevel,
parentID: parentMiddle,
},
}
for desc, tc := range cases {
page, err := groupRepo.RetrieveAllParents(context.Background(), tc.parentID, auth.PageMetadata{Level: tc.level, Metadata: tc.Metadata})
size := len(page.Groups)
assert.Equal(t, tc.Size, uint64(size), fmt.Sprintf("%s: expected size %d got %d\n", desc, tc.Size, size))
assert.Equal(t, tc.Total, page.Total, fmt.Sprintf("%s: expected total %d got %d\n", desc, tc.Total, page.Total))
assert.Nil(t, err, fmt.Sprintf("%s: expected no error got %d\n", desc, err))
}
}
func TestRetrieveAllChildren(t *testing.T) {
t.Cleanup(func() { cleanUp(t) })
dbMiddleware := postgres.NewDatabase(db)
groupRepo := postgres.NewGroupRepo(dbMiddleware)
uid, err := idProvider.ID()
require.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err))
metadata := auth.GroupMetadata{
"field": "value",
}
wrongMeta := auth.GroupMetadata{
"wrong": "wrong",
}
metaNum := uint64(3)
n := uint64(10)
groupID := generateGroupID(t)
firstParentID := groupID
parentID := ""
parentMiddle := ""
for i := uint64(0); i < n; i++ {
creationTime := time.Now().UTC()
group := auth.Group{
ID: groupID,
Name: fmt.Sprintf("%s-%d", groupName, i),
OwnerID: uid,
ParentID: parentID,
CreatedAt: creationTime,
UpdatedAt: creationTime,
}
// Create Groups with metadata.
if i < metaNum {
group.Metadata = metadata
}
if i == n/2 {
parentMiddle = group.ID
}
_, err = groupRepo.Save(context.Background(), group)
require.Nil(t, err, fmt.Sprintf("unexpected error: %s\n", err))
parentID = group.ID
groupID = generateGroupID(t)
}
p, err := groupRepo.RetrieveAll(context.Background(), auth.PageMetadata{Level: auth.MaxLevel})
require.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err))
assert.Equal(t, n, p.Total, fmt.Sprintf("expected total %d got %d\n", n, p.Total))
cases := map[string]struct {
parentID string
size uint64
total uint64
metadata auth.PageMetadata
}{
"retrieve all children": {
size: auth.MaxLevel,
total: n,
metadata: auth.PageMetadata{
Level: auth.MaxLevel,
},
parentID: firstParentID,
},
"retrieve groups with existing metadata": {
size: metaNum,
total: metaNum,
metadata: auth.PageMetadata{
Level: auth.MaxLevel,
Metadata: metadata,
},
parentID: firstParentID,
},
"retrieve groups with non-existing metadata": {
total: 0,
size: 0,
metadata: auth.PageMetadata{
Level: auth.MaxLevel,
Metadata: wrongMeta,
},
parentID: firstParentID,
},
"retrieve groups with hierarchy level depth": {
total: n,
size: 2,
metadata: auth.PageMetadata{
Level: 2,
},
parentID: firstParentID,
},
"retrieve groups with hierarchy level depth and existing metadata": {
total: metaNum,
size: metaNum,
metadata: auth.PageMetadata{
Level: 3,
Metadata: metadata,
},
parentID: firstParentID,
},
"retrieve parent groups from children in the middle": {
total: n / 2,
size: n / 2,
metadata: auth.PageMetadata{
Level: auth.MaxLevel,
},
parentID: parentMiddle,
},
}
for desc, tc := range cases {
page, err := groupRepo.RetrieveAllChildren(context.Background(), tc.parentID, tc.metadata)
size := len(page.Groups)
assert.Equal(t, tc.size, uint64(size), fmt.Sprintf("%s: expected size %d got %d\n", desc, tc.size, size))
assert.Equal(t, tc.total, page.Total, fmt.Sprintf("%s: expected total %d got %d\n", desc, tc.total, page.Total))
assert.Nil(t, err, fmt.Sprintf("%s: expected no error got %d\n", desc, err))
}
}
func TestAssign(t *testing.T) {
t.Cleanup(func() { cleanUp(t) })
dbMiddleware := postgres.NewDatabase(db)
groupRepo := postgres.NewGroupRepo(dbMiddleware)
uid, err := idProvider.ID()
require.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err))
creationTime := time.Now().UTC()
group := auth.Group{
ID: generateGroupID(t),
Name: groupName + "Updated",
OwnerID: uid,
CreatedAt: creationTime,
UpdatedAt: creationTime,
}
pm := auth.PageMetadata{
Offset: 0,
Limit: 10,
}
group, err = groupRepo.Save(context.Background(), group)
require.Nil(t, err, fmt.Sprintf("group save got unexpected error: %s", err))
mid, err := idProvider.ID()
require.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err))
err = groupRepo.Assign(context.Background(), group.ID, "things", mid)
require.Nil(t, err, fmt.Sprintf("member assign save unexpected error: %s", err))
mp, err := groupRepo.Members(context.Background(), group.ID, "things", pm)
require.Nil(t, err, fmt.Sprintf("member assign save unexpected error: %s", err))
assert.True(t, mp.Total == 1, fmt.Sprintf("retrieve members of a group: expected %d got %d\n", 1, mp.Total))
err = groupRepo.Assign(context.Background(), group.ID, "things", mid)
assert.True(t, errors.Contains(err, auth.ErrMemberAlreadyAssigned), fmt.Sprintf("assign member again: expected %v got %v\n", auth.ErrMemberAlreadyAssigned, err))
}
func TestUnassign(t *testing.T) {
t.Cleanup(func() { cleanUp(t) })
dbMiddleware := postgres.NewDatabase(db)
groupRepo := postgres.NewGroupRepo(dbMiddleware)
uid, err := idProvider.ID()
require.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err))
creationTime := time.Now().UTC()
group := auth.Group{
ID: generateGroupID(t),
Name: groupName + "Updated",
OwnerID: uid,
CreatedAt: creationTime,
UpdatedAt: creationTime,
}
pm := auth.PageMetadata{
Offset: 0,
Limit: 10,
}
group, err = groupRepo.Save(context.Background(), group)
require.Nil(t, err, fmt.Sprintf("group save got unexpected error: %s", err))
mid, err := idProvider.ID()
require.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err))
err = groupRepo.Assign(context.Background(), group.ID, "things", mid)
require.Nil(t, err, fmt.Sprintf("member assign unexpected error: %s", err))
mid, err = idProvider.ID()
require.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err))
err = groupRepo.Assign(context.Background(), group.ID, "things", mid)
require.Nil(t, err, fmt.Sprintf("member assign unexpected error: %s", err))
mp, err := groupRepo.Members(context.Background(), group.ID, "things", pm)
require.Nil(t, err, fmt.Sprintf("member assign save unexpected error: %s", err))
assert.True(t, mp.Total == 2, fmt.Sprintf("retrieve members of a group: expected %d got %d\n", 2, mp.Total))
err = groupRepo.Unassign(context.Background(), group.ID, mid)
require.Nil(t, err, fmt.Sprintf("member unassign save unexpected error: %s", err))
mp, err = groupRepo.Members(context.Background(), group.ID, "things", pm)
require.Nil(t, err, fmt.Sprintf("members retrieve unexpected error: %s", err))
assert.True(t, mp.Total == 1, fmt.Sprintf("retrieve members of a group: expected %d got %d\n", 1, mp.Total))
}
func cleanUp(t *testing.T) {
_, err := db.Exec("delete from group_relations")
require.Nil(t, err, fmt.Sprintf("clean relations unexpected error: %s", err))
_, err = db.Exec("delete from groups")
require.Nil(t, err, fmt.Sprintf("clean groups unexpected error: %s", err))
}
+117
View File
@@ -0,0 +1,117 @@
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
package postgres
import (
"fmt"
"github.com/jmoiron/sqlx"
migrate "github.com/rubenv/sql-migrate"
)
// Config defines the options that are used when connecting to a PostgreSQL instance
type Config struct {
Host string
Port string
User string
Pass string
Name string
SSLMode string
SSLCert string
SSLKey string
SSLRootCert string
}
// Connect creates a connection to the PostgreSQL instance and applies any
// unapplied database migrations. A non-nil error is returned to indicate failure.
func Connect(cfg Config) (*sqlx.DB, error) {
url := fmt.Sprintf("host=%s port=%s user=%s dbname=%s password=%s sslmode=%s sslcert=%s sslkey=%s sslrootcert=%s", cfg.Host, cfg.Port, cfg.User, cfg.Name, cfg.Pass, cfg.SSLMode, cfg.SSLCert, cfg.SSLKey, cfg.SSLRootCert)
db, err := sqlx.Open("postgres", url)
if err != nil {
return nil, err
}
if err := migrateDB(db); err != nil {
return nil, err
}
return db, nil
}
func migrateDB(db *sqlx.DB) error {
migrations := &migrate.MemoryMigrationSource{
Migrations: []*migrate.Migration{
{
Id: "auth_1",
Up: []string{
`CREATE TABLE IF NOT EXISTS keys (
id VARCHAR(254) NOT NULL,
type SMALLINT,
subject VARCHAR(254) NOT NULL,
issuer_id UUID NOT NULL,
issued_at TIMESTAMP NOT NULL,
expires_at TIMESTAMP,
PRIMARY KEY (id, issuer_id)
)`,
`CREATE EXTENSION IF NOT EXISTS LTREE`,
`CREATE TABLE IF NOT EXISTS groups (
id VARCHAR(254) UNIQUE NOT NULL,
parent_id VARCHAR(254),
owner_id VARCHAR(254),
name VARCHAR(254) NOT NULL,
description VARCHAR(1024),
metadata JSONB,
path LTREE,
created_at TIMESTAMPTZ,
updated_at TIMESTAMPTZ,
UNIQUE (owner_id, name, parent_id),
FOREIGN KEY (parent_id) REFERENCES groups (id) ON DELETE CASCADE
)`,
`CREATE TABLE IF NOT EXISTS group_relations (
member_id VARCHAR(254) NOT NULL,
group_id VARCHAR(254) NOT NULL,
type VARCHAR(254),
created_at TIMESTAMPTZ,
updated_at TIMESTAMPTZ,
FOREIGN KEY (group_id) REFERENCES groups (id),
PRIMARY KEY (member_id, group_id)
)`,
`CREATE INDEX path_gist_idx ON groups USING GIST (path);`,
`CREATE OR REPLACE FUNCTION inherit_group()
RETURNS trigger
LANGUAGE PLPGSQL
AS
$$
BEGIN
IF NEW.parent_id IS NULL OR NEW.parent_id = '' THEN
RETURN NEW;
END IF;
IF NOT EXISTS (SELECT id FROM groups WHERE id = NEW.parent_id) THEN
RAISE EXCEPTION 'wrong parent id';
END IF;
SELECT text2ltree(ltree2text(path) || '.' || NEW.id) INTO NEW.path FROM groups WHERE id = NEW.parent_id;
RETURN NEW;
END;
$$`,
`CREATE TRIGGER inherit_group_tr
BEFORE INSERT
ON groups
FOR EACH ROW
EXECUTE PROCEDURE inherit_group();`,
},
Down: []string{
`DROP TABLE IF EXISTS keys`,
`DROP EXTENSION IF EXISTS LTREE`,
`DROP TABLE IF EXISTS groups`,
`DROP TABLE IF EXISTS group_relations`,
`DROP FUNCTION IF EXISTS inherit_group`,
`DROP TRIGGER IF EXISTS inherit_group_tr ON groups`,
},
},
},
}
_, err := migrate.Exec(db.DB, "postgres", migrations, migrate.Up)
return err
}
+122
View File
@@ -0,0 +1,122 @@
package postgres
import (
"context"
"database/sql"
"time"
"github.com/lib/pq"
"github.com/mainflux/mainflux/auth"
"github.com/mainflux/mainflux/pkg/errors"
)
var (
errSave = errors.New("failed to save key in database")
errRetrieve = errors.New("failed to retrieve key from database")
errDelete = errors.New("failed to delete key from database")
)
var _ auth.KeyRepository = (*repo)(nil)
const (
errDuplicate = "unique_violation"
errInvalid = "invalid_text_representation"
)
type repo struct {
db Database
}
// New instantiates a PostgreSQL implementation of key repository.
func New(db Database) auth.KeyRepository {
return &repo{
db: db,
}
}
func (kr repo) Save(ctx context.Context, key auth.Key) (string, error) {
q := `INSERT INTO keys (id, type, issuer_id, subject, issued_at, expires_at)
VALUES (:id, :type, :issuer_id, :subject, :issued_at, :expires_at)`
dbKey := toDBKey(key)
if _, err := kr.db.NamedExecContext(ctx, q, dbKey); err != nil {
pqErr, ok := err.(*pq.Error)
if ok {
if pqErr.Code.Name() == errDuplicate {
return "", errors.Wrap(auth.ErrConflict, pqErr)
}
}
return "", errors.Wrap(errSave, err)
}
return dbKey.ID, nil
}
func (kr repo) Retrieve(ctx context.Context, issuerID, id string) (auth.Key, error) {
q := `SELECT id, type, issuer_id, subject, issued_at, expires_at FROM keys WHERE issuer_id = $1 AND id = $2`
key := dbKey{}
if err := kr.db.QueryRowxContext(ctx, q, issuerID, id).StructScan(&key); err != nil {
pqErr, ok := err.(*pq.Error)
if err == sql.ErrNoRows || ok && errInvalid == pqErr.Code.Name() {
return auth.Key{}, errors.Wrap(auth.ErrNotFound, err)
}
return auth.Key{}, errors.Wrap(errRetrieve, err)
}
return toKey(key), nil
}
func (kr repo) Remove(ctx context.Context, issuerID, id string) error {
q := `DELETE FROM keys WHERE issuer_id = :issuer_id AND id = :id`
key := dbKey{
ID: id,
IssuerID: issuerID,
}
if _, err := kr.db.NamedExecContext(ctx, q, key); err != nil {
return errors.Wrap(errDelete, err)
}
return nil
}
type dbKey struct {
ID string `db:"id"`
Type uint32 `db:"type"`
IssuerID string `db:"issuer_id"`
Subject string `db:"subject"`
Revoked bool `db:"revoked"`
IssuedAt time.Time `db:"issued_at"`
ExpiresAt sql.NullTime `db:"expires_at"`
}
func toDBKey(key auth.Key) dbKey {
ret := dbKey{
ID: key.ID,
Type: key.Type,
IssuerID: key.IssuerID,
Subject: key.Subject,
IssuedAt: key.IssuedAt,
}
if !key.ExpiresAt.IsZero() {
ret.ExpiresAt = sql.NullTime{Time: key.ExpiresAt, Valid: true}
}
return ret
}
func toKey(key dbKey) auth.Key {
ret := auth.Key{
ID: key.ID,
Type: key.Type,
IssuerID: key.IssuerID,
Subject: key.Subject,
IssuedAt: key.IssuedAt,
}
if key.ExpiresAt.Valid {
ret.ExpiresAt = key.ExpiresAt.Time
}
return ret
}
+160
View File
@@ -0,0 +1,160 @@
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
package postgres_test
import (
"context"
"fmt"
"testing"
"time"
"github.com/mainflux/mainflux/auth"
"github.com/mainflux/mainflux/auth/postgres"
"github.com/mainflux/mainflux/pkg/errors"
"github.com/mainflux/mainflux/pkg/ulid"
"github.com/mainflux/mainflux/pkg/uuid"
"github.com/opentracing/opentracing-go"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
const email = "user-save@example.com"
var (
expTime = time.Now().Add(5 * time.Minute)
idProvider = uuid.New()
ulidProvider = ulid.New()
)
func TestKeySave(t *testing.T) {
dbMiddleware := postgres.NewDatabase(db)
repo := postgres.New(dbMiddleware)
id, err := idProvider.ID()
require.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err))
cases := []struct {
desc string
key auth.Key
err error
}{
{
desc: "save a new key",
key: auth.Key{
Subject: email,
IssuedAt: time.Now(),
ExpiresAt: expTime,
ID: id,
IssuerID: id,
},
err: nil,
},
{
desc: "save with duplicate id",
key: auth.Key{
Subject: email,
IssuedAt: time.Now(),
ExpiresAt: expTime,
ID: id,
IssuerID: id,
},
err: auth.ErrConflict,
},
}
for _, tc := range cases {
_, err := repo.Save(context.Background(), tc.key)
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
}
}
func TestKeyRetrieve(t *testing.T) {
dbMiddleware := postgres.NewDatabase(db)
repo := postgres.New(dbMiddleware)
id, err := idProvider.ID()
require.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err))
key := auth.Key{
Subject: email,
IssuedAt: time.Now(),
ExpiresAt: expTime,
ID: id,
IssuerID: id,
}
_, err = repo.Save(context.Background(), key)
assert.Nil(t, err, fmt.Sprintf("Storing Key expected to succeed: %s", err))
cases := []struct {
desc string
id string
owner string
err error
}{
{
desc: "retrieve an existing key",
id: key.ID,
owner: key.IssuerID,
err: nil,
},
{
desc: "retrieve unauthorized",
id: key.ID,
owner: "",
err: auth.ErrNotFound,
},
{
desc: "retrieve unknown key",
id: "",
owner: key.IssuerID,
err: auth.ErrNotFound,
},
}
for _, tc := range cases {
_, err := repo.Retrieve(context.Background(), tc.owner, tc.id)
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
}
}
func TestKeyRemove(t *testing.T) {
dbMiddleware := postgres.NewDatabase(db)
repo := postgres.New(dbMiddleware)
id, err := idProvider.ID()
require.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err))
key := auth.Key{
Subject: email,
IssuedAt: time.Now(),
ExpiresAt: expTime,
ID: id,
IssuerID: id,
}
_, err = repo.Save(opentracing.ContextWithSpan(context.Background(), opentracing.StartSpan("")), key)
assert.Nil(t, err, fmt.Sprintf("Storing Key expected to succeed: %s", err))
cases := []struct {
desc string
id string
owner string
err error
}{
{
desc: "remove an existing key",
id: key.ID,
owner: key.IssuerID,
err: nil,
},
{
desc: "remove key that does not exist",
id: key.ID,
owner: key.IssuerID,
err: nil,
},
}
for _, tc := range cases {
err := repo.Remove(context.Background(), tc.owner, tc.id)
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
}
}
+76
View File
@@ -0,0 +1,76 @@
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
// Package postgres_test contains tests for PostgreSQL repository
// implementations.
package postgres_test
import (
"database/sql"
"fmt"
"log"
"os"
"testing"
"github.com/jmoiron/sqlx"
"github.com/mainflux/mainflux/auth/postgres"
dockertest "github.com/ory/dockertest/v3"
)
var db *sqlx.DB
func TestMain(m *testing.M) {
pool, err := dockertest.NewPool("")
if err != nil {
log.Fatalf("Could not connect to docker: %s", err)
}
cfg := []string{
"POSTGRES_USER=test",
"POSTGRES_PASSWORD=test",
"POSTGRES_DB=test",
}
container, err := pool.Run("postgres", "10.8-alpine", cfg)
if err != nil {
log.Fatalf("Could not start container: %s", err)
}
port := container.GetPort("5432/tcp")
if err := pool.Retry(func() error {
url := fmt.Sprintf("host=localhost port=%s user=test dbname=test password=test sslmode=disable", port)
db, err := sql.Open("postgres", url)
if err != nil {
return err
}
return db.Ping()
}); err != nil {
log.Fatalf("Could not connect to docker: %s", err)
}
dbConfig := postgres.Config{
Host: "localhost",
Port: port,
User: "test",
Pass: "test",
Name: "test",
SSLMode: "disable",
SSLCert: "",
SSLKey: "",
SSLRootCert: "",
}
if db, err = postgres.Connect(dbConfig); err != nil {
log.Fatalf("Could not setup test DB connection: %s", err)
}
code := m.Run()
// Defers will not be run when using os.Exit
db.Close()
if err := pool.Purge(container); err != nil {
log.Fatalf("Could not purge container: %s", err)
}
os.Exit(code)
}
+74
View File
@@ -0,0 +1,74 @@
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
package postgres
import (
"context"
"database/sql"
"github.com/jmoiron/sqlx"
"github.com/opentracing/opentracing-go"
)
var _ Database = (*database)(nil)
type database struct {
db *sqlx.DB
}
// Database provides a database interface
type Database interface {
NamedExecContext(context.Context, string, interface{}) (sql.Result, error)
QueryRowxContext(context.Context, string, ...interface{}) *sqlx.Row
QueryxContext(context.Context, string, ...interface{}) (*sqlx.Rows, error)
NamedQueryContext(context.Context, string, interface{}) (*sqlx.Rows, error)
BeginTxx(ctx context.Context, opts *sql.TxOptions) (*sqlx.Tx, error)
}
// NewDatabase creates a ThingDatabase instance
func NewDatabase(db *sqlx.DB) Database {
return &database{
db: db,
}
}
func (d database) NamedQueryContext(ctx context.Context, query string, args interface{}) (*sqlx.Rows, error) {
addSpanTags(ctx, query)
return d.db.NamedQueryContext(ctx, query, args)
}
func (d database) NamedExecContext(ctx context.Context, query string, args interface{}) (sql.Result, error) {
addSpanTags(ctx, query)
return d.db.NamedExecContext(ctx, query, args)
}
func (d database) QueryRowxContext(ctx context.Context, query string, args ...interface{}) *sqlx.Row {
addSpanTags(ctx, query)
return d.db.QueryRowxContext(ctx, query, args...)
}
func (d database) QueryxContext(ctx context.Context, query string, args ...interface{}) (*sqlx.Rows, error) {
addSpanTags(ctx, query)
return d.db.QueryxContext(ctx, query, args...)
}
func (d database) BeginTxx(ctx context.Context, opts *sql.TxOptions) (*sqlx.Tx, error) {
span := opentracing.SpanFromContext(ctx)
if span != nil {
span.SetTag("span.kind", "client")
span.SetTag("peer.service", "postgres")
span.SetTag("db.type", "sql")
}
return d.db.BeginTxx(ctx, opts)
}
func addSpanTags(ctx context.Context, query string) {
span := opentracing.SpanFromContext(ctx)
if span != nil {
span.SetTag("sql.statement", query)
span.SetTag("span.kind", "client")
span.SetTag("peer.service", "postgres")
span.SetTag("db.type", "sql")
}
}
+333
View File
@@ -0,0 +1,333 @@
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
package auth
import (
"context"
"time"
"github.com/mainflux/mainflux"
"github.com/mainflux/mainflux/pkg/errors"
"github.com/mainflux/mainflux/pkg/ulid"
)
const (
loginDuration = 10 * time.Hour
recoveryDuration = 5 * time.Minute
)
var (
// ErrUnauthorizedAccess represents unauthorized access.
ErrUnauthorizedAccess = errors.New("unauthorized access")
// ErrMalformedEntity indicates malformed entity specification (e.g.
// invalid owner or ID).
ErrMalformedEntity = errors.New("malformed entity specification")
// ErrNotFound indicates a non-existing entity request.
ErrNotFound = errors.New("entity not found")
// ErrGenerateGroupID indicates error in creating group.
ErrGenerateGroupID = errors.New("failed to generate group id")
// ErrConflict indicates that entity already exists.
ErrConflict = errors.New("entity already exists")
// ErrFailedToRetrieveMembers failed to retrieve group members.
ErrFailedToRetrieveMembers = errors.New("failed to retrieve group members")
// ErrFailedToRetrieveMembership failed to retrieve memberships
ErrFailedToRetrieveMembership = errors.New("failed to retrieve memberships")
// ErrFailedToRetrieveAll failed to retrieve groups.
ErrFailedToRetrieveAll = errors.New("failed to retrieve all groups")
// ErrFailedToRetrieveParents failed to retrieve groups.
ErrFailedToRetrieveParents = errors.New("failed to retrieve all groups")
// ErrFailedToRetrieveChildren failed to retrieve groups.
ErrFailedToRetrieveChildren = errors.New("failed to retrieve all groups")
errIssueUser = errors.New("failed to issue new user key")
errIssueTmp = errors.New("failed to issue new temporary key")
errRevoke = errors.New("failed to remove key")
errRetrieve = errors.New("failed to retrieve key data")
errIdentify = errors.New("failed to validate token")
)
// Authn specifies an API that must be fullfiled by the domain service
// implementation, and all of its decorators (e.g. logging & metrics).
// Token is a string value of the actual Key and is used to authenticate
// an Auth service request.
type Authn interface {
// Issue issues a new Key, returning its token value alongside.
Issue(ctx context.Context, token string, key Key) (Key, string, error)
// Revoke removes the Key with the provided id that is
// issued by the user identified by the provided key.
Revoke(ctx context.Context, token, id string) error
// Retrieve retrieves data for the Key identified by the provided
// ID, that is issued by the user identified by the provided key.
RetrieveKey(ctx context.Context, token, id string) (Key, error)
// Identify validates token token. If token is valid, content
// is returned. If token is invalid, or invocation failed for some
// other reason, non-nil error value is returned in response.
Identify(ctx context.Context, token string) (Identity, error)
}
// Authz specifies an API for the authorization and will be implemented
// by evaluation of policies.
type Authz interface {
// Authorize checks access rights
Authorize(ctx context.Context, token, sub, obj, act string) (bool, error)
}
// Service specifies an API that must be fullfiled by the domain service
// implementation, and all of its decorators (e.g. logging & metrics).
// Token is a string value of the actual Key and is used to authenticate
// an Auth service request.
type Service interface {
Authn
Authz
// Implements groups API, creating groups, assigning members
GroupService
}
var _ Service = (*service)(nil)
type service struct {
keys KeyRepository
groups GroupRepository
idProvider mainflux.IDProvider
ulidProvider mainflux.IDProvider
tokenizer Tokenizer
}
// New instantiates the auth service implementation.
func New(keys KeyRepository, groups GroupRepository, idp mainflux.IDProvider, tokenizer Tokenizer) Service {
return &service{
tokenizer: tokenizer,
keys: keys,
groups: groups,
idProvider: idp,
ulidProvider: ulid.New(),
}
}
func (svc service) Issue(ctx context.Context, token string, key Key) (Key, string, error) {
if key.IssuedAt.IsZero() {
return Key{}, "", ErrInvalidKeyIssuedAt
}
switch key.Type {
case APIKey:
return svc.userKey(ctx, token, key)
case RecoveryKey:
return svc.tmpKey(recoveryDuration, key)
default:
return svc.tmpKey(loginDuration, key)
}
}
func (svc service) Revoke(ctx context.Context, token, id string) error {
issuerID, _, err := svc.login(token)
if err != nil {
return errors.Wrap(errRevoke, err)
}
if err := svc.keys.Remove(ctx, issuerID, id); err != nil {
return errors.Wrap(errRevoke, err)
}
return nil
}
func (svc service) RetrieveKey(ctx context.Context, token, id string) (Key, error) {
issuerID, _, err := svc.login(token)
if err != nil {
return Key{}, errors.Wrap(errRetrieve, err)
}
return svc.keys.Retrieve(ctx, issuerID, id)
}
func (svc service) Identify(ctx context.Context, token string) (Identity, error) {
key, err := svc.tokenizer.Parse(token)
if err == ErrAPIKeyExpired {
err = svc.keys.Remove(ctx, key.IssuerID, key.ID)
return Identity{}, errors.Wrap(ErrAPIKeyExpired, err)
}
if err != nil {
return Identity{}, errors.Wrap(errIdentify, err)
}
switch key.Type {
case APIKey, RecoveryKey, UserKey:
return Identity{ID: key.IssuerID, Email: key.Subject}, nil
default:
return Identity{}, ErrUnauthorizedAccess
}
}
func (svc service) Authorize(ctx context.Context, token, sub, obj, act string) (bool, error) {
return true, nil
}
func (svc service) tmpKey(duration time.Duration, key Key) (Key, string, error) {
key.ExpiresAt = key.IssuedAt.Add(duration)
secret, err := svc.tokenizer.Issue(key)
if err != nil {
return Key{}, "", errors.Wrap(errIssueTmp, err)
}
return key, secret, nil
}
func (svc service) userKey(ctx context.Context, token string, key Key) (Key, string, error) {
id, sub, err := svc.login(token)
if err != nil {
return Key{}, "", errors.Wrap(errIssueUser, err)
}
key.IssuerID = id
if key.Subject == "" {
key.Subject = sub
}
keyID, err := svc.idProvider.ID()
if err != nil {
return Key{}, "", errors.Wrap(errIssueUser, err)
}
key.ID = keyID
if _, err := svc.keys.Save(ctx, key); err != nil {
return Key{}, "", errors.Wrap(errIssueUser, err)
}
secret, err := svc.tokenizer.Issue(key)
if err != nil {
return Key{}, "", errors.Wrap(errIssueUser, err)
}
return key, secret, nil
}
func (svc service) login(token string) (string, string, error) {
key, err := svc.tokenizer.Parse(token)
if err != nil {
return "", "", err
}
// Only user key token is valid for login.
if key.Type != UserKey || key.IssuerID == "" {
return "", "", ErrUnauthorizedAccess
}
return key.IssuerID, key.Subject, nil
}
func (svc service) CreateGroup(ctx context.Context, token string, group Group) (Group, error) {
user, err := svc.Identify(ctx, token)
if err != nil {
return Group{}, errors.Wrap(ErrUnauthorizedAccess, err)
}
ulid, err := svc.ulidProvider.ID()
if err != nil {
return Group{}, errors.Wrap(ErrGenerateGroupID, err)
}
timestamp := getTimestmap()
group.UpdatedAt = timestamp
group.CreatedAt = timestamp
group.ID = ulid
group.OwnerID = user.ID
group, err = svc.groups.Save(ctx, group)
if err != nil {
return Group{}, err
}
return group, nil
}
func (svc service) ListGroups(ctx context.Context, token string, pm PageMetadata) (GroupPage, error) {
if _, err := svc.Identify(ctx, token); err != nil {
return GroupPage{}, errors.Wrap(ErrUnauthorizedAccess, err)
}
return svc.groups.RetrieveAll(ctx, pm)
}
func (svc service) ListParents(ctx context.Context, token string, childID string, pm PageMetadata) (GroupPage, error) {
if _, err := svc.Identify(ctx, token); err != nil {
return GroupPage{}, errors.Wrap(ErrUnauthorizedAccess, err)
}
return svc.groups.RetrieveAllParents(ctx, childID, pm)
}
func (svc service) ListChildren(ctx context.Context, token string, parentID string, pm PageMetadata) (GroupPage, error) {
if _, err := svc.Identify(ctx, token); err != nil {
return GroupPage{}, errors.Wrap(ErrUnauthorizedAccess, err)
}
return svc.groups.RetrieveAllChildren(ctx, parentID, pm)
}
func (svc service) ListMembers(ctx context.Context, token string, groupID, groupType string, pm PageMetadata) (MemberPage, error) {
if _, err := svc.Identify(ctx, token); err != nil {
return MemberPage{}, errors.Wrap(ErrUnauthorizedAccess, err)
}
mp, err := svc.groups.Members(ctx, groupID, groupType, pm)
if err != nil {
return MemberPage{}, errors.Wrap(ErrFailedToRetrieveMembers, err)
}
return mp, nil
}
func (svc service) RemoveGroup(ctx context.Context, token, id string) error {
if _, err := svc.Identify(ctx, token); err != nil {
return errors.Wrap(ErrUnauthorizedAccess, err)
}
return svc.groups.Delete(ctx, id)
}
func (svc service) UpdateGroup(ctx context.Context, token string, group Group) (Group, error) {
if _, err := svc.Identify(ctx, token); err != nil {
return Group{}, errors.Wrap(ErrUnauthorizedAccess, err)
}
group.UpdatedAt = getTimestmap()
return svc.groups.Update(ctx, group)
}
func (svc service) ViewGroup(ctx context.Context, token, id string) (Group, error) {
if _, err := svc.Identify(ctx, token); err != nil {
return Group{}, errors.Wrap(ErrUnauthorizedAccess, err)
}
return svc.groups.RetrieveByID(ctx, id)
}
func (svc service) Assign(ctx context.Context, token string, groupID, groupType string, memberIDs ...string) error {
if _, err := svc.Identify(ctx, token); err != nil {
return errors.Wrap(ErrUnauthorizedAccess, err)
}
return svc.groups.Assign(ctx, groupID, groupType, memberIDs...)
}
func (svc service) Unassign(ctx context.Context, token string, groupID string, memberIDs ...string) error {
if _, err := svc.Identify(ctx, token); err != nil {
return errors.Wrap(ErrUnauthorizedAccess, err)
}
return svc.groups.Unassign(ctx, groupID, memberIDs...)
}
func (svc service) ListMemberships(ctx context.Context, token string, memberID string, pm PageMetadata) (GroupPage, error) {
if _, err := svc.Identify(ctx, token); err != nil {
return GroupPage{}, errors.Wrap(ErrUnauthorizedAccess, err)
}
return svc.groups.Memberships(ctx, memberID, pm)
}
func getTimestmap() time.Time {
return time.Now().UTC().Round(time.Millisecond)
}
+983
View File
@@ -0,0 +1,983 @@
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
package auth_test
import (
"context"
"fmt"
"testing"
"time"
"github.com/mainflux/mainflux/auth"
"github.com/mainflux/mainflux/auth/jwt"
"github.com/mainflux/mainflux/auth/mocks"
"github.com/mainflux/mainflux/pkg/errors"
"github.com/mainflux/mainflux/pkg/uuid"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
var idProvider = uuid.New()
const (
secret = "secret"
email = "test@example.com"
id = "testID"
groupName = "mfx"
description = "Description"
)
func newService() auth.Service {
repo := mocks.NewKeyRepository()
groupRepo := mocks.NewGroupRepository()
idProvider := uuid.NewMock()
t := jwt.New(secret)
return auth.New(repo, groupRepo, idProvider, t)
}
func TestIssue(t *testing.T) {
svc := newService()
_, secret, err := svc.Issue(context.Background(), "", auth.Key{Type: auth.UserKey, IssuedAt: time.Now(), IssuerID: id, Subject: email})
assert.Nil(t, err, fmt.Sprintf("Issuing login key expected to succeed: %s", err))
cases := []struct {
desc string
key auth.Key
token string
err error
}{
{
desc: "issue user key",
key: auth.Key{
Type: auth.UserKey,
IssuedAt: time.Now(),
},
token: secret,
err: nil,
},
{
desc: "issue user key with no time",
key: auth.Key{
Type: auth.UserKey,
},
token: secret,
err: auth.ErrInvalidKeyIssuedAt,
},
{
desc: "issue API key",
key: auth.Key{
Type: auth.APIKey,
IssuedAt: time.Now(),
},
token: secret,
err: nil,
},
{
desc: "issue API key unauthorized",
key: auth.Key{
Type: auth.APIKey,
IssuedAt: time.Now(),
},
token: "invalid",
err: auth.ErrUnauthorizedAccess,
},
{
desc: "issue API key with no time",
key: auth.Key{
Type: auth.APIKey,
},
token: secret,
err: auth.ErrInvalidKeyIssuedAt,
},
{
desc: "issue recovery key",
key: auth.Key{
Type: auth.RecoveryKey,
IssuedAt: time.Now(),
},
token: "",
err: nil,
},
{
desc: "issue recovery with no issue time",
key: auth.Key{
Type: auth.RecoveryKey,
},
token: secret,
err: auth.ErrInvalidKeyIssuedAt,
},
}
for _, tc := range cases {
_, _, err := svc.Issue(context.Background(), tc.token, tc.key)
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s expected %s got %s\n", tc.desc, tc.err, err))
}
}
func TestRevoke(t *testing.T) {
svc := newService()
_, secret, err := svc.Issue(context.Background(), "", auth.Key{Type: auth.UserKey, IssuedAt: time.Now(), IssuerID: id, Subject: email})
assert.Nil(t, err, fmt.Sprintf("Issuing login key expected to succeed: %s", err))
key := auth.Key{
Type: auth.APIKey,
IssuedAt: time.Now(),
IssuerID: id,
Subject: email,
}
newKey, _, err := svc.Issue(context.Background(), secret, key)
assert.Nil(t, err, fmt.Sprintf("Issuing user's key expected to succeed: %s", err))
cases := []struct {
desc string
id string
token string
err error
}{
{
desc: "revoke user key",
id: newKey.ID,
token: secret,
err: nil,
},
{
desc: "revoke non-existing user key",
id: newKey.ID,
token: secret,
err: nil,
},
{
desc: "revoke unauthorized",
id: newKey.ID,
token: "",
err: auth.ErrUnauthorizedAccess,
},
}
for _, tc := range cases {
err := svc.Revoke(context.Background(), tc.token, tc.id)
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s expected %s got %s\n", tc.desc, tc.err, err))
}
}
func TestRetrieve(t *testing.T) {
svc := newService()
_, secret, err := svc.Issue(context.Background(), "", auth.Key{Type: auth.UserKey, IssuedAt: time.Now(), Subject: email, IssuerID: id})
assert.Nil(t, err, fmt.Sprintf("Issuing login key expected to succeed: %s", err))
key := auth.Key{
ID: "id",
Type: auth.APIKey,
IssuerID: id,
Subject: email,
IssuedAt: time.Now(),
}
_, userToken, err := svc.Issue(context.Background(), "", auth.Key{Type: auth.UserKey, IssuedAt: time.Now(), IssuerID: id, Subject: email})
assert.Nil(t, err, fmt.Sprintf("Issuing user key expected to succeed: %s", err))
apiKey, apiToken, err := svc.Issue(context.Background(), secret, key)
assert.Nil(t, err, fmt.Sprintf("Issuing user's key expected to succeed: %s", err))
_, resetToken, err := svc.Issue(context.Background(), "", auth.Key{Type: auth.RecoveryKey, IssuedAt: time.Now()})
assert.Nil(t, err, fmt.Sprintf("Issuing reset key expected to succeed: %s", err))
cases := []struct {
desc string
id string
token string
err error
}{
{
desc: "retrieve user key",
id: apiKey.ID,
token: userToken,
err: nil,
},
{
desc: "retrieve non-existing user key",
id: "invalid",
token: userToken,
err: auth.ErrNotFound,
},
{
desc: "retrieve unauthorized",
id: apiKey.ID,
token: "wrong",
err: auth.ErrUnauthorizedAccess,
},
{
desc: "retrieve with API token",
id: apiKey.ID,
token: apiToken,
err: auth.ErrUnauthorizedAccess,
},
{
desc: "retrieve with reset token",
id: apiKey.ID,
token: resetToken,
err: auth.ErrUnauthorizedAccess,
},
}
for _, tc := range cases {
_, err := svc.RetrieveKey(context.Background(), tc.token, tc.id)
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s expected %s got %s\n", tc.desc, tc.err, err))
}
}
func TestIdentify(t *testing.T) {
svc := newService()
_, loginSecret, err := svc.Issue(context.Background(), "", auth.Key{Type: auth.UserKey, IssuedAt: time.Now(), IssuerID: id, Subject: email})
assert.Nil(t, err, fmt.Sprintf("Issuing login key expected to succeed: %s", err))
_, recoverySecret, err := svc.Issue(context.Background(), "", auth.Key{Type: auth.RecoveryKey, IssuedAt: time.Now(), IssuerID: id, Subject: email})
assert.Nil(t, err, fmt.Sprintf("Issuing reset key expected to succeed: %s", err))
_, apiSecret, err := svc.Issue(context.Background(), loginSecret, auth.Key{Type: auth.APIKey, IssuerID: id, Subject: email, IssuedAt: time.Now(), ExpiresAt: time.Now().Add(time.Minute)})
assert.Nil(t, err, fmt.Sprintf("Issuing user key expected to succeed: %s", err))
exp1 := time.Now().Add(-2 * time.Second)
_, expSecret, err := svc.Issue(context.Background(), loginSecret, auth.Key{Type: auth.APIKey, IssuedAt: time.Now(), ExpiresAt: exp1})
assert.Nil(t, err, fmt.Sprintf("Issuing expired user key expected to succeed: %s", err))
_, invalidSecret, err := svc.Issue(context.Background(), loginSecret, auth.Key{Type: 22, IssuedAt: time.Now()})
assert.Nil(t, err, fmt.Sprintf("Issuing user key expected to succeed: %s", err))
cases := []struct {
desc string
key string
idt auth.Identity
err error
}{
{
desc: "identify login key",
key: loginSecret,
idt: auth.Identity{id, email},
err: nil,
},
{
desc: "identify recovery key",
key: recoverySecret,
idt: auth.Identity{id, email},
err: nil,
},
{
desc: "identify API key",
key: apiSecret,
idt: auth.Identity{id, email},
err: nil,
},
{
desc: "identify expired API key",
key: expSecret,
idt: auth.Identity{},
err: auth.ErrAPIKeyExpired,
},
{
desc: "identify expired key",
key: invalidSecret,
idt: auth.Identity{},
err: auth.ErrUnauthorizedAccess,
},
{
desc: "identify invalid key",
key: "invalid",
idt: auth.Identity{},
err: auth.ErrUnauthorizedAccess,
},
}
for _, tc := range cases {
idt, err := svc.Identify(context.Background(), tc.key)
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s expected %s got %s\n", tc.desc, tc.err, err))
assert.Equal(t, tc.idt, idt, fmt.Sprintf("%s expected %s got %s\n", tc.desc, tc.idt, idt))
}
}
func TestCreateGroup(t *testing.T) {
svc := newService()
_, secret, err := svc.Issue(context.Background(), "", auth.Key{Type: auth.UserKey, IssuedAt: time.Now(), IssuerID: id, Subject: email})
assert.Nil(t, err, fmt.Sprintf("Issuing login key expected to succeed: %s", err))
key := auth.Key{
ID: "id",
Type: auth.APIKey,
IssuerID: id,
Subject: email,
IssuedAt: time.Now(),
}
_, apiToken, err := svc.Issue(context.Background(), secret, key)
assert.Nil(t, err, fmt.Sprintf("Issuing user's key expected to succeed: %s", err))
group := auth.Group{
Name: "Group",
Description: description,
}
parentGroup := auth.Group{
Name: "ParentGroup",
Description: description,
}
parent, err := svc.CreateGroup(context.Background(), apiToken, parentGroup)
assert.Nil(t, err, fmt.Sprintf("Creating parent group expected to succeed: %s", err))
cases := []struct {
desc string
group auth.Group
err error
}{
{
desc: "create new group",
group: group,
err: nil,
},
{
desc: "create group with existing name",
group: group,
err: nil,
},
{
desc: "create group with parent",
group: auth.Group{
Name: groupName,
ParentID: parent.ID,
},
err: nil,
},
{
desc: "create group with invalid parent",
group: auth.Group{
Name: groupName,
ParentID: "xxxxxxxxxx",
},
err: auth.ErrCreateGroup,
},
}
for _, tc := range cases {
_, err := svc.CreateGroup(context.Background(), apiToken, tc.group)
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
}
}
func TestUpdateGroup(t *testing.T) {
svc := newService()
_, secret, err := svc.Issue(context.Background(), "", auth.Key{Type: auth.UserKey, IssuedAt: time.Now(), IssuerID: id, Subject: email})
assert.Nil(t, err, fmt.Sprintf("Issuing login key expected to succeed: %s", err))
key := auth.Key{
ID: "id",
Type: auth.APIKey,
IssuerID: id,
Subject: email,
IssuedAt: time.Now(),
}
_, apiToken, err := svc.Issue(context.Background(), secret, key)
assert.Nil(t, err, fmt.Sprintf("Issuing user's key expected to succeed: %s", err))
group := auth.Group{
Name: "Group",
Description: description,
Metadata: auth.GroupMetadata{
"field": "value",
},
}
group, err = svc.CreateGroup(context.Background(), apiToken, group)
assert.Nil(t, err, fmt.Sprintf("Creating parent group failed: %s", err))
cases := []struct {
desc string
group auth.Group
err error
}{
{
desc: "update group",
group: auth.Group{
ID: group.ID,
Name: "NewName",
Description: "NewDescription",
Metadata: auth.GroupMetadata{
"field": "value2",
},
},
err: nil,
},
}
for _, tc := range cases {
g, err := svc.UpdateGroup(context.Background(), apiToken, tc.group)
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
assert.Equal(t, g.ID, tc.group.ID, fmt.Sprintf("ID: expected %s got %s\n", g.ID, tc.group.ID))
assert.Equal(t, g.Name, tc.group.Name, fmt.Sprintf("Name: expected %s got %s\n", g.Name, tc.group.Name))
assert.Equal(t, g.Description, tc.group.Description, fmt.Sprintf("Description: expected %s got %s\n", g.Description, tc.group.Description))
assert.Equal(t, g.Metadata["field"], g.Metadata["field"], fmt.Sprintf("Metadata: expected %s got %s\n", g.Metadata, tc.group.Metadata))
}
}
func TestViewGroup(t *testing.T) {
svc := newService()
_, secret, err := svc.Issue(context.Background(), "", auth.Key{Type: auth.UserKey, IssuedAt: time.Now(), IssuerID: id, Subject: email})
assert.Nil(t, err, fmt.Sprintf("Issuing login key expected to succeed: %s", err))
key := auth.Key{
ID: "id",
Type: auth.APIKey,
IssuerID: id,
Subject: email,
IssuedAt: time.Now(),
}
_, apiToken, err := svc.Issue(context.Background(), secret, key)
assert.Nil(t, err, fmt.Sprintf("Issuing user's key expected to succeed: %s", err))
group := auth.Group{
Name: "Group",
Description: description,
Metadata: auth.GroupMetadata{
"field": "value",
},
}
group, err = svc.CreateGroup(context.Background(), apiToken, group)
assert.Nil(t, err, fmt.Sprintf("Creating parent group failed: %s", err))
cases := []struct {
desc string
token string
groupID string
err error
}{
{
desc: "view group",
token: apiToken,
groupID: group.ID,
err: nil,
},
{
desc: "view group with unauthorized token",
token: "wrongtoken",
groupID: group.ID,
err: auth.ErrUnauthorizedAccess,
},
{
desc: "view group for wrong id",
token: apiToken,
groupID: "wrong",
err: auth.ErrGroupNotFound,
},
}
for _, tc := range cases {
_, err := svc.ViewGroup(context.Background(), tc.token, tc.groupID)
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
}
}
func TestListGroups(t *testing.T) {
svc := newService()
_, secret, err := svc.Issue(context.Background(), "", auth.Key{Type: auth.UserKey, IssuedAt: time.Now(), IssuerID: id, Subject: email})
assert.Nil(t, err, fmt.Sprintf("Issuing login key expected to succeed: %s", err))
key := auth.Key{
ID: "id",
Type: auth.APIKey,
IssuerID: id,
Subject: email,
IssuedAt: time.Now(),
}
_, apiToken, err := svc.Issue(context.Background(), secret, key)
assert.Nil(t, err, fmt.Sprintf("Issuing user's key expected to succeed: %s", err))
group := auth.Group{
Description: description,
Metadata: auth.GroupMetadata{
"field": "value",
},
}
n := uint64(10)
parentID := ""
for i := uint64(0); i < n; i++ {
group.Name = fmt.Sprintf("Group%d", i)
group.ParentID = parentID
g, err := svc.CreateGroup(context.Background(), apiToken, group)
require.Nil(t, err, fmt.Sprintf("unexpected error: %s\n", err))
parentID = g.ID
}
cases := map[string]struct {
token string
level uint64
size uint64
metadata auth.GroupMetadata
err error
}{
"list all groups": {
token: apiToken,
level: 5,
size: n,
err: nil,
},
"list groups for level 1": {
token: apiToken,
level: 1,
size: n,
err: nil,
},
"list all groups with wrong token": {
token: "wrongToken",
level: 5,
size: 0,
err: auth.ErrUnauthorizedAccess,
},
}
for desc, tc := range cases {
page, err := svc.ListGroups(context.Background(), tc.token, auth.PageMetadata{Level: tc.level, Metadata: tc.metadata})
size := uint64(len(page.Groups))
assert.Equal(t, tc.size, size, fmt.Sprintf("%s: expected %d got %d\n", desc, tc.size, size))
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", desc, tc.err, err))
}
}
func TestListChildren(t *testing.T) {
svc := newService()
_, secret, err := svc.Issue(context.Background(), "", auth.Key{Type: auth.UserKey, IssuedAt: time.Now(), IssuerID: id, Subject: email})
assert.Nil(t, err, fmt.Sprintf("Issuing login key expected to succeed: %s", err))
key := auth.Key{
ID: "id",
Type: auth.APIKey,
IssuerID: id,
Subject: email,
IssuedAt: time.Now(),
}
_, apiToken, err := svc.Issue(context.Background(), secret, key)
assert.Nil(t, err, fmt.Sprintf("Issuing user's key expected to succeed: %s", err))
group := auth.Group{
Description: description,
Metadata: auth.GroupMetadata{
"field": "value",
},
}
n := uint64(10)
parentID := ""
groupIDs := make([]string, n)
for i := uint64(0); i < n; i++ {
group.Name = fmt.Sprintf("Group%d", i)
group.ParentID = parentID
g, err := svc.CreateGroup(context.Background(), apiToken, group)
require.Nil(t, err, fmt.Sprintf("unexpected error: %s\n", err))
parentID = g.ID
groupIDs[i] = g.ID
}
cases := map[string]struct {
token string
level uint64
size uint64
id string
metadata auth.GroupMetadata
err error
}{
"list all children": {
token: apiToken,
level: 5,
id: groupIDs[0],
size: n,
err: nil,
},
"list all groups with wrong token": {
token: "wrongToken",
level: 5,
size: 0,
err: auth.ErrUnauthorizedAccess,
},
}
for desc, tc := range cases {
page, err := svc.ListChildren(context.Background(), tc.token, tc.id, auth.PageMetadata{Level: tc.level, Metadata: tc.metadata})
size := uint64(len(page.Groups))
assert.Equal(t, tc.size, size, fmt.Sprintf("%s: expected %d got %d\n", desc, tc.size, size))
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", desc, tc.err, err))
}
}
func TestListParents(t *testing.T) {
svc := newService()
_, secret, err := svc.Issue(context.Background(), "", auth.Key{Type: auth.UserKey, IssuedAt: time.Now(), IssuerID: id, Subject: email})
assert.Nil(t, err, fmt.Sprintf("Issuing login key expected to succeed: %s", err))
key := auth.Key{
ID: "id",
Type: auth.APIKey,
IssuerID: id,
Subject: email,
IssuedAt: time.Now(),
}
_, apiToken, err := svc.Issue(context.Background(), secret, key)
assert.Nil(t, err, fmt.Sprintf("Issuing user's key expected to succeed: %s", err))
group := auth.Group{
Description: description,
Metadata: auth.GroupMetadata{
"field": "value",
},
}
n := uint64(10)
parentID := ""
groupIDs := make([]string, n)
for i := uint64(0); i < n; i++ {
group.Name = fmt.Sprintf("Group%d", i)
group.ParentID = parentID
g, err := svc.CreateGroup(context.Background(), apiToken, group)
require.Nil(t, err, fmt.Sprintf("unexpected error: %s\n", err))
parentID = g.ID
groupIDs[i] = g.ID
}
cases := map[string]struct {
token string
level uint64
size uint64
id string
metadata auth.GroupMetadata
err error
}{
"list all parents": {
token: apiToken,
level: 5,
id: groupIDs[n-1],
size: n,
err: nil,
},
"list all parents with wrong token": {
token: "wrongToken",
level: 5,
size: 0,
err: auth.ErrUnauthorizedAccess,
},
}
for desc, tc := range cases {
page, err := svc.ListParents(context.Background(), tc.token, tc.id, auth.PageMetadata{Level: tc.level, Metadata: tc.metadata})
size := uint64(len(page.Groups))
assert.Equal(t, tc.size, size, fmt.Sprintf("%s: expected %d got %d\n", desc, tc.size, size))
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", desc, tc.err, err))
}
}
func TestListMembers(t *testing.T) {
svc := newService()
_, secret, err := svc.Issue(context.Background(), "", auth.Key{Type: auth.UserKey, IssuedAt: time.Now(), IssuerID: id, Subject: email})
assert.Nil(t, err, fmt.Sprintf("Issuing login key expected to succeed: %s", err))
key := auth.Key{
ID: "id",
Type: auth.APIKey,
IssuerID: id,
Subject: email,
IssuedAt: time.Now(),
}
_, apiToken, err := svc.Issue(context.Background(), secret, key)
assert.Nil(t, err, fmt.Sprintf("Issuing user's key expected to succeed: %s", err))
group := auth.Group{
Description: description,
Metadata: auth.GroupMetadata{
"field": "value",
},
}
g, err := svc.CreateGroup(context.Background(), apiToken, group)
assert.Nil(t, err, fmt.Sprintf("Creating group expected to succeed: %s", err))
group.ID = g.ID
n := uint64(10)
for i := uint64(0); i < n; i++ {
uid, err := idProvider.ID()
require.Nil(t, err, fmt.Sprintf("unexpected error: %s\n", err))
err = svc.Assign(context.Background(), apiToken, group.ID, "things", uid)
require.Nil(t, err, fmt.Sprintf("Assign member expected to succeed: %s\n", err))
}
cases := map[string]struct {
token string
size uint64
offset uint64
limit uint64
group auth.Group
metadata auth.GroupMetadata
err error
}{
"list all members": {
token: apiToken,
offset: 0,
limit: n,
group: group,
size: n,
err: nil,
},
"list half members": {
token: apiToken,
offset: n / 2,
limit: n,
group: group,
size: n / 2,
err: nil,
},
"list all members with wrong token": {
token: "wrongToken",
offset: 0,
limit: n,
size: 0,
err: auth.ErrUnauthorizedAccess,
},
}
for desc, tc := range cases {
page, err := svc.ListMembers(context.Background(), tc.token, tc.group.ID, "things", auth.PageMetadata{Offset: tc.offset, Limit: tc.limit, Metadata: tc.metadata})
size := uint64(len(page.Members))
assert.Equal(t, tc.size, size, fmt.Sprintf("%s: expected %d got %d\n", desc, tc.size, size))
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", desc, tc.err, err))
}
}
func TestListMemberships(t *testing.T) {
svc := newService()
_, secret, err := svc.Issue(context.Background(), "", auth.Key{Type: auth.UserKey, IssuedAt: time.Now(), IssuerID: id, Subject: email})
assert.Nil(t, err, fmt.Sprintf("Issuing login key expected to succeed: %s", err))
key := auth.Key{
ID: "id",
Type: auth.APIKey,
IssuerID: id,
Subject: email,
IssuedAt: time.Now(),
}
_, apiToken, err := svc.Issue(context.Background(), secret, key)
assert.Nil(t, err, fmt.Sprintf("Issuing user's key expected to succeed: %s", err))
group := auth.Group{
Description: description,
Metadata: auth.GroupMetadata{
"field": "value",
},
}
memberID, err := idProvider.ID()
require.Nil(t, err, fmt.Sprintf("unexpected error: %s\n", err))
n := uint64(10)
for i := uint64(0); i < n; i++ {
group.Name = fmt.Sprintf("Group%d", i)
g, err := svc.CreateGroup(context.Background(), apiToken, group)
require.Nil(t, err, fmt.Sprintf("unexpected error: %s\n", err))
err = svc.Assign(context.Background(), apiToken, g.ID, "things", memberID)
require.Nil(t, err, fmt.Sprintf("Assign member expected to succeed: %s\n", err))
}
cases := map[string]struct {
token string
size uint64
offset uint64
limit uint64
group auth.Group
metadata auth.GroupMetadata
err error
}{
"list all members": {
token: apiToken,
offset: 0,
limit: n,
group: group,
size: n,
err: nil,
},
"list half members": {
token: apiToken,
offset: n / 2,
limit: n,
group: group,
size: n / 2,
err: nil,
},
"list all members with wrong token": {
token: "wrongToken",
offset: 0,
limit: n,
size: 0,
err: auth.ErrUnauthorizedAccess,
},
}
for desc, tc := range cases {
page, err := svc.ListMemberships(context.Background(), tc.token, memberID, auth.PageMetadata{Limit: tc.limit, Offset: tc.offset, Metadata: tc.metadata})
size := uint64(len(page.Groups))
assert.Equal(t, tc.size, size, fmt.Sprintf("%s: expected %d got %d\n", desc, tc.size, size))
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", desc, tc.err, err))
}
}
func TestRemoveGroup(t *testing.T) {
svc := newService()
_, secret, err := svc.Issue(context.Background(), "", auth.Key{Type: auth.UserKey, IssuedAt: time.Now(), IssuerID: id, Subject: email})
assert.Nil(t, err, fmt.Sprintf("Issuing login key expected to succeed: %s", err))
key := auth.Key{
ID: "id",
Type: auth.APIKey,
IssuerID: id,
Subject: email,
IssuedAt: time.Now(),
}
_, apiToken, err := svc.Issue(context.Background(), secret, key)
assert.Nil(t, err, fmt.Sprintf("Issuing user's key expected to succeed: %s", err))
uid, err := idProvider.ID()
require.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err))
creationTime := time.Now().UTC()
group := auth.Group{
Name: groupName,
OwnerID: uid,
CreatedAt: creationTime,
UpdatedAt: creationTime,
}
group, err = svc.CreateGroup(context.Background(), apiToken, group)
require.Nil(t, err, fmt.Sprintf("group save got unexpected error: %s", err))
err = svc.RemoveGroup(context.Background(), "wrongToken", group.ID)
assert.True(t, errors.Contains(err, auth.ErrUnauthorizedAccess), fmt.Sprintf("Unauthorized access: expected %v got %v", auth.ErrUnauthorizedAccess, err))
err = svc.RemoveGroup(context.Background(), apiToken, "wrongID")
assert.True(t, errors.Contains(err, auth.ErrGroupNotFound), fmt.Sprintf("Remove group with wrong id: expected %v got %v", auth.ErrGroupNotFound, err))
gp, err := svc.ListGroups(context.Background(), apiToken, auth.PageMetadata{Level: auth.MaxLevel})
require.Nil(t, err, fmt.Sprintf("list groups unexpected error: %s", err))
assert.True(t, gp.Total == 1, fmt.Sprintf("retrieve members of a group: expected %d got %d\n", 1, gp.Total))
err = svc.RemoveGroup(context.Background(), apiToken, group.ID)
assert.True(t, errors.Contains(err, nil), fmt.Sprintf("Unauthorized access: expected %v got %v", nil, err))
gp, err = svc.ListGroups(context.Background(), apiToken, auth.PageMetadata{Level: auth.MaxLevel})
require.Nil(t, err, fmt.Sprintf("list groups save unexpected error: %s", err))
assert.True(t, gp.Total == 0, fmt.Sprintf("retrieve members of a group: expected %d got %d\n", 0, gp.Total))
}
func TestAssign(t *testing.T) {
svc := newService()
_, secret, err := svc.Issue(context.Background(), "", auth.Key{Type: auth.UserKey, IssuedAt: time.Now(), IssuerID: id, Subject: email})
assert.Nil(t, err, fmt.Sprintf("Issuing login key expected to succeed: %s", err))
key := auth.Key{
ID: "id",
Type: auth.APIKey,
IssuerID: id,
Subject: email,
IssuedAt: time.Now(),
}
_, apiToken, err := svc.Issue(context.Background(), secret, key)
assert.Nil(t, err, fmt.Sprintf("Issuing user's key expected to succeed: %s", err))
uid, err := idProvider.ID()
require.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err))
creationTime := time.Now().UTC()
group := auth.Group{
Name: groupName + "Updated",
OwnerID: uid,
CreatedAt: creationTime,
UpdatedAt: creationTime,
}
group, err = svc.CreateGroup(context.Background(), apiToken, group)
require.Nil(t, err, fmt.Sprintf("group save got unexpected error: %s", err))
mid, err := idProvider.ID()
require.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err))
err = svc.Assign(context.Background(), apiToken, group.ID, "things", mid)
require.Nil(t, err, fmt.Sprintf("member assign save unexpected error: %s", err))
mp, err := svc.ListMembers(context.Background(), apiToken, group.ID, "things", auth.PageMetadata{Offset: 0, Limit: 10})
require.Nil(t, err, fmt.Sprintf("member assign save unexpected error: %s", err))
assert.True(t, mp.Total == 1, fmt.Sprintf("retrieve members of a group: expected %d got %d\n", 1, mp.Total))
err = svc.Assign(context.Background(), "wrongToken", group.ID, "things", mid)
assert.True(t, errors.Contains(err, auth.ErrUnauthorizedAccess), fmt.Sprintf("Unauthorized access: expected %v got %v", auth.ErrUnauthorizedAccess, err))
}
func TestUnassign(t *testing.T) {
svc := newService()
_, secret, err := svc.Issue(context.Background(), "", auth.Key{Type: auth.UserKey, IssuedAt: time.Now(), IssuerID: id, Subject: email})
assert.Nil(t, err, fmt.Sprintf("Issuing login key expected to succeed: %s", err))
key := auth.Key{
ID: "id",
Type: auth.APIKey,
IssuerID: id,
Subject: email,
IssuedAt: time.Now(),
}
_, apiToken, err := svc.Issue(context.Background(), secret, key)
assert.Nil(t, err, fmt.Sprintf("Issuing user's key expected to succeed: %s", err))
uid, err := idProvider.ID()
require.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err))
creationTime := time.Now().UTC()
group := auth.Group{
Name: groupName + "Updated",
OwnerID: uid,
CreatedAt: creationTime,
UpdatedAt: creationTime,
}
group, err = svc.CreateGroup(context.Background(), apiToken, group)
require.Nil(t, err, fmt.Sprintf("group save got unexpected error: %s", err))
mid, err := idProvider.ID()
require.Nil(t, err, fmt.Sprintf("got unexpected error: %s", err))
err = svc.Assign(context.Background(), apiToken, group.ID, "things", mid)
require.Nil(t, err, fmt.Sprintf("member assign save unexpected error: %s", err))
mp, err := svc.ListMembers(context.Background(), apiToken, group.ID, "things", auth.PageMetadata{Limit: 10, Offset: 0})
require.Nil(t, err, fmt.Sprintf("member assign save unexpected error: %s", err))
assert.True(t, mp.Total == 1, fmt.Sprintf("retrieve members of a group: expected %d got %d\n", 1, mp.Total))
err = svc.Unassign(context.Background(), apiToken, group.ID, mid)
require.Nil(t, err, fmt.Sprintf("member unassign save unexpected error: %s", err))
mp, err = svc.ListMembers(context.Background(), apiToken, group.ID, "things", auth.PageMetadata{Limit: 10, Offset: 0})
require.Nil(t, err, fmt.Sprintf("member assign save unexpected error: %s", err))
assert.True(t, mp.Total == 0, fmt.Sprintf("retrieve members of a group: expected %d got %d\n", 0, mp.Total))
err = svc.Unassign(context.Background(), "wrongToken", group.ID, mid)
assert.True(t, errors.Contains(err, auth.ErrUnauthorizedAccess), fmt.Sprintf("Unauthorized access: expected %v got %v", auth.ErrUnauthorizedAccess, err))
err = svc.Unassign(context.Background(), apiToken, group.ID, mid)
assert.True(t, errors.Contains(err, auth.ErrGroupNotFound), fmt.Sprintf("Unauthorized access: expected %v got %v", nil, err))
}
+13
View File
@@ -0,0 +1,13 @@
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
package auth
// Tokenizer specifies API for encoding and decoding between string and Key.
type Tokenizer interface {
// Issue converts API Key to its string representation.
Issue(Key) (string, error)
// Parse extracts API Key data from string token.
Parse(string) (Key, error)
}
+129
View File
@@ -0,0 +1,129 @@
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
// Package tracing contains middlewares that will add spans to existing traces.
package tracing
import (
"context"
"github.com/mainflux/mainflux/auth"
opentracing "github.com/opentracing/opentracing-go"
)
const (
assign = "assign"
saveGroup = "save_group"
deleteGroup = "delete_group"
updateGroup = "update_group"
retrieveByID = "retrieve_by_id"
retrieveAllParents = "retrieve_all_parents"
retrieveAllChildren = "retrieve_all_children"
retrieveAll = "retrieve_all_groups"
memberships = "memberships"
members = "members"
unassign = "unassign"
)
var _ auth.GroupRepository = (*groupRepositoryMiddleware)(nil)
type groupRepositoryMiddleware struct {
tracer opentracing.Tracer
repo auth.GroupRepository
}
// GroupRepositoryMiddleware tracks request and their latency, and adds spans to context.
func GroupRepositoryMiddleware(tracer opentracing.Tracer, gr auth.GroupRepository) auth.GroupRepository {
return groupRepositoryMiddleware{
tracer: tracer,
repo: gr,
}
}
func (grm groupRepositoryMiddleware) Save(ctx context.Context, g auth.Group) (auth.Group, error) {
span := createSpan(ctx, grm.tracer, saveGroup)
defer span.Finish()
ctx = opentracing.ContextWithSpan(ctx, span)
return grm.repo.Save(ctx, g)
}
func (grm groupRepositoryMiddleware) Update(ctx context.Context, g auth.Group) (auth.Group, error) {
span := createSpan(ctx, grm.tracer, updateGroup)
defer span.Finish()
ctx = opentracing.ContextWithSpan(ctx, span)
return grm.repo.Update(ctx, g)
}
func (grm groupRepositoryMiddleware) Delete(ctx context.Context, groupID string) error {
span := createSpan(ctx, grm.tracer, deleteGroup)
defer span.Finish()
ctx = opentracing.ContextWithSpan(ctx, span)
return grm.repo.Delete(ctx, groupID)
}
func (grm groupRepositoryMiddleware) RetrieveByID(ctx context.Context, id string) (auth.Group, error) {
span := createSpan(ctx, grm.tracer, retrieveByID)
defer span.Finish()
ctx = opentracing.ContextWithSpan(ctx, span)
return grm.repo.RetrieveByID(ctx, id)
}
func (grm groupRepositoryMiddleware) RetrieveAllParents(ctx context.Context, groupID string, pm auth.PageMetadata) (auth.GroupPage, error) {
span := createSpan(ctx, grm.tracer, retrieveAllParents)
defer span.Finish()
ctx = opentracing.ContextWithSpan(ctx, span)
return grm.repo.RetrieveAllParents(ctx, groupID, pm)
}
func (grm groupRepositoryMiddleware) RetrieveAllChildren(ctx context.Context, groupID string, pm auth.PageMetadata) (auth.GroupPage, error) {
span := createSpan(ctx, grm.tracer, retrieveAllChildren)
defer span.Finish()
ctx = opentracing.ContextWithSpan(ctx, span)
return grm.repo.RetrieveAllChildren(ctx, groupID, pm)
}
func (grm groupRepositoryMiddleware) RetrieveAll(ctx context.Context, pm auth.PageMetadata) (auth.GroupPage, error) {
span := createSpan(ctx, grm.tracer, retrieveAll)
defer span.Finish()
ctx = opentracing.ContextWithSpan(ctx, span)
return grm.repo.RetrieveAll(ctx, pm)
}
func (grm groupRepositoryMiddleware) Memberships(ctx context.Context, memberID string, pm auth.PageMetadata) (auth.GroupPage, error) {
span := createSpan(ctx, grm.tracer, memberships)
defer span.Finish()
ctx = opentracing.ContextWithSpan(ctx, span)
return grm.repo.Memberships(ctx, memberID, pm)
}
func (grm groupRepositoryMiddleware) Members(ctx context.Context, groupID, groupType string, pm auth.PageMetadata) (auth.MemberPage, error) {
span := createSpan(ctx, grm.tracer, members)
defer span.Finish()
ctx = opentracing.ContextWithSpan(ctx, span)
return grm.repo.Members(ctx, groupID, groupType, pm)
}
func (grm groupRepositoryMiddleware) Assign(ctx context.Context, groupID, groupType string, memberIDs ...string) error {
span := createSpan(ctx, grm.tracer, assign)
defer span.Finish()
ctx = opentracing.ContextWithSpan(ctx, span)
return grm.repo.Assign(ctx, groupID, groupType, memberIDs...)
}
func (grm groupRepositoryMiddleware) Unassign(ctx context.Context, groupID string, memberIDs ...string) error {
span := createSpan(ctx, grm.tracer, unassign)
defer span.Finish()
ctx = opentracing.ContextWithSpan(ctx, span)
return grm.repo.Unassign(ctx, groupID, memberIDs...)
}
+73
View File
@@ -0,0 +1,73 @@
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
// Package tracing contains middlewares that will add spans
// to existing traces.
package tracing
import (
"context"
"github.com/mainflux/mainflux/auth"
opentracing "github.com/opentracing/opentracing-go"
)
const (
saveOp = "save"
retrieveOp = "retrieve_by_id"
revokeOp = "remove"
)
var _ auth.KeyRepository = (*keyRepositoryMiddleware)(nil)
// keyRepositoryMiddleware tracks request and their latency, and adds spans
// to context.
type keyRepositoryMiddleware struct {
tracer opentracing.Tracer
repo auth.KeyRepository
}
// New tracks request and their latency, and adds spans
// to context.
func New(repo auth.KeyRepository, tracer opentracing.Tracer) auth.KeyRepository {
return keyRepositoryMiddleware{
tracer: tracer,
repo: repo,
}
}
func (krm keyRepositoryMiddleware) Save(ctx context.Context, key auth.Key) (string, error) {
span := createSpan(ctx, krm.tracer, saveOp)
defer span.Finish()
ctx = opentracing.ContextWithSpan(ctx, span)
return krm.repo.Save(ctx, key)
}
func (krm keyRepositoryMiddleware) Retrieve(ctx context.Context, owner, id string) (auth.Key, error) {
span := createSpan(ctx, krm.tracer, retrieveOp)
defer span.Finish()
ctx = opentracing.ContextWithSpan(ctx, span)
return krm.repo.Retrieve(ctx, owner, id)
}
func (krm keyRepositoryMiddleware) Remove(ctx context.Context, owner, id string) error {
span := createSpan(ctx, krm.tracer, revokeOp)
defer span.Finish()
ctx = opentracing.ContextWithSpan(ctx, span)
return krm.repo.Remove(ctx, owner, id)
}
func createSpan(ctx context.Context, tracer opentracing.Tracer, opName string) opentracing.Span {
if parentSpan := opentracing.SpanFromContext(ctx); parentSpan != nil {
return tracer.StartSpan(
opName,
opentracing.ChildOf(parentSpan.Context()),
)
}
return tracer.StartSpan(opName)
}
+62 -67
View File
@@ -1,6 +1,7 @@
# BOOTSTRAP SERVICE
New devices need to be configured properly and connected to the Mainflux. Bootstrap service is used in order to accomplish that. This service provides the following features:
1) Creating new Mainflux Things
2) Providing basic configuration for the newly created Things
3) Enabling/disabling Things
@@ -9,9 +10,10 @@ Pre-provisioning a new Thing is as simple as sending Configuration data to the B
In order to bootstrap successfully, the Thing needs to send bootstrapping request to the specific URL, as well as a secret key. This key and URL are pre-provisioned during the manufacturing process. If the Thing is provisioned on the Bootstrap service side, the corresponding configuration will be sent as a response. Otherwise, the Thing will be saved so that it can be provisioned later.
***Thing Configuration***
## Thing Configuration Entity
Thing Configuration consists of two logical parts: the custom configuration that can be interpreted by the Thing itself and Mainflux-related configuration. Mainflux config contains:
1) corresponding Mainflux Thing ID
2) corresponding Mainflux Thing key
3) list of the Mainflux channels the Thing is connected to
@@ -27,83 +29,55 @@ Enabling and disabling Thing (adding Thing to/from whitelist) is as simple as co
Switching between states `Active` and `Inactive` enables and disables Thing, respectively.
Thing configuration also contains the so-called `external ID` and `external key`. An external ID is a unique identifier of corresponding Thing. For example, a device MAC address is a good choice for external ID. External key is a secret key that is used for authentication during the bootstrapping procedure.
## Configuration
The service is configured using the environment variables presented in the following table. Note that any unset variables will be replaced with their default values.
| Variable | Description | Default |
|-------------------------------|-------------------------------------------------------------------------|-----------------------|
| MF_BOOTSTRAP_LOG_LEVEL | Log level for Bootstrap (debug, info, warn, error) | error |
| MF_BOOTSTRAP_DB_HOST | Database host address | localhost |
| MF_BOOTSTRAP_DB_PORT | Database host port | 5432 |
| MF_BOOTSTRAP_DB_USER | Database user | mainflux |
| MF_BOOTSTRAP_DB_PASS | Database password | mainflux |
| MF_BOOTSTRAP_DB | Name of the database used by the service | bootstrap |
| MF_BOOTSTRAP_DB_SSL_MODE | Database connection SSL mode (disable, require, verify-ca, verify-full) | disable |
| MF_BOOTSTRAP_DB_SSL_CERT | Path to the PEM encoded certificate file | |
| MF_BOOTSTRAP_DB_SSL_KEY | Path to the PEM encoded key file | |
| MF_BOOTSTRAP_DB_SSL_ROOT_CERT | Path to the PEM encoded root certificate file | |
| MF_BOOTSTRAP_CLIENT_TLS | Flag that indicates if TLS should be turned on | false |
| MF_BOOTSTRAP_CA_CERTS | Path to trusted CAs in PEM format | |
| MF_BOOTSTRAP_PORT | Bootstrap service HTTP port | 8180 |
| MF_BOOTSTRAP_SERVER_CERT | Path to server certificate in pem format | |
| MF_BOOTSTRAP_SERVER_KEY | Path to server key in pem format | |
| MF_SDK_BASE_URL | Base url for Mainflux SDK | http://localhost |
| MF_SDK_THINGS_PREFIX | SDK prefix for Things service | |
| MF_USERS_URL | Users service URL | localhost:8181 |
| MF_THINGS_ES_URL | Things service event source URL | localhost:6379 |
| MF_THINGS_ES_PASS | Things service event source password | |
| MF_THINGS_ES_DB | Things service event source database | 0 |
| MF_BOOTSTRAP_INSTANCE_NAME | Bootstrap service instance name | bootstrap |
| Variable | Description | Default |
|-------------------------------|-------------------------------------------------------------------------|----------------------------------|
| MF_BOOTSTRAP_LOG_LEVEL | Log level for Bootstrap (debug, info, warn, error) | error |
| MF_BOOTSTRAP_DB_HOST | Database host address | localhost |
| MF_BOOTSTRAP_DB_PORT | Database host port | 5432 |
| MF_BOOTSTRAP_DB_USER | Database user | mainflux |
| MF_BOOTSTRAP_DB_PASS | Database password | mainflux |
| MF_BOOTSTRAP_DB | Name of the database used by the service | bootstrap |
| MF_BOOTSTRAP_DB_SSL_MODE | Database connection SSL mode (disable, require, verify-ca, verify-full) | disable |
| MF_BOOTSTRAP_DB_SSL_CERT | Path to the PEM encoded certificate file | |
| MF_BOOTSTRAP_DB_SSL_KEY | Path to the PEM encoded key file | |
| MF_BOOTSTRAP_DB_SSL_ROOT_CERT | Path to the PEM encoded root certificate file | |
| MF_BOOTSTRAP_ENCRYPT_KEY | Secret key for secure bootstrapping encryption | 12345678910111213141516171819202 |
| MF_BOOTSTRAP_CLIENT_TLS | Flag that indicates if TLS should be turned on | false |
| MF_BOOTSTRAP_CA_CERTS | Path to trusted CAs in PEM format | |
| MF_BOOTSTRAP_PORT | Bootstrap service HTTP port | 8180 |
| MF_BOOTSTRAP_SERVER_CERT | Path to server certificate in pem format | |
| MF_BOOTSTRAP_SERVER_KEY | Path to server key in pem format | |
| MF_SDK_BASE_URL | Base url for Mainflux SDK | http://localhost |
| MF_SDK_THINGS_PREFIX | SDK prefix for Things service | |
| MF_THINGS_ES_URL | Things service event source URL | localhost:6379 |
| MF_THINGS_ES_PASS | Things service event source password | |
| MF_THINGS_ES_DB | Things service event source database | 0 |
| MF_BOOTSTRAP_ES_URL | Bootstrap service event source URL | localhost:6379 |
| MF_BOOTSTRAP_ES_PASS | Bootstrap service event source password | |
| MF_BOOTSTRAP_ES_DB | Bootstrap service event source database | 0 |
| MF_BOOTSTRAP_EVENT_CONSUMER | Bootstrap service event source consumer name | bootstrap |
| MF_JAEGER_URL | Jaeger server URL | localhost:6831 |
| MF_AUTH_GRPC_URL | Auth service gRPC URL | localhost:8181 |
| MF_AUTH_GRPC_TIMEOUT | Auth service gRPC request timeout in seconds | 1s |
## Deployment
The service itself is distributed as Docker container. The following snippet
provides a compose file template that can be used to deploy the service container
locally:
```yaml
version: "2"
bootstrap:
image: mainflux/bootstrap:latest
container_name: mainflux-bootstrap
depends_on:
- bootstrap-db
restart: on-failure
ports:
- 8200:8200
environment:
MF_BOOTSTRAP_LOG_LEVEL: [Bootstrap log level]
MF_BOOTSTRAP_DB_HOST: [Database host address]
MF_BOOTSTRAP_DB_PORT: [Database host port]
MF_BOOTSTRAP_DB_USER: [Database user]
MF_BOOTSTRAP_DB_PASS: [Database password]
MF_BOOTSTRAP_DB: [Name of the database used by the service]
MF_BOOTSTRAP_DB_SSL_MODE: [SSL mode to connect to the database with]
MF_BOOTSTRAP_DB_SSL_CERT: [Path to the PEM encoded certificate file]
MF_BOOTSTRAP_DB_SSL_KEY: [Path to the PEM encoded key file]
MF_BOOTSTRAP_DB_SSL_ROOT_CERT: [Path to the PEM encoded root certificate file]
MF_BOOTSTRAP_CLIENT_TLS: [Boolean value to enable/disable client TLS]
MF_BOOTSTRAP_CA_CERTS: [Path to trusted CAs in PEM format]
MF_BOOTSTRAP_PORT: 8200
MF_BOOTSTRAP_SERVER_CERT: [String path to server cert in pem format]
MF_BOOTSTRAP_SERVER_KEY: [String path to server key in pem format]
MF_SDK_BASE_URL: [Base SDK URL for the Mainflux services]
MF_SDK_THINGS_PREFIX: [SDK prefix for Things service]
MF_USERS_URL: [Users service URL]
MF_THINGS_ES_URL: [Things service event source URL]
MF_THINGS_ES_PASS: [Things service event source password]
MF_THINGS_ES_DB: [Things service event source database]
MF_BOOTSTRAP_INSTANCE_NAME: [Bootstrap service instance name]
```
The service itself is distributed as Docker container. Check the [`boostrap`](https://github.com/mainflux/mainflux/blob/master/docker/addons/bootstrap/docker-compose.yml#L32-L56) service section in
docker-compose to see how service is deployed.
To start the service outside of the container, execute the following shell script:
```bash
# download the latest version of the service
go get github.com/mainflux/mainflux
git clone https://github.com/mainflux/mainflux
cd $GOPATH/src/github.com/mainflux/mainflux
cd mainflux
# compile the service
make bootstrap
@@ -112,7 +86,28 @@ make bootstrap
make install
# set the environment variables and run the service
MF_BOOTSTRAP_LOG_LEVEL=[Bootstrap log level] MF_BOOTSTRAP_DB_HOST=[Database host address] MF_BOOTSTRAP_DB_PORT=[Database host port] MF_BOOTSTRAP_DB_USER=[Database user] MF_BOOTSTRAP_DB_PASS=[Database password] MF_BOOTSTRAP_DB=[Name of the database used by the service] MF_BOOTSTRAP_DB_SSL_MODE=[SSL mode to connect to the database with] MF_BOOTSTRAP_DB_SSL_CERT=[Path to the PEM encoded certificate file] MF_BOOTSTRAP_DB_SSL_KEY=[Path to the PEM encoded key file] MF_BOOTSTRAP_DB_SSL_ROOT_CERT=[Path to the PEM encoded root certificate file] MF_BOOTSTRAP_CLIENT_TLS=[Boolean value to enable/disable client TLS] MF_BOOTSTRAP_CA_CERTS=[Path to trusted CAs in PEM format] MF_BOOTSTRAP_PORT=[Service HTTP port] MF_BOOTSTRAP_SERVER_CERT=[Path to server certificate] MF_BOOTSTRAP_SERVER_KEY=[Path to server key] MF_SDK_BASE_URL=[Base SDK URL for the Mainflux services] MF_SDK_THINGS_PREFIX=[SDK prefix for Things service] MF_USERS_URL=[Users service URL] $GOBIN/mainflux-bootstrap
MF_BOOTSTRAP_LOG_LEVEL=[Bootstrap log level] \
MF_BOOTSTRAP_DB_HOST=[Database host address] \
MF_BOOTSTRAP_DB_PORT=[Database host port] \
MF_BOOTSTRAP_DB_USER=[Database user] \
MF_BOOTSTRAP_DB_PASS=[Database password] \
MF_BOOTSTRAP_DB=[Name of the database used by the service] \
MF_BOOTSTRAP_DB_SSL_MODE=[SSL mode to connect to the database with] \
MF_BOOTSTRAP_DB_SSL_CERT=[Path to the PEM encoded certificate file] \
MF_BOOTSTRAP_DB_SSL_KEY=[Path to the PEM encoded key file] \
MF_BOOTSTRAP_DB_SSL_ROOT_CERT=[Path to the PEM encoded root certificate file] \
MF_BOOTSTRAP_ENCRYPT_KEY=[Hex-encoded encryption key used for secure bootstrap] \
MF_BOOTSTRAP_CLIENT_TLS=[Boolean value to enable/disable client TLS] \
MF_BOOTSTRAP_CA_CERTS=[Path to trusted CAs in PEM format] \
MF_BOOTSTRAP_PORT=[Service HTTP port] \
MF_BOOTSTRAP_SERVER_CERT=[Path to server certificate] \
MF_BOOTSTRAP_SERVER_KEY=[Path to server key] \
MF_SDK_BASE_URL=[Base SDK URL for the Mainflux services] \
MF_SDK_THINGS_PREFIX=[SDK prefix for Things service] \
MF_JAEGER_URL=[Jaeger server URL] \
MF_AUTH_GRPC_URL=[Auth service gRPC URL] \
MF_AUTH_GRPC_TIMEOUT=[Auth service gRPC request timeout in seconds] \
$GOBIN/mainflux-bootstrap
```
Setting `MF_BOOTSTRAP_CA_CERTS` expects a file in PEM format of trusted CAs. This will enable TLS against the Users gRPC endpoint trusting only those CAs that are provided.
@@ -120,6 +115,6 @@ Setting `MF_BOOTSTRAP_CA_CERTS` expects a file in PEM format of trusted CAs. Thi
## Usage
For more information about service capabilities and its usage, please check out
the [API documentation](swagger.yml).
the [API documentation](openapi.yml).
[doc]: http://mainflux.readthedocs.io
+2 -6
View File
@@ -1,9 +1,5 @@
//
// Copyright (c) 2018
// Mainflux
//
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
//
// Package api contains implementation of things service HTTP API.
// Package api contains implementation of bootstrap service HTTP API.
package api
+52 -48
View File
@@ -1,9 +1,5 @@
//
// Copyright (c) 2018
// Mainflux
//
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
//
package api
@@ -32,10 +28,13 @@ func addEndpoint(svc bootstrap.Service) endpoint.Endpoint {
ExternalKey: req.ExternalKey,
MFChannels: channels,
Name: req.Name,
ClientCert: req.ClientCert,
ClientKey: req.ClientKey,
CACert: req.CACert,
Content: req.Content,
}
saved, err := svc.Add(req.key, config)
saved, err := svc.Add(req.token, config)
if err != nil {
return nil, err
}
@@ -49,6 +48,23 @@ func addEndpoint(svc bootstrap.Service) endpoint.Endpoint {
}
}
func updateCertEndpoint(svc bootstrap.Service) endpoint.Endpoint {
return func(_ context.Context, request interface{}) (interface{}, error) {
req := request.(updateCertReq)
if err := req.validate(); err != nil {
return nil, err
}
if err := svc.UpdateCert(req.key, req.thingID, req.ClientCert, req.ClientKey, req.CACert); err != nil {
return nil, err
}
res := configRes{}
return res, nil
}
}
func viewEndpoint(svc bootstrap.Service) endpoint.Endpoint {
return func(_ context.Context, request interface{}) (interface{}, error) {
req := request.(entityReq)
@@ -146,49 +162,37 @@ func listEndpoint(svc bootstrap.Service) endpoint.Endpoint {
if err != nil {
return nil, err
}
switch {
case req.filter.Unknown:
res := listUnknownRes{}
for _, cfg := range page.Configs {
res.Configs = append(res.Configs, unknownRes{
ExternalID: cfg.ExternalID,
ExternalKey: cfg.ExternalKey,
res := listRes{
Total: page.Total,
Offset: page.Offset,
Limit: page.Limit,
Configs: []viewRes{},
}
for _, cfg := range page.Configs {
var channels []channelRes
for _, ch := range cfg.MFChannels {
channels = append(channels, channelRes{
ID: ch.ID,
Name: ch.Name,
Metadata: ch.Metadata,
})
}
return res, nil
default:
res := listRes{
Total: page.Total,
Offset: page.Offset,
Limit: page.Limit,
Configs: []viewRes{},
view := viewRes{
MFThing: cfg.MFThing,
MFKey: cfg.MFKey,
Channels: channels,
ExternalID: cfg.ExternalID,
ExternalKey: cfg.ExternalKey,
Name: cfg.Name,
Content: cfg.Content,
State: cfg.State,
}
for _, cfg := range page.Configs {
var channels []channelRes
for _, ch := range cfg.MFChannels {
channels = append(channels, channelRes{
ID: ch.ID,
Name: ch.Name,
Metadata: ch.Metadata,
})
}
view := viewRes{
MFThing: cfg.MFThing,
MFKey: cfg.MFKey,
Channels: channels,
ExternalID: cfg.ExternalID,
ExternalKey: cfg.ExternalKey,
Name: cfg.Name,
Content: cfg.Content,
State: cfg.State,
}
res.Configs = append(res.Configs, view)
}
return res, nil
res.Configs = append(res.Configs, view)
}
return res, nil
}
}
@@ -208,19 +212,19 @@ func removeEndpoint(svc bootstrap.Service) endpoint.Endpoint {
}
}
func bootstrapEndpoint(svc bootstrap.Service, reader bootstrap.ConfigReader) endpoint.Endpoint {
func bootstrapEndpoint(svc bootstrap.Service, reader bootstrap.ConfigReader, secure bool) endpoint.Endpoint {
return func(_ context.Context, request interface{}) (interface{}, error) {
req := request.(bootstrapReq)
if err := req.validate(); err != nil {
return nil, err
}
cfg, err := svc.Bootstrap(req.key, req.id)
cfg, err := svc.Bootstrap(req.key, req.id, secure)
if err != nil {
return nil, err
}
return reader.ReadConfig(cfg)
return reader.ReadConfig(cfg, secure)
}
}
+243 -161
View File
@@ -1,13 +1,13 @@
//
// Copyright (c) 2018
// Mainflux
//
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
//
package api_test
import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"encoding/hex"
"encoding/json"
"fmt"
"io"
@@ -22,9 +22,10 @@ import (
"github.com/mainflux/mainflux/bootstrap"
bsapi "github.com/mainflux/mainflux/bootstrap/api"
"github.com/mainflux/mainflux/bootstrap/mocks"
mfsdk "github.com/mainflux/mainflux/sdk/go"
mfsdk "github.com/mainflux/mainflux/pkg/sdk/go"
"github.com/mainflux/mainflux/things"
thingsapi "github.com/mainflux/mainflux/things/api/http"
thingsapi "github.com/mainflux/mainflux/things/api/things/http"
"github.com/opentracing/opentracing-go/mocktracer"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@@ -37,7 +38,6 @@ const (
channelsNum = 3
contentType = "application/json"
wrongID = "wrong_id"
metadata = `{"meta": "data"}`
addExternalID = "external-id"
addExternalKey = "external-key"
addName = "name"
@@ -45,7 +45,9 @@ const (
)
var (
encKey = []byte("1234567891011121")
addChannels = []string{"1"}
metadata = map[string]interface{}{"meta": "data"}
addReq = struct {
ThingID string `json:"thing_id"`
ExternalID string `json:"external_id"`
@@ -62,14 +64,26 @@ var (
}
updateReq = struct {
Channels []string `json:"channels"`
Content string `json:"content"`
State bootstrap.State `json:"state"`
Channels []string `json:"channels,omitempty"`
Content string `json:"content,omitempty"`
State bootstrap.State `json:"state,omitempty"`
ClientCert string `json:"client_cert,omitempty"`
ClientKey string `json:"client_key,omitempty"`
CACert string `json:"ca_cert,omitempty"`
}{
Channels: []string{"2", "3"},
Content: "config update",
State: 1,
Channels: []string{"2", "3"},
Content: "config update",
State: 1,
ClientCert: "newcert",
ClientKey: "newkey",
CACert: "newca",
}
bsErrorRes = toJSON(errorRes{bootstrap.ErrBootstrap.Error()})
unauthRes = toJSON(errorRes{bootstrap.ErrUnauthorizedAccess.Error()})
malformedRes = toJSON(errorRes{bootstrap.ErrMalformedEntity.Error()})
extKeyNotFoundRes = toJSON(errorRes{bootstrap.ErrExternalKeyNotFound.Error()})
extSecKeyNotFoundRes = toJSON(errorRes{bootstrap.ErrSecureBootstrap.Error()})
)
type testRequest struct {
@@ -88,6 +102,9 @@ func newConfig(channels []bootstrap.Channel) bootstrap.Config {
MFChannels: channels,
Name: addName,
Content: addContent,
ClientCert: "newcert",
ClientKey: "newkey",
CACert: "newca",
}
}
@@ -109,14 +126,44 @@ func (tr testRequest) make() (*http.Response, error) {
return tr.client.Do(req)
}
func newService(users mainflux.UsersServiceClient, unknown map[string]string, url string) bootstrap.Service {
things := mocks.NewConfigsRepository(unknown)
func enc(in []byte) ([]byte, error) {
block, err := aes.NewCipher(encKey)
if err != nil {
return nil, err
}
ciphertext := make([]byte, aes.BlockSize+len(in))
iv := ciphertext[:aes.BlockSize]
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
return nil, err
}
stream := cipher.NewCFBEncrypter(block, iv)
stream.XORKeyStream(ciphertext[aes.BlockSize:], in)
return ciphertext, nil
}
func dec(in []byte) ([]byte, error) {
block, err := aes.NewCipher(encKey)
if err != nil {
return nil, err
}
if len(in) < aes.BlockSize {
return nil, bootstrap.ErrMalformedEntity
}
iv := in[:aes.BlockSize]
in = in[aes.BlockSize:]
stream := cipher.NewCFBDecrypter(block, iv)
stream.XORKeyStream(in, in)
return in, nil
}
func newService(auth mainflux.AuthServiceClient, url string) bootstrap.Service {
things := mocks.NewConfigsRepository()
config := mfsdk.Config{
BaseURL: url,
}
sdk := mfsdk.NewSDK(config)
return bootstrap.New(users, things, sdk)
return bootstrap.New(auth, things, sdk, encKey)
}
func generateChannels() map[string]things.Channel {
@@ -132,17 +179,17 @@ func generateChannels() map[string]things.Channel {
return channels
}
func newThingsService(users mainflux.UsersServiceClient) things.Service {
return mocks.NewThingsService(map[string]things.Thing{}, generateChannels(), users)
func newThingsService(auth mainflux.AuthServiceClient) things.Service {
return mocks.NewThingsService(map[string]things.Thing{}, generateChannels(), auth)
}
func newThingsServer(svc things.Service) *httptest.Server {
mux := thingsapi.MakeHandler(svc)
mux := thingsapi.MakeHandler(mocktracer.New(), svc)
return httptest.NewServer(mux)
}
func newBootstrapServer(svc bootstrap.Service) *httptest.Server {
mux := bsapi.MakeHandler(svc, bootstrap.NewConfigReader())
mux := bsapi.MakeHandler(svc, bootstrap.NewConfigReader(encKey))
return httptest.NewServer(mux)
}
@@ -155,7 +202,7 @@ func TestAdd(t *testing.T) {
users := mocks.NewUsersService(map[string]string{validToken: email})
ts := newThingsServer(newThingsService(users))
svc := newService(users, nil, ts.URL)
svc := newService(users, ts.URL)
bs := newBootstrapServer(svc)
data := toJSON(addReq)
@@ -279,7 +326,7 @@ func TestView(t *testing.T) {
users := mocks.NewUsersService(map[string]string{validToken: email})
ts := newThingsServer(newThingsService(users))
svc := newService(users, nil, ts.URL)
svc := newService(users, ts.URL)
bs := newBootstrapServer(svc)
c := newConfig([]bootstrap.Channel{})
@@ -288,7 +335,7 @@ func TestView(t *testing.T) {
c.MFChannels = append(c.MFChannels, bootstrap.Channel{
ID: ch.ID,
Name: fmt.Sprintf("%s%s", "name ", id),
Metadata: fmt.Sprintf("{\"type\":\"some type %s\"}", id),
Metadata: map[string]interface{}{"type": fmt.Sprintf("some type %s", id)},
})
}
@@ -361,7 +408,7 @@ func TestView(t *testing.T) {
assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode))
var view config
if err := json.NewDecoder(res.Body).Decode(&view); err != io.EOF {
assert.Nil(t, err, fmt.Sprintf("Decoding expeceted to succeed %s: %s", tc.desc, err))
assert.Nil(t, err, fmt.Sprintf("Decoding expected to succeed %s: %s", tc.desc, err))
}
assert.ElementsMatch(t, tc.res.Channels, view.Channels, fmt.Sprintf("%s: expected response '%s' got '%s'", tc.desc, tc.res.Channels, view.Channels))
@@ -376,7 +423,7 @@ func TestUpdate(t *testing.T) {
users := mocks.NewUsersService(map[string]string{validToken: email})
ts := newThingsServer(newThingsService(users))
svc := newService(users, nil, ts.URL)
svc := newService(users, ts.URL)
bs := newBootstrapServer(svc)
c := newConfig([]bootstrap.Channel{bootstrap.Channel{ID: "1"}})
@@ -466,12 +513,106 @@ func TestUpdate(t *testing.T) {
assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode))
}
}
func TestUpdateCert(t *testing.T) {
users := mocks.NewUsersService(map[string]string{validToken: email})
ts := newThingsServer(newThingsService(users))
svc := newService(users, ts.URL)
bs := newBootstrapServer(svc)
c := newConfig([]bootstrap.Channel{bootstrap.Channel{ID: "1"}})
saved, err := svc.Add(validToken, c)
require.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err))
data := toJSON(updateReq)
cases := []struct {
desc string
req string
id string
auth string
contentType string
status int
}{
{
desc: "update unauthorized",
req: data,
id: saved.MFThing,
auth: invalidToken,
contentType: contentType,
status: http.StatusForbidden,
},
{
desc: "update with an empty token",
req: data,
id: saved.MFThing,
auth: "",
contentType: contentType,
status: http.StatusForbidden,
},
{
desc: "update a valid config",
req: data,
id: saved.MFThing,
auth: validToken,
contentType: contentType,
status: http.StatusOK,
},
{
desc: "update a config with wrong content type",
req: data,
id: saved.MFThing,
auth: validToken,
contentType: "",
status: http.StatusUnsupportedMediaType,
},
{
desc: "update a non-existing config",
req: data,
id: wrongID,
auth: validToken,
contentType: contentType,
status: http.StatusNotFound,
},
{
desc: "update a config with invalid request format",
req: "}",
id: saved.MFKey,
auth: validToken,
contentType: contentType,
status: http.StatusBadRequest,
},
{
desc: "update a config with an empty request",
id: saved.MFThing,
req: "",
auth: validToken,
contentType: contentType,
status: http.StatusBadRequest,
},
}
for _, tc := range cases {
req := testRequest{
client: bs.Client(),
method: http.MethodPatch,
url: fmt.Sprintf("%s/things/configs/certs/%s", bs.URL, tc.id),
contentType: tc.contentType,
token: tc.auth,
body: strings.NewReader(tc.req),
}
res, err := req.make()
assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err))
assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode))
}
}
func TestUpdateConnections(t *testing.T) {
users := mocks.NewUsersService(map[string]string{validToken: email})
ts := newThingsServer(newThingsService(users))
svc := newService(users, nil, ts.URL)
svc := newService(users, ts.URL)
bs := newBootstrapServer(svc)
c := newConfig([]bootstrap.Channel{bootstrap.Channel{ID: "1"}})
@@ -583,7 +724,7 @@ func TestList(t *testing.T) {
users := mocks.NewUsersService(map[string]string{validToken: email})
ts := newThingsServer(newThingsService(users))
svc := newService(users, nil, ts.URL)
svc := newService(users, ts.URL)
bs := newBootstrapServer(svc)
path := fmt.Sprintf("%s/%s", bs.URL, "things/configs")
@@ -751,7 +892,7 @@ func TestList(t *testing.T) {
res: configPage{},
},
{
desc: "view list with invalid query params",
desc: "view list with invalid query parameters",
auth: validToken,
url: fmt.Sprintf("%s?offset=%d&limit=%d&state=%d&key=%%", path, 10, 10, bootstrap.Inactive),
status: http.StatusBadRequest,
@@ -832,7 +973,7 @@ func TestRemove(t *testing.T) {
users := mocks.NewUsersService(map[string]string{validToken: email})
ts := newThingsServer(newThingsService(users))
svc := newService(users, nil, ts.URL)
svc := newService(users, ts.URL)
bs := newBootstrapServer(svc)
c := newConfig([]bootstrap.Channel{bootstrap.Channel{ID: "1"}})
@@ -890,108 +1031,11 @@ func TestRemove(t *testing.T) {
}
}
func TestListUnknown(t *testing.T) {
unknownNum := 10
unknown := make([]config, unknownNum)
unknownConfigs := make(map[string]string, unknownNum)
// Save some unknown elements.
for i := 0; i < unknownNum; i++ {
u := config{
ExternalID: fmt.Sprintf("key-%s", strconv.Itoa(i)),
ExternalKey: fmt.Sprintf("%s%s", addExternalKey, strconv.Itoa(i)),
}
unknownConfigs[u.ExternalID] = u.ExternalKey
unknown[i] = u
}
users := mocks.NewUsersService(map[string]string{validToken: email})
ts := newThingsServer(newThingsService(users))
svc := newService(users, unknownConfigs, ts.URL)
bs := newBootstrapServer(svc)
path := fmt.Sprintf("%s/%s", bs.URL, "things/unknown/configs")
cases := []struct {
desc string
auth string
url string
status int
res []config
}{
{
desc: "view unknown unauthorized",
auth: invalidToken,
url: fmt.Sprintf("%s?offset=%d&limit=%d", path, 0, 5),
status: http.StatusForbidden,
res: nil,
},
{
desc: "view unknown with an empty token",
auth: "",
url: fmt.Sprintf("%s?offset=%d&limit=%d", path, 0, 5),
status: http.StatusForbidden,
res: nil,
},
{
desc: "view unknown with limit < 0",
auth: validToken,
url: fmt.Sprintf("%s?offset=%d&limit=%d", path, 0, -5),
status: http.StatusBadRequest,
res: nil,
},
{
desc: "view unknown with offset < 0",
auth: validToken,
url: fmt.Sprintf("%s?offset=%d&limit=%d", path, -3, 5),
status: http.StatusBadRequest,
res: nil,
},
{
desc: "view unknown with invalid query params",
auth: validToken,
url: fmt.Sprintf("%s?offset=%d&limit=%d&key=%%", path, 0, -5),
status: http.StatusBadRequest,
res: nil,
},
{
desc: "view a list of unknown",
auth: validToken,
url: fmt.Sprintf("%s?offset=%d&limit=%d", path, 0, 5),
status: http.StatusOK,
res: unknown[:5],
},
{
desc: "view unknown with no page paremeters",
auth: validToken,
url: fmt.Sprintf("%s", path),
status: http.StatusOK,
res: unknown[:10],
},
}
for _, tc := range cases {
req := testRequest{
client: bs.Client(),
method: http.MethodGet,
url: tc.url,
token: tc.auth,
}
res, err := req.make()
assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err))
assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode))
var body map[string][]config
json.NewDecoder(res.Body).Decode(&body)
assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err))
assert.ElementsMatch(t, tc.res, body["configs"], fmt.Sprintf("%s: expected response '%s' got '%s'", tc.desc, tc.res, body["configs"]))
}
}
func TestBootstrap(t *testing.T) {
users := mocks.NewUsersService(map[string]string{validToken: email})
ts := newThingsServer(newThingsService(users))
svc := newService(users, map[string]string{}, ts.URL)
svc := newService(users, ts.URL)
bs := newBootstrapServer(svc)
c := newConfig([]bootstrap.Channel{bootstrap.Channel{ID: "1"}})
@@ -999,6 +1043,9 @@ func TestBootstrap(t *testing.T) {
saved, err := svc.Add(validToken, c)
require.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err))
encExternKey, err := enc([]byte(c.ExternalKey))
require.Nil(t, err, fmt.Sprintf("Encrypting config expected to succeed: %s.\n", err))
var channels []channel
for _, ch := range saved.MFChannels {
channels = append(channels, channel{ID: ch.ID, Name: ch.Name, Metadata: ch.Metadata})
@@ -1009,56 +1056,84 @@ func TestBootstrap(t *testing.T) {
MFKey string `json:"mainflux_key"`
MFChannels []channel `json:"mainflux_channels"`
Content string `json:"content"`
ClientCert string `json:"client_cert"`
ClientKey string `json:"client_key"`
CACert string `json:"ca_cert"`
}{
MFThing: saved.MFThing,
MFKey: saved.MFKey,
MFChannels: channels,
Content: saved.Content,
ClientCert: saved.ClientCert,
ClientKey: saved.ClientKey,
CACert: saved.CACert,
}
data := toJSON(s)
cases := []struct {
desc string
external_id string
external_key string
status int
res string
desc string
externalID string
externalKey string
status int
res string
secure bool
}{
{
desc: "bootstrap a Thing with unknown ID",
external_id: unknown,
external_key: c.ExternalKey,
status: http.StatusNotFound,
res: "",
desc: "bootstrap a Thing with unknown ID",
externalID: unknown,
externalKey: c.ExternalKey,
status: http.StatusNotFound,
res: bsErrorRes,
secure: false,
},
{
desc: "bootstrap a Thing with an empty ID",
external_id: "",
external_key: c.ExternalKey,
status: http.StatusBadRequest,
res: "",
desc: "bootstrap a Thing with an empty ID",
externalID: "",
externalKey: c.ExternalKey,
status: http.StatusBadRequest,
res: malformedRes,
secure: false,
},
{
desc: "bootstrap a Thing with unknown key",
external_id: c.ExternalID,
external_key: unknown,
status: http.StatusNotFound,
res: "",
desc: "bootstrap a Thing with unknown key",
externalID: c.ExternalID,
externalKey: unknown,
status: http.StatusNotFound,
res: extKeyNotFoundRes,
secure: false,
},
{
desc: "bootstrap a Thing with an empty key",
external_id: c.ExternalID,
external_key: "",
status: http.StatusForbidden,
res: "",
desc: "bootstrap a Thing with an empty key",
externalID: c.ExternalID,
externalKey: "",
status: http.StatusForbidden,
res: unauthRes,
secure: false,
},
{
desc: "bootstrap known Thing",
external_id: c.ExternalID,
external_key: c.ExternalKey,
status: http.StatusOK,
res: data,
desc: "bootstrap known Thing",
externalID: c.ExternalID,
externalKey: c.ExternalKey,
status: http.StatusOK,
res: data,
secure: false,
},
{
desc: "bootstrap secure",
externalID: fmt.Sprintf("secure/%s", c.ExternalID),
externalKey: hex.EncodeToString(encExternKey),
status: http.StatusOK,
res: data,
secure: true,
},
{
desc: "bootstrap secure with unencrypted key",
externalID: fmt.Sprintf("secure/%s", c.ExternalID),
externalKey: c.ExternalKey,
status: http.StatusNotFound,
res: extSecKeyNotFoundRes,
secure: true,
},
}
@@ -1066,8 +1141,8 @@ func TestBootstrap(t *testing.T) {
req := testRequest{
client: bs.Client(),
method: http.MethodGet,
url: fmt.Sprintf("%s/things/bootstrap/%s", bs.URL, tc.external_id),
token: tc.external_key,
url: fmt.Sprintf("%s/things/bootstrap/%s", bs.URL, tc.externalID),
token: tc.externalKey,
}
res, err := req.make()
assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err))
@@ -1075,6 +1150,9 @@ func TestBootstrap(t *testing.T) {
assert.Equal(t, tc.status, res.StatusCode, fmt.Sprintf("%s: expected status code %d got %d", tc.desc, tc.status, res.StatusCode))
body, err := ioutil.ReadAll(res.Body)
assert.Nil(t, err, fmt.Sprintf("%s: unexpected error %s", tc.desc, err))
if tc.secure && tc.status == http.StatusOK {
body, err = dec(body)
}
data := strings.Trim(string(body), "\n")
assert.Equal(t, tc.res, data, fmt.Sprintf("%s: expected response '%s' got '%s'", tc.desc, tc.res, data))
@@ -1085,7 +1163,7 @@ func TestChangeState(t *testing.T) {
users := mocks.NewUsersService(map[string]string{validToken: email})
ts := newThingsServer(newThingsService(users))
svc := newService(users, nil, ts.URL)
svc := newService(users, ts.URL)
bs := newBootstrapServer(svc)
c := newConfig([]bootstrap.Channel{bootstrap.Channel{ID: "1"}})
@@ -1208,3 +1286,7 @@ type configPage struct {
Limit uint64 `json:"limit"`
Configs []config `json:"configs"`
}
type errorRes struct {
Err string `json:"error"`
}
+37 -28
View File
@@ -1,9 +1,5 @@
//
// Copyright (c) 2018
// Mainflux
//
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
//
// +build !test
@@ -29,9 +25,9 @@ func NewLoggingMiddleware(svc bootstrap.Service, logger log.Logger) bootstrap.Se
return &loggingMiddleware{logger, svc}
}
func (lm *loggingMiddleware) Add(key string, thing bootstrap.Config) (saved bootstrap.Config, err error) {
func (lm *loggingMiddleware) Add(token string, cfg bootstrap.Config) (saved bootstrap.Config, err error) {
defer func(begin time.Time) {
message := fmt.Sprintf("Method add for key %s and thing %s took %s to complete", key, saved.MFThing, time.Since(begin))
message := fmt.Sprintf("Method add for token %s and thing %s took %s to complete", token, saved.MFThing, time.Since(begin))
if err != nil {
lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err))
return
@@ -39,12 +35,12 @@ func (lm *loggingMiddleware) Add(key string, thing bootstrap.Config) (saved boot
lm.logger.Info(fmt.Sprintf("%s without errors.", message))
}(time.Now())
return lm.svc.Add(key, thing)
return lm.svc.Add(token, cfg)
}
func (lm *loggingMiddleware) View(key, id string) (saved bootstrap.Config, err error) {
func (lm *loggingMiddleware) View(token, id string) (saved bootstrap.Config, err error) {
defer func(begin time.Time) {
message := fmt.Sprintf("Method view for key %s and thing %s took %s to complete", key, saved.MFThing, time.Since(begin))
message := fmt.Sprintf("Method view for token %s and thing %s took %s to complete", token, saved.MFThing, time.Since(begin))
if err != nil {
lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err))
return
@@ -52,12 +48,12 @@ func (lm *loggingMiddleware) View(key, id string) (saved bootstrap.Config, err e
lm.logger.Info(fmt.Sprintf("%s without errors.", message))
}(time.Now())
return lm.svc.View(key, id)
return lm.svc.View(token, id)
}
func (lm *loggingMiddleware) Update(key string, thing bootstrap.Config) (err error) {
func (lm *loggingMiddleware) Update(token string, cfg bootstrap.Config) (err error) {
defer func(begin time.Time) {
message := fmt.Sprintf("Method update for key %s and thing %s took %s to complete", key, thing.MFThing, time.Since(begin))
message := fmt.Sprintf("Method update for token %s and thing %s took %s to complete", token, cfg.MFThing, time.Since(begin))
if err != nil {
lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err))
return
@@ -65,12 +61,12 @@ func (lm *loggingMiddleware) Update(key string, thing bootstrap.Config) (err err
lm.logger.Info(fmt.Sprintf("%s without errors.", message))
}(time.Now())
return lm.svc.Update(key, thing)
return lm.svc.Update(token, cfg)
}
func (lm *loggingMiddleware) UpdateConnections(key, id string, connections []string) (err error) {
func (lm *loggingMiddleware) UpdateCert(token, thingID, clientCert, clientKey, caCert string) (err error) {
defer func(begin time.Time) {
message := fmt.Sprintf("Method update_connections for key %s and thing %s took %s to complete", key, id, time.Since(begin))
message := fmt.Sprintf("Method update_cert for thing with id %s took %s to complete", thingID, time.Since(begin))
if err != nil {
lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err))
return
@@ -78,12 +74,12 @@ func (lm *loggingMiddleware) UpdateConnections(key, id string, connections []str
lm.logger.Info(fmt.Sprintf("%s without errors.", message))
}(time.Now())
return lm.svc.UpdateConnections(key, id, connections)
return lm.svc.UpdateCert(token, thingID, clientCert, clientKey, caCert)
}
func (lm *loggingMiddleware) List(key string, filter bootstrap.Filter, offset, limit uint64) (res bootstrap.ConfigsPage, err error) {
func (lm *loggingMiddleware) UpdateConnections(token, id string, connections []string) (err error) {
defer func(begin time.Time) {
message := fmt.Sprintf("Method list for key %s and offset %d and limit %d took %s to complete", key, offset, limit, time.Since(begin))
message := fmt.Sprintf("Method update_connections for token %s and thing %s took %s to complete", token, id, time.Since(begin))
if err != nil {
lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err))
return
@@ -91,12 +87,12 @@ func (lm *loggingMiddleware) List(key string, filter bootstrap.Filter, offset, l
lm.logger.Info(fmt.Sprintf("%s without errors.", message))
}(time.Now())
return lm.svc.List(key, filter, offset, limit)
return lm.svc.UpdateConnections(token, id, connections)
}
func (lm *loggingMiddleware) Remove(key, id string) (err error) {
func (lm *loggingMiddleware) List(token string, filter bootstrap.Filter, offset, limit uint64) (res bootstrap.ConfigsPage, err error) {
defer func(begin time.Time) {
message := fmt.Sprintf("Method remove for key %s and thing %s took %s to complete", key, id, time.Since(begin))
message := fmt.Sprintf("Method list for token %s and offset %d and limit %d took %s to complete", token, offset, limit, time.Since(begin))
if err != nil {
lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err))
return
@@ -104,10 +100,23 @@ func (lm *loggingMiddleware) Remove(key, id string) (err error) {
lm.logger.Info(fmt.Sprintf("%s without errors.", message))
}(time.Now())
return lm.svc.Remove(key, id)
return lm.svc.List(token, filter, offset, limit)
}
func (lm *loggingMiddleware) Bootstrap(externalKey, externalID string) (cfg bootstrap.Config, err error) {
func (lm *loggingMiddleware) Remove(token, id string) (err error) {
defer func(begin time.Time) {
message := fmt.Sprintf("Method remove for token %s and thing %s took %s to complete", token, id, time.Since(begin))
if err != nil {
lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err))
return
}
lm.logger.Info(fmt.Sprintf("%s without errors.", message))
}(time.Now())
return lm.svc.Remove(token, id)
}
func (lm *loggingMiddleware) Bootstrap(externalKey, externalID string, secure bool) (cfg bootstrap.Config, err error) {
defer func(begin time.Time) {
message := fmt.Sprintf("Method bootstrap for thing with external id %s took %s to complete", externalID, time.Since(begin))
if err != nil {
@@ -117,12 +126,12 @@ func (lm *loggingMiddleware) Bootstrap(externalKey, externalID string) (cfg boot
lm.logger.Info(fmt.Sprintf("%s without errors.", message))
}(time.Now())
return lm.svc.Bootstrap(externalKey, externalID)
return lm.svc.Bootstrap(externalKey, externalID, secure)
}
func (lm *loggingMiddleware) ChangeState(key, id string, state bootstrap.State) (err error) {
func (lm *loggingMiddleware) ChangeState(token, id string, state bootstrap.State) (err error) {
defer func(begin time.Time) {
message := fmt.Sprintf("Method change_state for key %s and thing %s took %s to complete", key, id, time.Since(begin))
message := fmt.Sprintf("Method change_state for token %s and thing %s took %s to complete", token, id, time.Since(begin))
if err != nil {
lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err))
return
@@ -130,7 +139,7 @@ func (lm *loggingMiddleware) ChangeState(key, id string, state bootstrap.State)
lm.logger.Info(fmt.Sprintf("%s without errors.", message))
}(time.Now())
return lm.svc.ChangeState(key, id, state)
return lm.svc.ChangeState(token, id, state)
}
func (lm *loggingMiddleware) UpdateChannelHandler(channel bootstrap.Channel) (err error) {
+27 -23
View File
@@ -1,9 +1,5 @@
//
// Copyright (c) 2018
// Mainflux
//
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
//
// +build !test
@@ -24,8 +20,7 @@ type metricsMiddleware struct {
svc bootstrap.Service
}
// MetricsMiddleware instruments core service by tracking request count and
// latency.
// MetricsMiddleware instruments core service by tracking request count and latency.
func MetricsMiddleware(svc bootstrap.Service, counter metrics.Counter, latency metrics.Histogram) bootstrap.Service {
return &metricsMiddleware{
counter: counter,
@@ -34,76 +29,85 @@ func MetricsMiddleware(svc bootstrap.Service, counter metrics.Counter, latency m
}
}
func (mm *metricsMiddleware) Add(key string, thing bootstrap.Config) (saved bootstrap.Config, err error) {
func (mm *metricsMiddleware) Add(token string, cfg bootstrap.Config) (saved bootstrap.Config, err error) {
defer func(begin time.Time) {
mm.counter.With("method", "add").Add(1)
mm.latency.With("method", "add").Observe(time.Since(begin).Seconds())
}(time.Now())
return mm.svc.Add(key, thing)
return mm.svc.Add(token, cfg)
}
func (mm *metricsMiddleware) View(id, key string) (saved bootstrap.Config, err error) {
func (mm *metricsMiddleware) View(token, id string) (saved bootstrap.Config, err error) {
defer func(begin time.Time) {
mm.counter.With("method", "view").Add(1)
mm.latency.With("method", "view").Observe(time.Since(begin).Seconds())
}(time.Now())
return mm.svc.View(id, key)
return mm.svc.View(token, id)
}
func (mm *metricsMiddleware) Update(key string, thing bootstrap.Config) (err error) {
func (mm *metricsMiddleware) Update(token string, cfg bootstrap.Config) (err error) {
defer func(begin time.Time) {
mm.counter.With("method", "update").Add(1)
mm.latency.With("method", "update").Observe(time.Since(begin).Seconds())
}(time.Now())
return mm.svc.Update(key, thing)
return mm.svc.Update(token, cfg)
}
func (mm *metricsMiddleware) UpdateConnections(key, id string, connections []string) (err error) {
func (mm *metricsMiddleware) UpdateCert(token, thingKey, clientCert, clientKey, caCert string) (err error) {
defer func(begin time.Time) {
mm.counter.With("method", "update_cert").Add(1)
mm.latency.With("method", "update_cert").Observe(time.Since(begin).Seconds())
}(time.Now())
return mm.svc.UpdateCert(token, thingKey, clientCert, clientKey, caCert)
}
func (mm *metricsMiddleware) UpdateConnections(token, id string, connections []string) (err error) {
defer func(begin time.Time) {
mm.counter.With("method", "update_connections").Add(1)
mm.latency.With("method", "update_connections").Observe(time.Since(begin).Seconds())
}(time.Now())
return mm.svc.UpdateConnections(key, id, connections)
return mm.svc.UpdateConnections(token, id, connections)
}
func (mm *metricsMiddleware) List(key string, filter bootstrap.Filter, offset, limit uint64) (saved bootstrap.ConfigsPage, err error) {
func (mm *metricsMiddleware) List(token string, filter bootstrap.Filter, offset, limit uint64) (saved bootstrap.ConfigsPage, err error) {
defer func(begin time.Time) {
mm.counter.With("method", "list").Add(1)
mm.latency.With("method", "list").Observe(time.Since(begin).Seconds())
}(time.Now())
return mm.svc.List(key, filter, offset, limit)
return mm.svc.List(token, filter, offset, limit)
}
func (mm *metricsMiddleware) Remove(id, key string) (err error) {
func (mm *metricsMiddleware) Remove(token, id string) (err error) {
defer func(begin time.Time) {
mm.counter.With("method", "remove").Add(1)
mm.latency.With("method", "remove").Observe(time.Since(begin).Seconds())
}(time.Now())
return mm.svc.Remove(id, key)
return mm.svc.Remove(token, id)
}
func (mm *metricsMiddleware) Bootstrap(externalKey, externalID string) (cfg bootstrap.Config, err error) {
func (mm *metricsMiddleware) Bootstrap(externalKey, externalID string, secure bool) (cfg bootstrap.Config, err error) {
defer func(begin time.Time) {
mm.counter.With("method", "bootstrap").Add(1)
mm.latency.With("method", "bootstrap").Observe(time.Since(begin).Seconds())
}(time.Now())
return mm.svc.Bootstrap(externalKey, externalID)
return mm.svc.Bootstrap(externalKey, externalID, secure)
}
func (mm *metricsMiddleware) ChangeState(id, key string, state bootstrap.State) (err error) {
func (mm *metricsMiddleware) ChangeState(token, id string, state bootstrap.State) (err error) {
defer func(begin time.Time) {
mm.counter.With("method", "change_state").Add(1)
mm.latency.With("method", "change_state").Observe(time.Since(begin).Seconds())
}(time.Now())
return mm.svc.ChangeState(id, key, state)
return mm.svc.ChangeState(token, id, state)
}
func (mm *metricsMiddleware) UpdateChannelHandler(channel bootstrap.Channel) (err error) {
+26 -7
View File
@@ -1,9 +1,5 @@
//
// Copyright (c) 2018
// Mainflux
//
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
//
package api
@@ -14,17 +10,20 @@ type apiReq interface {
}
type addReq struct {
key string
token string
ThingID string `json:"thing_id"`
ExternalID string `json:"external_id"`
ExternalKey string `json:"external_key"`
Channels []string `json:"channels"`
Name string `json:"name"`
Content string `json:"content"`
ClientCert string `json:"client_cert"`
ClientKey string `json:"client_key"`
CACert string `json:"ca_cert"`
}
func (req addReq) validate() error {
if req.key == "" {
if req.token == "" {
return bootstrap.ErrUnauthorizedAccess
}
@@ -71,6 +70,26 @@ func (req updateReq) validate() error {
return nil
}
type updateCertReq struct {
key string
thingID string
ClientCert string `json:"client_cert"`
ClientKey string `json:"client_key"`
CACert string `json:"ca_cert"`
}
func (req updateCertReq) validate() error {
if req.key == "" {
return bootstrap.ErrUnauthorizedAccess
}
if req.thingID == "" {
return bootstrap.ErrNotFound
}
return nil
}
type updateConnReq struct {
key string
id string
+37 -5
View File
@@ -11,28 +11,28 @@ import (
func TestAddReqValidation(t *testing.T) {
cases := []struct {
desc string
key string
token string
externalID string
externalKey string
err error
}{
{
desc: "empty key",
key: "",
token: "",
externalID: "external-id",
externalKey: "external-key",
err: bootstrap.ErrUnauthorizedAccess,
},
{
desc: "empty external ID",
key: "key",
token: "token",
externalID: "",
externalKey: "external-key",
err: bootstrap.ErrMalformedEntity,
},
{
desc: "empty external key",
key: "key",
token: "token",
externalID: "external-id",
externalKey: "",
err: bootstrap.ErrMalformedEntity,
@@ -41,7 +41,7 @@ func TestAddReqValidation(t *testing.T) {
for _, tc := range cases {
req := addReq{
key: tc.key,
token: tc.token,
ExternalID: tc.externalID,
ExternalKey: tc.externalKey,
}
@@ -114,6 +114,38 @@ func TestUpdateReqValidation(t *testing.T) {
}
}
func TestUpdateCertReqValidation(t *testing.T) {
cases := []struct {
desc string
key string
thingID string
err error
}{
{
desc: "empty key",
key: "",
thingID: "thingID",
err: bootstrap.ErrUnauthorizedAccess,
},
{
desc: "empty thing key",
key: "key",
thingID: "",
err: bootstrap.ErrNotFound,
},
}
for _, tc := range cases {
req := updateCertReq{
key: tc.key,
thingID: tc.thingID,
}
err := req.validate()
assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
}
}
func TestUpdateConnReqValidation(t *testing.T) {
cases := []struct {
desc string
+5 -26
View File
@@ -1,9 +1,5 @@
//
// Copyright (c) 2018
// Mainflux
//
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
//
package api
@@ -93,27 +89,6 @@ func (res viewRes) Empty() bool {
return false
}
type unknownRes struct {
ExternalID string `json:"external_id"`
ExternalKey string `json:"external_key,omitempty"`
}
type listUnknownRes struct {
Configs []unknownRes `json:"configs"`
}
func (res listUnknownRes) Code() int {
return http.StatusOK
}
func (res listUnknownRes) Headers() map[string]string {
return map[string]string{}
}
func (res listUnknownRes) Empty() bool {
return false
}
type listRes struct {
Total uint64 `json:"total"`
Offset uint64 `json:"offset"`
@@ -146,3 +121,7 @@ func (res stateRes) Headers() map[string]string {
func (res stateRes) Empty() bool {
return true
}
type errorRes struct {
Err string `json:"error"`
}
+86 -70
View File
@@ -1,16 +1,11 @@
//
// Copyright (c) 2018
// Mainflux
//
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
//
package api
import (
"context"
"encoding/json"
"errors"
"io"
"net/http"
"net/url"
@@ -21,6 +16,7 @@ import (
"github.com/go-zoo/bone"
"github.com/mainflux/mainflux"
"github.com/mainflux/mainflux/bootstrap"
"github.com/mainflux/mainflux/pkg/errors"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
@@ -31,10 +27,10 @@ const (
)
var (
errUnsupportedContentType = errors.New("unsupported content type")
errInvalidQueryParams = errors.New("invalid query params")
fullMatch = []string{"state", "external_id", "mainflux_id", "mainflux_key"}
partialMatch = []string{"name"}
errInvalidLimitParam = errors.New("invalid limit query param")
errInvalidOffsetParam = errors.New("invalid offset query param")
fullMatch = []string{"state", "external_id", "mainflux_id", "mainflux_key"}
partialMatch = []string{"name"}
)
// MakeHandler returns a HTTP handler for API endpoints.
@@ -62,6 +58,12 @@ func MakeHandler(svc bootstrap.Service, reader bootstrap.ConfigReader) http.Hand
encodeResponse,
opts...))
r.Patch("/things/configs/certs/:id", kithttp.NewServer(
updateCertEndpoint(svc),
decodeUpdateCertRequest,
encodeResponse,
opts...))
r.Put("/things/configs/connections/:id", kithttp.NewServer(
updateConnEndpoint(svc),
decodeUpdateConnRequest,
@@ -74,16 +76,16 @@ func MakeHandler(svc bootstrap.Service, reader bootstrap.ConfigReader) http.Hand
encodeResponse,
opts...))
r.Get("/things/unknown/configs", kithttp.NewServer(
listEndpoint(svc),
decodeUnknownRequest,
r.Get("/things/bootstrap/:external_id", kithttp.NewServer(
bootstrapEndpoint(svc, reader, false),
decodeBootstrapRequest,
encodeResponse,
opts...))
r.Get("/things/bootstrap/:external_id", kithttp.NewServer(
bootstrapEndpoint(svc, reader),
r.Get("/things/bootstrap/secure/:external_id", kithttp.NewServer(
bootstrapEndpoint(svc, reader, true),
decodeBootstrapRequest,
encodeResponse,
encodeSecureRes,
opts...))
r.Put("/things/state/:id", kithttp.NewServer(
@@ -106,12 +108,12 @@ func MakeHandler(svc bootstrap.Service, reader bootstrap.ConfigReader) http.Hand
func decodeAddRequest(_ context.Context, r *http.Request) (interface{}, error) {
if !strings.Contains(r.Header.Get("Content-Type"), contentType) {
return nil, errUnsupportedContentType
return nil, errors.ErrUnsupportedContentType
}
req := addReq{key: r.Header.Get("Authorization")}
req := addReq{token: r.Header.Get("Authorization")}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
return nil, err
return nil, errors.Wrap(bootstrap.ErrMalformedEntity, err)
}
return req, nil
@@ -119,13 +121,30 @@ func decodeAddRequest(_ context.Context, r *http.Request) (interface{}, error) {
func decodeUpdateRequest(_ context.Context, r *http.Request) (interface{}, error) {
if !strings.Contains(r.Header.Get("Content-Type"), contentType) {
return nil, errUnsupportedContentType
return nil, errors.ErrUnsupportedContentType
}
req := updateReq{key: r.Header.Get("Authorization")}
req.id = bone.GetValue(r, "id")
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
return nil, err
return nil, errors.Wrap(bootstrap.ErrMalformedEntity, err)
}
return req, nil
}
func decodeUpdateCertRequest(_ context.Context, r *http.Request) (interface{}, error) {
if !strings.Contains(r.Header.Get("Content-Type"), contentType) {
return nil, errors.ErrUnsupportedContentType
}
req := updateCertReq{
key: r.Header.Get("Authorization"),
thingID: bone.GetValue(r, "id"),
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
return nil, errors.Wrap(bootstrap.ErrMalformedEntity, err)
}
return req, nil
@@ -133,34 +152,13 @@ func decodeUpdateRequest(_ context.Context, r *http.Request) (interface{}, error
func decodeUpdateConnRequest(_ context.Context, r *http.Request) (interface{}, error) {
if !strings.Contains(r.Header.Get("Content-Type"), contentType) {
return nil, errUnsupportedContentType
return nil, errors.ErrUnsupportedContentType
}
req := updateConnReq{key: r.Header.Get("Authorization")}
req.id = bone.GetValue(r, "id")
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
return nil, err
}
return req, nil
}
func decodeUnknownRequest(_ context.Context, r *http.Request) (interface{}, error) {
q, err := url.ParseQuery(r.URL.RawQuery)
if err != nil {
return nil, errInvalidQueryParams
}
offset, limit, err := parsePagePrams(q)
if err != nil {
return nil, err
}
req := listReq{
key: r.Header.Get("Authorization"),
filter: bootstrap.Filter{Unknown: true},
offset: offset,
limit: limit,
return nil, errors.Wrap(bootstrap.ErrMalformedEntity, err)
}
return req, nil
@@ -169,7 +167,7 @@ func decodeUnknownRequest(_ context.Context, r *http.Request) (interface{}, erro
func decodeListRequest(_ context.Context, r *http.Request) (interface{}, error) {
q, err := url.ParseQuery(r.URL.RawQuery)
if err != nil {
return nil, errInvalidQueryParams
return nil, errors.ErrInvalidQueryParams
}
offset, limit, err := parsePagePrams(q)
@@ -200,13 +198,13 @@ func decodeBootstrapRequest(_ context.Context, r *http.Request) (interface{}, er
func decodeStateRequest(_ context.Context, r *http.Request) (interface{}, error) {
if !strings.Contains(r.Header.Get("Content-Type"), contentType) {
return nil, errUnsupportedContentType
return nil, errors.ErrUnsupportedContentType
}
req := changeStateReq{key: r.Header.Get("Authorization")}
req.id = bone.GetValue(r, "id")
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
return nil, err
return nil, errors.Wrap(bootstrap.ErrMalformedEntity, err)
}
return req, nil
@@ -238,33 +236,51 @@ func encodeResponse(_ context.Context, w http.ResponseWriter, response interface
return json.NewEncoder(w).Encode(response)
}
func encodeError(_ context.Context, err error, w http.ResponseWriter) {
func encodeSecureRes(_ context.Context, w http.ResponseWriter, response interface{}) error {
w.Header().Set("Content-Type", contentType)
w.WriteHeader(http.StatusOK)
if b, ok := response.([]byte); ok {
if _, err := w.Write(b); err != nil {
return err
}
}
return nil
}
switch err {
case errUnsupportedContentType:
w.WriteHeader(http.StatusUnsupportedMediaType)
case errInvalidQueryParams, bootstrap.ErrMalformedEntity:
w.WriteHeader(http.StatusBadRequest)
case bootstrap.ErrNotFound:
w.WriteHeader(http.StatusNotFound)
case bootstrap.ErrUnauthorizedAccess:
w.WriteHeader(http.StatusForbidden)
case bootstrap.ErrConflict:
w.WriteHeader(http.StatusConflict)
case bootstrap.ErrThings:
w.WriteHeader(http.StatusServiceUnavailable)
case io.EOF:
w.WriteHeader(http.StatusBadRequest)
default:
switch err.(type) {
case *json.SyntaxError:
func encodeError(_ context.Context, err error, w http.ResponseWriter) {
switch errorVal := err.(type) {
case errors.Error:
w.Header().Set("Content-Type", contentType)
switch {
case errors.Contains(errorVal, errors.ErrUnsupportedContentType):
w.WriteHeader(http.StatusUnsupportedMediaType)
case errors.Contains(errorVal, errors.ErrInvalidQueryParams):
w.WriteHeader(http.StatusBadRequest)
case *json.UnmarshalTypeError:
case errors.Contains(errorVal, bootstrap.ErrMalformedEntity):
w.WriteHeader(http.StatusBadRequest)
case errors.Contains(errorVal, bootstrap.ErrNotFound):
w.WriteHeader(http.StatusNotFound)
case errors.Contains(errorVal, bootstrap.ErrUnauthorizedAccess):
w.WriteHeader(http.StatusForbidden)
case errors.Contains(errorVal, bootstrap.ErrConflict):
w.WriteHeader(http.StatusConflict)
case errors.Contains(errorVal, bootstrap.ErrThings):
w.WriteHeader(http.StatusServiceUnavailable)
case errors.Contains(errorVal, io.EOF):
w.WriteHeader(http.StatusBadRequest)
case errors.Contains(errorVal, io.ErrUnexpectedEOF):
w.WriteHeader(http.StatusBadRequest)
default:
w.WriteHeader(http.StatusInternalServerError)
}
if errorVal.Msg() != "" {
if err := json.NewEncoder(w).Encode(errorRes{Err: errorVal.Msg()}); err != nil {
w.WriteHeader(http.StatusInternalServerError)
}
}
default:
w.WriteHeader(http.StatusInternalServerError)
}
}
@@ -275,7 +291,7 @@ func parseUint(s string) (uint64, error) {
ret, err := strconv.ParseUint(s, 10, 64)
if err != nil {
return 0, errInvalidQueryParams
return 0, errors.ErrInvalidQueryParams
}
return ret, nil
@@ -285,13 +301,13 @@ func parsePagePrams(q url.Values) (uint64, uint64, error) {
offset, err := parseUint(q.Get("offset"))
q.Del("offset")
if err != nil {
return 0, 0, err
return 0, 0, errors.Wrap(errInvalidOffsetParam, err)
}
limit, err := parseUint(q.Get("limit"))
q.Del("limit")
if err != nil {
return 0, 0, err
return 0, 0, errors.Wrap(errInvalidLimitParam, err)
}
if limit > maxLimit {
+23 -27
View File
@@ -1,9 +1,5 @@
//
// Copyright (c) 2018
// Mainflux
//
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
//
package bootstrap
@@ -16,6 +12,9 @@ type Config struct {
MFThing string
Owner string
Name string
ClientCert string
ClientKey string
CACert string
MFKey string
MFChannels []Channel
ExternalID string
@@ -28,12 +27,11 @@ type Config struct {
type Channel struct {
ID string
Name string
Metadata interface{}
Metadata map[string]interface{}
}
// Filter is used for the search filters.
type Filter struct {
Unknown bool
FullMatch map[string]string
PartialMatch map[string]string
}
@@ -51,56 +49,54 @@ type ConfigsPage struct {
type ConfigRepository interface {
// Save persists the Config. Successful operation is indicated by non-nil
// error response.
Save(Config, []string) (string, error)
Save(cfg Config, chsConnIDs []string) (string, error)
// RetrieveByID retrieves the Config having the provided identifier, that is owned
// by the specified user.
RetrieveByID(string, string) (Config, error)
RetrieveByID(owner, id string) (Config, error)
// RetrieveAll retrieves a subset of Configs that are owned
// by the specific user, with given filter parameters.
RetrieveAll(string, Filter, uint64, uint64) ConfigsPage
RetrieveAll(owner string, filter Filter, offset, limit uint64) ConfigsPage
// RetrieveByExternalID returns Config for given external ID.
RetrieveByExternalID(string, string) (Config, error)
RetrieveByExternalID(externalID string) (Config, error)
// Update performs and update to an existing Config. A non-nil error is returned
// Update updates an existing Config. A non-nil error is returned
// to indicate operation failure.
Update(Config) error
Update(cfg Config) error
// UpdateCerts updates an existing Config certificate and owner.
// A non-nil error is returned to indicate operation failure.
UpdateCert(owner, thingID, clientCert, clientKey, caCert string) error
// UpdateConnections updates a list of Channels the Config is connected to
// adding new Channels if needed.
UpdateConnections(string, string, []Channel, []string) error
UpdateConnections(owner, id string, channels []Channel, connections []string) error
// Remove removes the Config having the provided identifier, that is owned
// by the specified user.
Remove(string, string) error
Remove(owner, id string) error
// ChangeState changes of the Config, that is owned by the specific user.
ChangeState(string, string, State) error
// SaveUnknown saves Thing which unsuccessfully bootstrapped.
SaveUnknown(string, string) error
// RetrieveUnknown returns a subset of unsuccessfully bootstrapped Things.
RetrieveUnknown(uint64, uint64) ConfigsPage
ChangeState(owner, id string, state State) error
// ListExisting retrieves those channels from the given list that exist in DB.
ListExisting(string, []string) ([]Channel, error)
ListExisting(owner string, ids []string) ([]Channel, error)
// Methods RemoveThing, UpdateChannel, and RemoveChannel are related to
// event sourcing. That's why these methods surpass ownership check.
// RemoveThing removes Config of the Thing with the given ID.
RemoveThing(string) error
RemoveThing(id string) error
// UpdateChannel updates channel with the given ID.
UpdateChannel(Channel) error
UpdateChannel(c Channel) error
// RemoveChannel removes channel with the given ID.
RemoveChannel(string) error
RemoveChannel(id string) error
// DisconnectHandler changes state of the Config when the corresponding Thing is
// disconnected from the Channel.
DisconnectThing(string, string) error
DisconnectThing(channelID, thingID string) error
}
+1 -5
View File
@@ -1,9 +1,5 @@
//
// Copyright (c) 2018
// Mainflux
//
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
//
// Package bootstrap contains the domain concept definitions needed to support
// Mainflux bootstrap service functionality.
+39 -77
View File
@@ -1,9 +1,5 @@
//
// Copyright (c) 2018
// Mainflux
//
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
//
package mocks
@@ -28,15 +24,13 @@ type configRepositoryMock struct {
counter uint64
configs map[string]bootstrap.Config
channels map[string]bootstrap.Channel
unknown map[string]string
}
// NewConfigsRepository creates in-memory thing repository.
func NewConfigsRepository(unknown map[string]string) bootstrap.ConfigRepository {
// NewConfigsRepository creates in-memory config repository.
func NewConfigsRepository() bootstrap.ConfigRepository {
return &configRepositoryMock{
configs: make(map[string]bootstrap.Config),
channels: make(map[string]bootstrap.Channel),
unknown: unknown,
}
}
@@ -65,12 +59,11 @@ func (crm *configRepositoryMock) Save(config bootstrap.Config, connections []str
}
crm.configs[config.MFThing] = config
delete(crm.unknown, config.ExternalID)
return config.MFThing, nil
}
func (crm *configRepositoryMock) RetrieveByID(key, id string) (bootstrap.Config, error) {
func (crm *configRepositoryMock) RetrieveByID(token, id string) (bootstrap.Config, error) {
crm.mu.Lock()
defer crm.mu.Unlock()
@@ -78,7 +71,7 @@ func (crm *configRepositoryMock) RetrieveByID(key, id string) (bootstrap.Config,
if !ok {
return bootstrap.Config{}, bootstrap.ErrNotFound
}
if c.Owner != key {
if c.Owner != token {
return bootstrap.Config{}, bootstrap.ErrUnauthorizedAccess
}
@@ -86,7 +79,7 @@ func (crm *configRepositoryMock) RetrieveByID(key, id string) (bootstrap.Config,
}
func (crm *configRepositoryMock) RetrieveAll(key string, filter bootstrap.Filter, offset, limit uint64) bootstrap.ConfigsPage {
func (crm *configRepositoryMock) RetrieveAll(token string, filter bootstrap.Filter, offset, limit uint64) bootstrap.ConfigsPage {
crm.mu.Lock()
defer crm.mu.Unlock()
@@ -114,7 +107,7 @@ func (crm *configRepositoryMock) RetrieveAll(key string, filter bootstrap.Filter
id, _ := strconv.ParseUint(v.MFThing, 10, 64)
if (state == emptyState || v.State == state) &&
(name == "" || strings.Index(strings.ToLower(v.Name), name) != notFoundIdx) &&
v.Owner == key {
v.Owner == token {
if id >= first && id < last {
configs = append(configs, v)
}
@@ -134,13 +127,13 @@ func (crm *configRepositoryMock) RetrieveAll(key string, filter bootstrap.Filter
}
}
func (crm *configRepositoryMock) RetrieveByExternalID(externalKey, externalID string) (bootstrap.Config, error) {
func (crm *configRepositoryMock) RetrieveByExternalID(externalID string) (bootstrap.Config, error) {
crm.mu.Lock()
defer crm.mu.Unlock()
for _, thing := range crm.configs {
if thing.ExternalID == externalID && thing.ExternalKey == externalKey {
return thing, nil
for _, cfg := range crm.configs {
if cfg.ExternalID == externalID {
return cfg, nil
}
}
@@ -163,7 +156,28 @@ func (crm *configRepositoryMock) Update(config bootstrap.Config) error {
return nil
}
func (crm *configRepositoryMock) UpdateConnections(key, id string, channels []bootstrap.Channel, connections []string) error {
func (crm *configRepositoryMock) UpdateCert(owner, thingID, clientCert, clientKey, caCert string) error {
crm.mu.Lock()
defer crm.mu.Unlock()
var forUpdate bootstrap.Config
for _, v := range crm.configs {
if v.MFThing == thingID && v.Owner == owner {
forUpdate = v
break
}
}
if _, ok := crm.configs[forUpdate.MFThing]; !ok {
return bootstrap.ErrNotFound
}
forUpdate.ClientCert = clientCert
forUpdate.ClientKey = clientKey
forUpdate.CACert = caCert
crm.configs[forUpdate.MFThing] = forUpdate
return nil
}
func (crm *configRepositoryMock) UpdateConnections(token, id string, channels []bootstrap.Channel, connections []string) error {
crm.mu.Lock()
defer crm.mu.Unlock()
@@ -171,6 +185,7 @@ func (crm *configRepositoryMock) UpdateConnections(key, id string, channels []bo
if !ok {
return bootstrap.ErrNotFound
}
for _, ch := range channels {
crm.channels[ch.ID] = ch
}
@@ -188,12 +203,12 @@ func (crm *configRepositoryMock) UpdateConnections(key, id string, channels []bo
return nil
}
func (crm *configRepositoryMock) Remove(key, id string) error {
func (crm *configRepositoryMock) Remove(token, id string) error {
crm.mu.Lock()
defer crm.mu.Unlock()
for k, v := range crm.configs {
if v.Owner == key && k == id {
if v.Owner == token && k == id {
delete(crm.configs, k)
break
}
@@ -202,7 +217,7 @@ func (crm *configRepositoryMock) Remove(key, id string) error {
return nil
}
func (crm *configRepositoryMock) ChangeState(key, id string, state bootstrap.State) error {
func (crm *configRepositoryMock) ChangeState(token, id string, state bootstrap.State) error {
crm.mu.Lock()
defer crm.mu.Unlock()
@@ -210,7 +225,7 @@ func (crm *configRepositoryMock) ChangeState(key, id string, state bootstrap.Sta
if !ok {
return bootstrap.ErrNotFound
}
if config.Owner != key {
if config.Owner != token {
return bootstrap.ErrUnauthorizedAccess
}
@@ -219,60 +234,7 @@ func (crm *configRepositoryMock) ChangeState(key, id string, state bootstrap.Sta
return nil
}
func (crm *configRepositoryMock) RetrieveUnknown(offset, limit uint64) bootstrap.ConfigsPage {
crm.mu.Lock()
defer crm.mu.Unlock()
configs := []bootstrap.Config{}
i := uint64(0)
l := int(limit)
var keys []string
for k := range crm.unknown {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
if i >= offset && len(configs) < l {
configs = append(configs, bootstrap.Config{
ExternalID: k,
ExternalKey: crm.unknown[k],
})
}
i++
}
return bootstrap.ConfigsPage{
Total: uint64(len(crm.unknown)),
Offset: offset,
Limit: limit,
Configs: configs,
}
}
func (crm *configRepositoryMock) RemoveUnknown(key, id string) error {
crm.mu.Lock()
defer crm.mu.Unlock()
for k, v := range crm.unknown {
if k == id && v == key {
delete(crm.unknown, k)
return nil
}
}
return nil
}
func (crm *configRepositoryMock) SaveUnknown(key, id string) error {
crm.mu.Lock()
defer crm.mu.Unlock()
crm.unknown[id] = key
return nil
}
func (crm *configRepositoryMock) ListExisting(key string, connections []string) ([]bootstrap.Channel, error) {
func (crm *configRepositoryMock) ListExisting(token string, connections []string) ([]bootstrap.Channel, error) {
crm.mu.Lock()
defer crm.mu.Unlock()
+79 -48
View File
@@ -1,9 +1,5 @@
//
// Copyright (c) 2018
// Mainflux
//
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
//
package mocks
@@ -23,48 +19,50 @@ type mainfluxThings struct {
counter uint64
things map[string]things.Thing
channels map[string]things.Channel
users mainflux.UsersServiceClient
auth mainflux.AuthServiceClient
connections map[string][]string
}
// NewThingsService returns Mainflux Things service mock.
// Only methods used by SDK are mocked.
func NewThingsService(things map[string]things.Thing, channels map[string]things.Channel, users mainflux.UsersServiceClient) things.Service {
func NewThingsService(things map[string]things.Thing, channels map[string]things.Channel, auth mainflux.AuthServiceClient) things.Service {
return &mainfluxThings{
things: things,
channels: channels,
users: users,
auth: auth,
connections: make(map[string][]string),
}
}
func (svc *mainfluxThings) AddThing(owner string, thing things.Thing) (things.Thing, error) {
func (svc *mainfluxThings) CreateThings(_ context.Context, owner string, ths ...things.Thing) ([]things.Thing, error) {
svc.mu.Lock()
defer svc.mu.Unlock()
userID, err := svc.users.Identify(context.Background(), &mainflux.Token{Value: owner})
userID, err := svc.auth.Identify(context.Background(), &mainflux.Token{Value: owner})
if err != nil {
return things.Thing{}, things.ErrUnauthorizedAccess
return []things.Thing{}, things.ErrUnauthorizedAccess
}
for i := range ths {
svc.counter++
ths[i].Owner = userID.Email
ths[i].ID = strconv.FormatUint(svc.counter, 10)
ths[i].Key = ths[i].ID
svc.things[ths[i].ID] = ths[i]
}
svc.counter++
thing.Owner = userID.Value
thing.ID = strconv.FormatUint(svc.counter, 10)
thing.Key = thing.ID
svc.things[thing.ID] = thing
return thing, nil
return ths, nil
}
func (svc *mainfluxThings) ViewThing(owner, id string) (things.Thing, error) {
func (svc *mainfluxThings) ViewThing(_ context.Context, owner, id string) (things.Thing, error) {
svc.mu.Lock()
defer svc.mu.Unlock()
userID, err := svc.users.Identify(context.Background(), &mainflux.Token{Value: owner})
userID, err := svc.auth.Identify(context.Background(), &mainflux.Token{Value: owner})
if err != nil {
return things.Thing{}, things.ErrUnauthorizedAccess
}
if t, ok := svc.things[id]; ok && t.Owner == userID.Value {
if t, ok := svc.things[id]; ok && t.Owner == userID.Email {
return t, nil
}
@@ -72,29 +70,32 @@ func (svc *mainfluxThings) ViewThing(owner, id string) (things.Thing, error) {
return things.Thing{}, things.ErrNotFound
}
func (svc *mainfluxThings) Connect(owner, chanID, thingID string) error {
func (svc *mainfluxThings) Connect(_ context.Context, owner string, chIDs, thIDs []string) error {
svc.mu.Lock()
defer svc.mu.Unlock()
userID, err := svc.users.Identify(context.Background(), &mainflux.Token{Value: owner})
userID, err := svc.auth.Identify(context.Background(), &mainflux.Token{Value: owner})
if err != nil {
return things.ErrUnauthorizedAccess
}
if svc.channels[chanID].Owner != userID.Value {
return things.ErrNotFound
for _, chID := range chIDs {
if svc.channels[chID].Owner != userID.Email {
return things.ErrUnauthorizedAccess
}
for _, thID := range thIDs {
svc.connections[chID] = append(svc.connections[chID], thID)
}
}
svc.connections[chanID] = append(svc.connections[chanID], thingID)
return nil
}
func (svc *mainfluxThings) Disconnect(owner, chanID, thingID string) error {
func (svc *mainfluxThings) Disconnect(_ context.Context, owner, chanID, thingID string) error {
svc.mu.Lock()
defer svc.mu.Unlock()
userID, err := svc.users.Identify(context.Background(), &mainflux.Token{Value: owner})
if err != nil || svc.channels[chanID].Owner != userID.Value {
userID, err := svc.auth.Identify(context.Background(), &mainflux.Token{Value: owner})
if err != nil || svc.channels[chanID].Owner != userID.Email {
return things.ErrUnauthorizedAccess
}
@@ -121,29 +122,29 @@ func (svc *mainfluxThings) Disconnect(owner, chanID, thingID string) error {
return nil
}
func (svc *mainfluxThings) RemoveThing(owner, id string) error {
func (svc *mainfluxThings) RemoveThing(_ context.Context, owner, id string) error {
svc.mu.Lock()
defer svc.mu.Unlock()
userID, err := svc.users.Identify(context.Background(), &mainflux.Token{Value: owner})
userID, err := svc.auth.Identify(context.Background(), &mainflux.Token{Value: owner})
if err != nil {
return things.ErrUnauthorizedAccess
}
if t, ok := svc.things[id]; !ok || t.Owner != userID.Value {
if t, ok := svc.things[id]; !ok || t.Owner != userID.Email {
return things.ErrNotFound
}
delete(svc.things, id)
conns := make(map[string][]string)
for k, v := range svc.connections {
idx := findIndex(v, id)
if idx != -1 {
i := findIndex(v, id)
if i != -1 {
var tmp []string
if idx != len(v)-2 {
tmp = v[idx+1:]
if i != len(v)-2 {
tmp = v[i+1:]
}
conns[k] = append(v[:idx], tmp...)
conns[k] = append(v[:i], tmp...)
}
}
@@ -151,50 +152,76 @@ func (svc *mainfluxThings) RemoveThing(owner, id string) error {
return nil
}
func (svc *mainfluxThings) ViewChannel(owner, id string) (things.Channel, error) {
func (svc *mainfluxThings) ViewChannel(_ context.Context, owner, id string) (things.Channel, error) {
if c, ok := svc.channels[id]; ok {
return c, nil
}
return things.Channel{}, things.ErrNotFound
}
func (svc *mainfluxThings) UpdateThing(string, things.Thing) error {
func (svc *mainfluxThings) UpdateThing(context.Context, string, things.Thing) error {
panic("not implemented")
}
func (svc *mainfluxThings) ListThings(string, uint64, uint64) (things.ThingsPage, error) {
func (svc *mainfluxThings) UpdateKey(context.Context, string, string, string) error {
panic("not implemented")
}
func (svc *mainfluxThings) ListChannelsByThing(string, string, uint64, uint64) (things.ChannelsPage, error) {
func (svc *mainfluxThings) ListThings(context.Context, string, things.PageMetadata) (things.Page, error) {
panic("not implemented")
}
func (svc *mainfluxThings) ListThingsByChannel(string, string, uint64, uint64) (things.ThingsPage, error) {
func (svc *mainfluxThings) ListChannelsByThing(context.Context, string, string, things.PageMetadata) (things.ChannelsPage, error) {
panic("not implemented")
}
func (svc *mainfluxThings) CreateChannel(string, things.Channel) (things.Channel, error) {
func (svc *mainfluxThings) ListThingsByChannel(context.Context, string, string, things.PageMetadata) (things.Page, error) {
panic("not implemented")
}
func (svc *mainfluxThings) UpdateChannel(string, things.Channel) error {
func (svc *mainfluxThings) CreateChannels(_ context.Context, owner string, chs ...things.Channel) ([]things.Channel, error) {
svc.mu.Lock()
defer svc.mu.Unlock()
userID, err := svc.auth.Identify(context.Background(), &mainflux.Token{Value: owner})
if err != nil {
return []things.Channel{}, things.ErrUnauthorizedAccess
}
for i := range chs {
svc.counter++
chs[i].Owner = userID.Email
chs[i].ID = strconv.FormatUint(svc.counter, 10)
svc.channels[chs[i].ID] = chs[i]
}
return chs, nil
}
func (svc *mainfluxThings) UpdateChannel(context.Context, string, things.Channel) error {
panic("not implemented")
}
func (svc *mainfluxThings) ListChannels(string, uint64, uint64) (things.ChannelsPage, error) {
func (svc *mainfluxThings) ListChannels(context.Context, string, things.PageMetadata) (things.ChannelsPage, error) {
panic("not implemented")
}
func (svc *mainfluxThings) RemoveChannel(string, string) error {
func (svc *mainfluxThings) RemoveChannel(context.Context, string, string) error {
panic("not implemented")
}
func (svc *mainfluxThings) CanAccess(string, string) (string, error) {
func (svc *mainfluxThings) CanAccessByKey(context.Context, string, string) (string, error) {
panic("not implemented")
}
func (svc *mainfluxThings) Identify(string) (string, error) {
func (svc *mainfluxThings) CanAccessByID(context.Context, string, string) error {
panic("not implemented")
}
func (svc *mainfluxThings) IsChannelOwner(context.Context, string, string) error {
panic("not implemented")
}
func (svc *mainfluxThings) Identify(context.Context, string) (string, error) {
panic("not implemented")
}
@@ -207,3 +234,7 @@ func findIndex(list []string, val string) int {
return -1
}
func (svc *mainfluxThings) ListMembers(ctx context.Context, token, groupID string, pm things.PageMetadata) (things.Page, error) {
panic("not implemented")
}
+30 -11
View File
@@ -1,34 +1,53 @@
//
// Copyright (c) 2018
// Mainflux
//
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
//
package mocks
import (
"context"
"github.com/golang/protobuf/ptypes/empty"
"github.com/mainflux/mainflux"
"github.com/mainflux/mainflux/users"
"google.golang.org/grpc"
)
var _ mainflux.UsersServiceClient = (*usersServiceMock)(nil)
var _ mainflux.AuthServiceClient = (*serviceMock)(nil)
type usersServiceMock struct {
type serviceMock struct {
users map[string]string
}
// NewUsersService creates mock of users service.
func NewUsersService(users map[string]string) mainflux.UsersServiceClient {
return &usersServiceMock{users}
func NewUsersService(users map[string]string) mainflux.AuthServiceClient {
return &serviceMock{users}
}
func (svc usersServiceMock) Identify(ctx context.Context, in *mainflux.Token, opts ...grpc.CallOption) (*mainflux.UserID, error) {
func (svc serviceMock) Identify(ctx context.Context, in *mainflux.Token, opts ...grpc.CallOption) (*mainflux.UserIdentity, error) {
if id, ok := svc.users[in.Value]; ok {
return &mainflux.UserID{Value: id}, nil
return &mainflux.UserIdentity{Email: id, Id: id}, nil
}
return nil, users.ErrUnauthorizedAccess
}
func (svc serviceMock) Issue(ctx context.Context, in *mainflux.IssueReq, opts ...grpc.CallOption) (*mainflux.Token, error) {
if id, ok := svc.users[in.GetEmail()]; ok {
switch in.Type {
default:
return &mainflux.Token{Value: id}, nil
}
}
return nil, users.ErrUnauthorizedAccess
}
func (svc serviceMock) Authorize(ctx context.Context, req *mainflux.AuthorizeReq, _ ...grpc.CallOption) (r *mainflux.AuthorizeRes, err error) {
panic("not implemented")
}
func (svc serviceMock) Members(ctx context.Context, req *mainflux.MembersReq, _ ...grpc.CallOption) (r *mainflux.MembersRes, err error) {
panic("not implemented")
}
func (svc serviceMock) Assign(ctx context.Context, req *mainflux.Assignment, _ ...grpc.CallOption) (r *empty.Empty, err error) {
panic("not implemented")
}
+516
View File
@@ -0,0 +1,516 @@
openapi: 3.0.1
info:
title: Mainflux Bootstrap service
description: HTTP API for managing platform things configuration.
version: "1.0.0"
paths:
/things/configs:
post:
summary: Adds new config
description: |
Adds new config to the list of config owned by user identified using
the provided access token.
tags:
- configs
parameters:
- $ref: "#/components/parameters/Authorization"
requestBody:
$ref: "#/components/requestBodies/ConfigCreateReq"
responses:
'201':
$ref: "#/components/responses/ConfigCreateRes"
'400':
description: Failed due to malformed JSON.
'403':
description: Missing or invalid access token provided.
'415':
description: Missing or invalid content type.
'500':
$ref: "#/components/responses/ServiceError"
get:
summary: Retrieves managed configs
description: |
Retrieves a list of managed configs. Due to performance concerns, data
is retrieved in subsets. The API configs must ensure that the entire
dataset is consumed either by making subsequent requests, or by
increasing the subset size of the initial request.
tags:
- configs
parameters:
- $ref: "#/components/parameters/Authorization"
- $ref: "#/components/parameters/Limit"
- $ref: "#/components/parameters/Offset"
- $ref: "#/components/parameters/State"
- $ref: "#/components/parameters/Name"
responses:
'200':
$ref: "#/components/responses/ConfigListRes"
'400':
description: Failed due to malformed query parameters.
'403':
description: Missing or invalid access token provided.
'500':
$ref: "#/components/responses/ServiceError"
/things/configs/{configId}:
get:
summary: Retrieves config info (with channels).
tags:
- configs
parameters:
- $ref: "#/components/parameters/Authorization"
- $ref: "#/components/parameters/ConfigId"
responses:
'200':
$ref: "#/components/responses/ConfigRes"
'403':
description: Missing or invalid access token provided.
'404':
description: Config does not exist.
'500':
$ref: "#/components/responses/ServiceError"
put:
summary: Updates config info
description: |
Update is performed by replacing the current resource data with values
provided in a request payload. Note that the owner, ID, external ID,
external key, Mainflux Thing ID and key cannot be changed.
tags:
- configs
parameters:
- $ref: "#/components/parameters/Authorization"
- $ref: "#/components/parameters/ConfigId"
requestBody:
$ref: "#/components/requestBodies/ConfigUpdateReq"
responses:
'200':
description: Config updated.
'400':
description: Failed due to malformed JSON.
'403':
description: Missing or invalid access token provided.
'404':
description: Config does not exist.
'415':
description: Missing or invalid content type.
'500':
$ref: "#/components/responses/ServiceError"
delete:
summary: Removes a Config
description: |
Removes a Config. In case of successful removal the service will ensure
that the removed config is disconnected from all of the Mainflux channels.
tags:
- configs
parameters:
- $ref: "#/components/parameters/Authorization"
- $ref: "#/components/parameters/ConfigId"
responses:
'204':
description: Config removed.
'400':
description: Failed due to malformed config ID.
'403':
description: Missing or invalid access token provided.
'500':
$ref: "#/components/responses/ServiceError"
/things/configs/certs/{configId}:
patch:
summary: Updates certs
description: |
Update is performed by replacing the current certificate data with values
provided in a request payload.
tags:
- configs
parameters:
- $ref: "#/components/parameters/Authorization"
- $ref: "#/components/parameters/ConfigId"
requestBody:
$ref: "#/components/requestBodies/ConfigCertUpdateReq"
responses:
'200':
description: Config updated.
'400':
description: Failed due to malformed JSON.
'403':
description: Missing or invalid access token provided.
'404':
description: Config does not exist.
'415':
description: Missing or invalid content type.
'500':
$ref: "#/components/responses/ServiceError"
/things/configs/connections/{configId}:
put:
summary: Updates channels the thing is connected to
description: |
Update connections performs update of the channel list corresponding
Thing is connected to.
tags:
- configs
parameters:
- $ref: "#/components/parameters/Authorization"
- $ref: "#/components/parameters/ConfigId"
requestBody:
$ref: "#/components/requestBodies/ConfigConnUpdateReq"
responses:
'200':
description: Config updated.
'400':
description: Failed due to malformed JSON.
'403':
description: Missing or invalid access token provided.
'404':
description: Config does not exist.
'415':
description: Missing or invalid content type.
'500':
$ref: "#/components/responses/ServiceError"
/things/bootstrap/{externalId}:
get:
summary: Retrieves configuration.
description: |
Retrieves a configuration with given external ID and external key.
tags:
- configs
parameters:
- $ref: "#/components/parameters/ConfigAuth"
- $ref: "#/components/parameters/ExternalId"
responses:
'200':
$ref: "#/components/responses/BootstrapConfigRes"
'404':
description: |
Failed to retrieve corresponding config.
'500':
$ref: "#/components/responses/ServiceError"
/things/bootstrap/secure/{externalId}:
get:
summary: Retrieves configuration.
description: |
Retrieves a configuration with given external ID and encrypted external key.
tags:
- configs
parameters:
- $ref: "#/components/parameters/EncConfigAuth"
- $ref: "#/components/parameters/ExternalId"
responses:
'200':
$ref: "#/components/responses/BootstrapConfigRes"
'404':
description: |
Failed to retrieve corresponding config.
'500':
$ref: "#/components/responses/ServiceError"
/things/state/{configId}:
put:
summary: Updates Config state.
description: |
Updating state represents enabling/disabling Config, i.e. connecting
and disconnecting corresponding Mainflux Thing to the list of Channels.
tags:
- configs
parameters:
- $ref: "#/components/parameters/Authorization"
- $ref: "#/components/parameters/ConfigId"
requestBody:
$ref: '#/components/requestBodies/ConfigStateUpdateReq'
responses:
'204':
description: Config removed.
'400':
description: Failed due to malformed config's ID.
'403':
description: Missing or invalid access token provided.
'500':
$ref: "#/components/responses/ServiceError"
components:
schemas:
State:
type: integer
enum: [0, 1]
Config:
type: object
properties:
mainflux_id:
type: string
format: uuid
description: Corresponding Mainflux Thing ID.
mainflux_key:
type: string
format: uuid
description: Corresponding Mainflux Thing key.
mainflux_channels:
type: array
minItems: 0
items:
type: object
properties:
id:
type: string
format: uuid
description: Channel unique identifier.
name:
type: string
description: Name of the Channel.
metadata:
type: object
description: Custom metadata related to the Channel.
external_id:
type: string
description: External ID (MAC address or some unique identifier).
external_key:
type: string
description: External key.
content:
type: string
description: Free-form custom configuration.
state:
$ref: "#/components/schemas/State"
required:
- external_id
- external_key
ConfigList:
type: object
properties:
total:
type: integer
description: Total number of results.
minimum: 0
offset:
type: integer
description: Number of items to skip during retrieval.
minimum: 0
default: 0
limit:
type: integer
description: Size of the subset to retrieve.
maximum: 100
default: 10
configs:
type: array
minItems: 0
uniqueItems: true
items:
$ref: "#/components/schemas/Config"
required:
- configs
BootstrapConfig:
type: object
properties:
mainflux_id:
type: string
format: uuid
description: Corresponding Mainflux Thing ID.
mainflux_key:
type: string
format: uuid
description: Corresponding Mainflux Thing key.
mainflux_channels:
type: array
minItems: 0
items:
type: string
content:
type: string
description: Free-form custom configuration.
client_cert:
type: string
description: Client certificate.
client_key:
type: string
description: Key for the client_cert.
ca_cert:
type: string
description: Issuing CA certificate.
required:
- mainflux_id
- mainflux_key
- mainflux_channels
- content
parameters:
Authorization:
name: Authorization
description: User's access token.
in: header
schema:
type: string
format: jwt
required: true
ConfigAuth:
name: configAuthorization
description: Configuration external key.
in: header
schema:
type: string
required: true
EncConfigAuth:
name: configAuthorization
description: |
Hex-encoded configuration external key encrypted using
the AES algorithm and SHA256 sum of the external key
itself as an encryption key.
in: header
schema:
type: string
required: true
ConfigId:
name: configId
description: Unique Config identifier. It's the ID of the corresponding Thing.
in: path
schema:
type: string
format: uuid
required: true
ExternalId:
name: externalId
description: Unique Config identifier provided by external entity.
in: path
schema:
type: string
required: true
Limit:
name: limit
description: Size of the subset to retrieve.
in: query
schema:
type: integer
default: 10
maximum: 100
minimum: 1
required: false
Offset:
name: offset
description: Number of items to skip during retrieval.
in: query
schema:
type: integer
default: 0
minimum: 0
required: false
State:
name: state
description: A state of items
in: query
schema:
$ref: "#/components/schemas/State"
required: false
Name:
name: name
description: Name of the config. Search by name is partial-match and case-insensitive.
in: query
schema:
type: string
required: false
requestBodies:
ConfigCreateReq:
description: JSON-formatted document describing the new config.
required: true
content:
application/json:
schema:
type: object
properties:
external_id:
type: string
description: External ID (MAC address or some unique identifier).
external_key:
type: string
description: External key.
thing_id:
type: string
description: ID of the corresponding Mainflux Thing.
channels:
type: array
minItems: 0
items:
type: string
content:
type: string
required:
- external_id
- external_key
ConfigUpdateReq:
description: JSON-formatted document describing the updated thing.
content:
application/json:
schema:
type: object
properties:
content:
type: string
name:
type: string
required:
- content
- name
ConfigCertUpdateReq:
description: JSON-formatted document describing the updated thing.
content:
application/json:
schema:
type: object
properties:
client_cert:
type: string
client_key:
type: string
ca_cert:
type: string
ConfigConnUpdateReq:
description: Array if IDs the thing is be connected to.
content:
application/json:
schema:
type: object
properties:
channels:
type: array
minItems: 0
items:
type: string
ConfigStateUpdateReq:
description: Update the state of the Config.
content:
application/json:
schema:
type: object
properties:
state:
$ref: "#/components/schemas/State"
responses:
ConfigCreateRes:
description: Config registered.
headers:
Location:
content:
text/plain:
schema:
type: string
description: Created configuration's relative URL (i.e. /things/configs/{configId}).
ConfigListRes:
description: Data retrieved. Configs from this list don't contain channels.
content:
application/json:
schema:
$ref: "#/components/schemas/ConfigList"
ConfigRes:
description: Data retrieved.
content:
application/json:
schema:
$ref: "#/components/schemas/Config"
BootstrapConfigRes:
description: |
Data retrieved. If secure, a response is encrypted using
the secret key, so the response is in the binary form.
content:
application/json:
schema:
$ref: "#/components/schemas/BootstrapConfig"
ServiceError:
description: Unexpected server-side error occurred.
+313 -199
View File
@@ -1,20 +1,19 @@
//
// Copyright (c) 2018
// Mainflux
//
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
//
package postgres
import (
"database/sql"
"encoding/json"
"fmt"
"strings"
"github.com/jmoiron/sqlx"
"github.com/lib/pq"
"github.com/mainflux/mainflux/bootstrap"
"github.com/mainflux/mainflux/logger"
"github.com/mainflux/mainflux/pkg/errors"
)
const (
@@ -29,32 +28,45 @@ const (
SELECT channel_id FROM connections c WHERE ch.mainflux_channel = c.channel_id);`
)
var (
errSaveDB = errors.New("failed to save bootstrap configuration to database")
errMarshalChannel = errors.New("failed to marshal channel into json")
errUnmarshalChannel = errors.New("failed to unmarshal json to channel")
errSaveChannels = errors.New("failed to insert channels to database")
errSaveConnections = errors.New("failed to insert connections to database")
errRetrieve = errors.New("failed to retreive bootstrap configuration from database")
errUpdate = errors.New("failed to update bootstrap configuration in database")
errRemove = errors.New("failed to remove bootstrap configuration from database")
errUpdateChannels = errors.New("failed to update channels in bootstrap configuration database")
errRemoveChannels = errors.New("failed to remove channels from bootstrap configuration in database")
errDisconnectThing = errors.New("failed to disconnect thing in bootstrap configuration in database")
)
var _ bootstrap.ConfigRepository = (*configRepository)(nil)
type configRepository struct {
db *sql.DB
db *sqlx.DB
log logger.Logger
}
// NewConfigRepository instantiates a PostgreSQL implementation of thing
// NewConfigRepository instantiates a PostgreSQL implementation of config
// repository.
func NewConfigRepository(db *sql.DB, log logger.Logger) bootstrap.ConfigRepository {
func NewConfigRepository(db *sqlx.DB, log logger.Logger) bootstrap.ConfigRepository {
return &configRepository{db: db, log: log}
}
func (cr configRepository) Save(cfg bootstrap.Config, connections []string) (string, error) {
q := `INSERT INTO configs (mainflux_thing, owner, name, mainflux_key, external_id, external_key, content, state)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)`
content := nullString(cfg.Content)
name := nullString(cfg.Name)
tx, err := cr.db.Begin()
func (cr configRepository) Save(cfg bootstrap.Config, chsConnIDs []string) (string, error) {
q := `INSERT INTO configs (mainflux_thing, owner, name, client_cert, client_key, ca_cert, mainflux_key, external_id, external_key, content, state)
VALUES (:mainflux_thing, :owner, :name, :client_cert, :client_key, :ca_cert, :mainflux_key, :external_id, :external_key, :content, :state)`
tx, err := cr.db.Beginx()
if err != nil {
return "", err
return "", errors.Wrap(errSaveDB, err)
}
if _, err := tx.Exec(q, cfg.MFThing, cfg.Owner, name, cfg.MFKey, cfg.ExternalID, cfg.ExternalKey, content, cfg.State); err != nil {
dbcfg := toDBConfig(cfg)
if _, err := tx.NamedExec(q, dbcfg); err != nil {
e := err
if pqErr, ok := err.(*pq.Error); ok && pqErr.Code.Name() == duplicateErr {
e = bootstrap.ErrConflict
@@ -62,27 +74,19 @@ func (cr configRepository) Save(cfg bootstrap.Config, connections []string) (str
cr.rollback("Failed to insert a Config", tx, err)
return "", e
return "", errors.Wrap(errSaveDB, e)
}
if err := insertChannels(cfg.Owner, cfg.MFChannels, tx); err != nil {
cr.rollback("Failed to insert Channels", tx, err)
return "", err
return "", errors.Wrap(errSaveChannels, err)
}
if err := insertConnections(cfg, connections, tx); err != nil {
if err := insertConnections(cfg, chsConnIDs, tx); err != nil {
cr.rollback("Failed to insert connections", tx, err)
return "", err
}
q = "DELETE FROM unknown_configs WHERE external_id = $1 AND external_key = $2"
if _, err := tx.Exec(q, cfg.ExternalID, cfg.ExternalKey); err != nil {
cr.rollback("Failed to remove from unknown", tx, err)
return "", err
return "", errors.Wrap(errSaveConnections, err)
}
if err := tx.Commit(); err != nil {
@@ -92,54 +96,65 @@ func (cr configRepository) Save(cfg bootstrap.Config, connections []string) (str
return cfg.MFThing, nil
}
func (cr configRepository) RetrieveByID(key, id string) (bootstrap.Config, error) {
q := `SELECT mainflux_thing, mainflux_key, external_id, external_key, name, content, state FROM configs WHERE mainflux_thing = $1 AND owner = $2`
cfg := bootstrap.Config{MFThing: id, Owner: key, MFChannels: []bootstrap.Channel{}}
var name, content sql.NullString
if err := cr.db.QueryRow(q, id, key).
Scan(&cfg.MFThing, &cfg.MFKey, &cfg.ExternalID, &cfg.ExternalKey, &name, &content, &cfg.State); err != nil {
func (cr configRepository) RetrieveByID(owner, id string) (bootstrap.Config, error) {
q := `SELECT mainflux_thing, mainflux_key, external_id, external_key, name, content, state
FROM configs
WHERE mainflux_thing = $1 AND owner = $2`
dbcfg := dbConfig{
MFThing: id,
Owner: owner,
}
if err := cr.db.QueryRowx(q, id, owner).StructScan(&dbcfg); err != nil {
empty := bootstrap.Config{}
if err == sql.ErrNoRows {
return empty, bootstrap.ErrNotFound
return empty, errors.Wrap(bootstrap.ErrNotFound, err)
}
return empty, err
return empty, errors.Wrap(errRetrieve, err)
}
q = `SELECT mainflux_channel, name, metadata FROM channels ch
INNER JOIN connections conn
ON ch.mainflux_channel = conn.channel_id AND ch.owner = conn.config_owner
WHERE conn.config_id = $1 AND conn.config_owner = $2`
INNER JOIN connections conn
ON ch.mainflux_channel = conn.channel_id AND ch.owner = conn.config_owner
WHERE conn.config_id = :mainflux_thing AND conn.config_owner = :owner`
rows, err := cr.db.Query(q, cfg.MFThing, cfg.Owner)
rows, err := cr.db.NamedQuery(q, dbcfg)
if err != nil {
cr.log.Error(fmt.Sprintf("Failed to retrieve connected due to %s", err))
return bootstrap.Config{}, err
return bootstrap.Config{}, errors.Wrap(errRetrieve, err)
}
defer rows.Close()
chans := []bootstrap.Channel{}
for rows.Next() {
c := bootstrap.Channel{}
if err := rows.Scan(&c.ID, &c.Name, &c.Metadata); err != nil {
dbch := dbChannel{}
if err := rows.StructScan(&dbch); err != nil {
cr.log.Error(fmt.Sprintf("Failed to read connected thing due to %s", err))
return bootstrap.Config{}, err
return bootstrap.Config{}, errors.Wrap(errRetrieve, err)
}
dbch.Owner = nullString(dbcfg.Owner)
cfg.MFChannels = append(cfg.MFChannels, c)
ch, err := toChannel(dbch)
if err != nil {
return bootstrap.Config{}, errors.Wrap(errRetrieve, err)
}
chans = append(chans, ch)
}
cfg.Content = content.String
cfg.Name = name.String
cfg := toConfig(dbcfg)
cfg.MFChannels = chans
return cfg, nil
}
func (cr configRepository) RetrieveAll(key string, filter bootstrap.Filter, offset, limit uint64) bootstrap.ConfigsPage {
search, params := cr.retrieveAll(key, filter)
func (cr configRepository) RetrieveAll(owner string, filter bootstrap.Filter, offset, limit uint64) bootstrap.ConfigsPage {
search, params := cr.retrieveAll(owner, filter)
n := len(params)
q := `SELECT mainflux_thing, mainflux_key, external_id, external_key, name, content, state
FROM configs %s ORDER BY mainflux_thing LIMIT $%d OFFSET $%d`
FROM configs %s ORDER BY mainflux_thing LIMIT $%d OFFSET $%d`
q = fmt.Sprintf(q, search, n+1, n+2)
rows, err := cr.db.Query(q, append(params, limit, offset)...)
@@ -153,7 +168,7 @@ func (cr configRepository) RetrieveAll(key string, filter bootstrap.Filter, offs
configs := []bootstrap.Config{}
for rows.Next() {
c := bootstrap.Config{Owner: key}
c := bootstrap.Config{Owner: owner}
if err := rows.Scan(&c.MFThing, &c.MFKey, &c.ExternalID, &c.ExternalKey, &name, &content, &c.State); err != nil {
cr.log.Error(fmt.Sprintf("Failed to read retrieved config due to %s", err))
return bootstrap.ConfigsPage{}
@@ -180,44 +195,53 @@ func (cr configRepository) RetrieveAll(key string, filter bootstrap.Filter, offs
}
}
func (cr configRepository) RetrieveByExternalID(externalKey, externalID string) (bootstrap.Config, error) {
q := `SELECT mainflux_thing, mainflux_key, owner, name, content, state FROM configs WHERE external_key = $1 AND external_id = $2`
cfg := bootstrap.Config{ExternalID: externalID, ExternalKey: externalKey, MFChannels: []bootstrap.Channel{}}
var name, content sql.NullString
func (cr configRepository) RetrieveByExternalID(externalID string) (bootstrap.Config, error) {
q := `SELECT mainflux_thing, mainflux_key, external_key, owner, name, client_cert, client_key, ca_cert, content, state
FROM configs
WHERE external_id = $1`
dbcfg := dbConfig{
ExternalID: externalID,
}
if err := cr.db.QueryRow(q, externalKey, externalID).
Scan(&cfg.MFThing, &cfg.MFKey, &cfg.Owner, &name, &content, &cfg.State); err != nil {
if err := cr.db.QueryRowx(q, externalID).StructScan(&dbcfg); err != nil {
empty := bootstrap.Config{}
if err == sql.ErrNoRows {
return empty, bootstrap.ErrNotFound
return empty, errors.Wrap(bootstrap.ErrNotFound, err)
}
return empty, err
return empty, errors.Wrap(errRetrieve, err)
}
q = `SELECT mainflux_channel, name, metadata FROM channels ch
INNER JOIN connections conn
ON ch.mainflux_channel = conn.channel_id AND ch.owner = conn.config_owner
WHERE conn.config_id = $1 AND conn.config_owner = $2`
INNER JOIN connections conn
ON ch.mainflux_channel = conn.channel_id AND ch.owner = conn.config_owner
WHERE conn.config_id = :mainflux_thing AND conn.config_owner = :owner`
rows, err := cr.db.Query(q, cfg.MFThing, cfg.Owner)
rows, err := cr.db.NamedQuery(q, dbcfg)
if err != nil {
cr.log.Error(fmt.Sprintf("Failed to retrieve connected due to %s", err))
return bootstrap.Config{}, err
return bootstrap.Config{}, errors.Wrap(errRetrieve, err)
}
defer rows.Close()
channels := []bootstrap.Channel{}
for rows.Next() {
c := bootstrap.Channel{}
if err := rows.Scan(&c.ID, &c.Name, &c.Metadata); err != nil {
dbch := dbChannel{}
if err := rows.StructScan(&dbch); err != nil {
cr.log.Error(fmt.Sprintf("Failed to read connected thing due to %s", err))
return bootstrap.Config{}, err
return bootstrap.Config{}, errors.Wrap(errRetrieve, err)
}
cfg.MFChannels = append(cfg.MFChannels, c)
ch, err := toChannel(dbch)
if err != nil {
cr.log.Error(fmt.Sprintf("Failed to deserialize channel due to %s", err))
return bootstrap.Config{}, errors.Wrap(errRetrieve, err)
}
channels = append(channels, ch)
}
cfg.Content = content.String
cfg.Name = name.String
cfg := toConfig(dbcfg)
cfg.MFChannels = channels
return cfg, nil
}
@@ -229,6 +253,26 @@ func (cr configRepository) Update(cfg bootstrap.Config) error {
name := nullString(cfg.Name)
res, err := cr.db.Exec(q, name, content, cfg.MFThing, cfg.Owner)
if err != nil {
return errors.Wrap(errUpdate, err)
}
cnt, err := res.RowsAffected()
if err != nil {
return errors.Wrap(errUpdate, err)
}
if cnt == 0 {
return bootstrap.ErrNotFound
}
return nil
}
func (cr configRepository) UpdateCert(owner, thingID, clientCert, clientKey, caCert string) error {
q := `UPDATE configs SET client_cert = $1, client_key = $2, ca_cert = $3 WHERE mainflux_thing = $4 AND owner = $5`
res, err := cr.db.Exec(q, clientCert, clientKey, caCert, thingID, owner)
if err != nil {
return err
}
@@ -245,19 +289,19 @@ func (cr configRepository) Update(cfg bootstrap.Config) error {
return nil
}
func (cr configRepository) UpdateConnections(key, id string, channels []bootstrap.Channel, connections []string) error {
tx, err := cr.db.Begin()
func (cr configRepository) UpdateConnections(owner, id string, channels []bootstrap.Channel, connections []string) error {
tx, err := cr.db.Beginx()
if err != nil {
return err
}
if err := insertChannels(key, channels, tx); err != nil {
if err := insertChannels(owner, channels, tx); err != nil {
cr.rollback("Failed to insert Channels during the update", tx, err)
return err
}
if err := updateConnections(key, id, connections, tx); err != nil {
if err := updateConnections(owner, id, connections, tx); err != nil {
if e, ok := err.(*pq.Error); ok {
if e.Code.Name() == fkViolation && e.Constraint == connConstraintErr {
return bootstrap.ErrNotFound
@@ -275,10 +319,10 @@ func (cr configRepository) UpdateConnections(key, id string, channels []bootstra
return nil
}
func (cr configRepository) Remove(key, id string) error {
func (cr configRepository) Remove(owner, id string) error {
q := `DELETE FROM configs WHERE mainflux_thing = $1 AND owner = $2`
if _, err := cr.db.Exec(q, id, key); err != nil {
return err
if _, err := cr.db.Exec(q, id, owner); err != nil {
return errors.Wrap(errRemove, err)
}
if _, err := cr.db.Exec(cleanupQuery); err != nil {
@@ -288,10 +332,10 @@ func (cr configRepository) Remove(key, id string) error {
return nil
}
func (cr configRepository) ChangeState(key, id string, state bootstrap.State) error {
func (cr configRepository) ChangeState(owner, id string, state bootstrap.State) error {
q := `UPDATE configs SET state = $1 WHERE mainflux_thing = $2 AND owner = $3;`
res, err := cr.db.Exec(q, state, id, key)
res, err := cr.db.Exec(q, state, id, owner)
if err != nil {
return err
}
@@ -308,20 +352,29 @@ func (cr configRepository) ChangeState(key, id string, state bootstrap.State) er
return nil
}
func (cr configRepository) ListExisting(key string, ids []string) ([]bootstrap.Channel, error) {
q := "SELECT mainflux_channel, name, metadata FROM channels WHERE owner = $1 AND mainflux_channel = ANY ($2)"
func (cr configRepository) ListExisting(owner string, ids []string) ([]bootstrap.Channel, error) {
var channels []bootstrap.Channel
if len(ids) == 0 {
return channels, nil
}
rows, err := cr.db.Query(q, key, pq.Array(ids))
q := "SELECT mainflux_channel, name, metadata FROM channels WHERE owner = $1 AND mainflux_channel = ANY ($2)"
rows, err := cr.db.Queryx(q, owner, pq.Array(ids))
if err != nil {
return []bootstrap.Channel{}, err
}
var channels []bootstrap.Channel
for rows.Next() {
var ch bootstrap.Channel
if err := rows.Scan(&ch.ID, &ch.Name, &ch.Metadata); err != nil {
var dbch dbChannel
if err := rows.StructScan(&dbch); err != nil {
cr.log.Error(fmt.Sprintf("Failed to read retrieved channels due to %s", err))
return []bootstrap.Channel{}, nil
return []bootstrap.Channel{}, err
}
ch, err := toChannel(dbch)
if err != nil {
cr.log.Error(fmt.Sprintf("Failed to deserialize channel due to %s", err))
return []bootstrap.Channel{}, err
}
channels = append(channels, ch)
@@ -330,55 +383,6 @@ func (cr configRepository) ListExisting(key string, ids []string) ([]bootstrap.C
return channels, nil
}
func (cr configRepository) SaveUnknown(key, id string) error {
q := `INSERT INTO unknown_configs (external_id, external_key) VALUES ($1, $2)`
if _, err := cr.db.Exec(q, id, key); err != nil {
if pqErr, ok := err.(*pq.Error); ok && pqErr.Code.Name() == duplicateErr {
return nil
}
return err
}
return nil
}
func (cr configRepository) RetrieveUnknown(offset, limit uint64) bootstrap.ConfigsPage {
q := `SELECT external_id, external_key FROM unknown_configs LIMIT $1 OFFSET $2`
rows, err := cr.db.Query(q, limit, offset)
if err != nil {
cr.log.Error(fmt.Sprintf("Failed to retrieve config due to %s", err))
return bootstrap.ConfigsPage{}
}
defer rows.Close()
items := []bootstrap.Config{}
for rows.Next() {
c := bootstrap.Config{}
if err := rows.Scan(&c.ExternalID, &c.ExternalKey); err != nil {
cr.log.Error(fmt.Sprintf("Failed to read retrieved config due to %s", err))
return bootstrap.ConfigsPage{}
}
items = append(items, c)
}
q = fmt.Sprintf(`SELECT COUNT(*) FROM unknown_configs`)
var total uint64
if err := cr.db.QueryRow(q).Scan(&total); err != nil {
cr.log.Error(fmt.Sprintf("Failed to count unknown configs due to %s", err))
return bootstrap.ConfigsPage{}
}
return bootstrap.ConfigsPage{
Total: total,
Offset: offset,
Limit: limit,
Configs: items,
}
}
func (cr configRepository) RemoveThing(id string) error {
q := `DELETE FROM configs WHERE mainflux_thing = $1`
_, err := cr.db.Exec(q, id)
@@ -386,38 +390,48 @@ func (cr configRepository) RemoveThing(id string) error {
if _, err := cr.db.Exec(cleanupQuery); err != nil {
cr.log.Warn("Failed to clean dangling channels after removal")
}
return err
if err != nil {
return errors.Wrap(errRemove, err)
}
return nil
}
func (cr configRepository) UpdateChannel(channel bootstrap.Channel) error {
q := `UPDATE channels SET name = $1, metadata = $2 WHERE mainflux_channel = $3`
_, err := cr.db.Exec(q, channel.Name, channel.Metadata, channel.ID)
func (cr configRepository) UpdateChannel(c bootstrap.Channel) error {
dbch, err := toDBChannel("", c)
if err != nil {
return err
}
return err
q := `UPDATE channels SET name = :name, metadata = :metadata WHERE mainflux_channel = :mainflux_channel`
if _, err = cr.db.NamedExec(q, dbch); err != nil {
return errors.Wrap(errUpdateChannels, err)
}
return nil
}
func (cr configRepository) RemoveChannel(id string) error {
q := `DELETE FROM channels WHERE mainflux_channel = $1`
_, err := cr.db.Exec(q, id)
return err
if _, err := cr.db.Exec(q, id); err != nil {
return errors.Wrap(errRemoveChannels, err)
}
return nil
}
func (cr configRepository) DisconnectThing(channelID, thingID string) error {
q := `UPDATE configs SET state = $1 WHERE EXISTS (
SELECT 1 FROM connections WHERE config_id = $2 AND channel_id = $3)`
_, err := cr.db.Exec(q, bootstrap.Inactive, thingID, channelID)
return err
if _, err := cr.db.Exec(q, bootstrap.Inactive, thingID, channelID); err != nil {
return errors.Wrap(errDisconnectThing, err)
}
return nil
}
func (cr configRepository) retrieveAll(key string, filter bootstrap.Filter) (string, []interface{}) {
func (cr configRepository) retrieveAll(owner string, filter bootstrap.Filter) (string, []interface{}) {
template := `WHERE owner = $1 %s`
params := []interface{}{key}
params := []interface{}{owner}
// One empty string so that strings Join works if only one filter is applied.
queries := []string{""}
// Since key is the first param, start from 2.
// Since owner is the first param, start from 2.
counter := 2
for k, v := range filter.FullMatch {
queries = append(queries, fmt.Sprintf("%s = $%d", k, counter))
@@ -435,7 +449,7 @@ func (cr configRepository) retrieveAll(key string, filter bootstrap.Filter) (str
return fmt.Sprintf(template, f), params
}
func (cr configRepository) rollback(content string, tx *sql.Tx, err error) {
func (cr configRepository) rollback(content string, tx *sqlx.Tx, err error) {
cr.log.Error(fmt.Sprintf("%s %s", content, err))
if err := tx.Rollback(); err != nil {
@@ -443,24 +457,23 @@ func (cr configRepository) rollback(content string, tx *sql.Tx, err error) {
}
}
func insertChannels(key string, channels []bootstrap.Channel, tx *sql.Tx) error {
func insertChannels(owner string, channels []bootstrap.Channel, tx *sqlx.Tx) error {
if len(channels) == 0 {
return nil
}
q := `INSERT INTO channels (mainflux_channel, owner, name, metadata) VALUES `
v := []interface{}{key}
var vals []string
// Since the first value is owner, start with the second one.
count := 2
var chans []dbChannel
for _, ch := range channels {
vals = append(vals, fmt.Sprintf("($%d, $1, $%d, $%d)", count, count+1, count+2))
v = append(v, ch.ID, ch.Name, ch.Metadata)
count += chanFieldsNum
dbch, err := toDBChannel(owner, ch)
if err != nil {
return err
}
chans = append(chans, dbch)
}
q = fmt.Sprintf("%s%s", q, strings.Join(vals, ","))
if _, err := tx.Exec(q, v...); err != nil {
q := `INSERT INTO channels (mainflux_channel, owner, name, metadata)
VALUES (:mainflux_channel, :owner, :name, :metadata)`
if _, err := tx.NamedExec(q, chans); err != nil {
e := err
if pqErr, ok := err.(*pq.Error); ok && pqErr.Code.Name() == duplicateErr {
e = bootstrap.ErrConflict
@@ -471,44 +484,38 @@ func insertChannels(key string, channels []bootstrap.Channel, tx *sql.Tx) error
return nil
}
func insertConnections(cfg bootstrap.Config, connections []string, tx *sql.Tx) error {
func insertConnections(cfg bootstrap.Config, connections []string, tx *sqlx.Tx) error {
if len(connections) == 0 {
return nil
}
q := `INSERT INTO connections (config_id, channel_id, config_owner, channel_owner) VALUES`
v := []interface{}{cfg.MFThing, cfg.Owner}
var vals []string
// Since the first value is Config ID and the second and third
// are Config owner, start with the second one.
count := 3
for _, id := range connections {
vals = append(vals, fmt.Sprintf("($1, $%d, $2, $2)", count))
v = append(v, id)
count++
q := `INSERT INTO connections (config_id, channel_id, config_owner, channel_owner)
VALUES (:config_id, :channel_id, :config_owner, :channel_owner)`
conns := []dbConnection{}
for _, conn := range connections {
dbconn := dbConnection{
Config: cfg.MFThing,
Channel: conn,
ConfigOwner: cfg.Owner,
ChannelOwner: cfg.Owner,
}
conns = append(conns, dbconn)
}
q = fmt.Sprintf("%s%s", q, strings.Join(vals, ","))
_, err := tx.Exec(q, v...)
_, err := tx.NamedExec(q, conns)
return err
}
func updateConnections(key, id string, connections []string, tx *sql.Tx) error {
func updateConnections(owner, id string, connections []string, tx *sqlx.Tx) error {
if len(connections) == 0 {
return nil
}
q := `DELETE FROM connections
WHERE config_id = $1 AND config_owner = $2 AND channel_owner = $2
AND channel_id NOT IN ($3)`
v := []interface{}{id, key}
v = append(v, pq.Array(connections))
res, err := tx.Exec(q, v...)
WHERE config_id = $1 AND config_owner = $2 AND channel_owner = $2
AND channel_id NOT IN ($3)`
res, err := tx.Exec(q, id, owner, pq.Array(connections))
if err != nil {
return err
}
@@ -518,22 +525,21 @@ func updateConnections(key, id string, connections []string, tx *sql.Tx) error {
return err
}
q = `INSERT INTO connections (config_id, channel_id, config_owner, channel_owner) VALUES`
v = []interface{}{id, key}
var vals []string
q = `INSERT INTO connections (config_id, channel_id, config_owner, channel_owner)
VALUES (:config_id, :channel_id, :config_owner, :channel_owner)`
// Since the first value is Config ID and the second is Config
// owner, start with the third one.
count := 3
for _, chID := range connections {
vals = append(vals, fmt.Sprintf("($1, $%d, $2, $2)", count))
v = append(v, chID)
count++
conns := []dbConnection{}
for _, conn := range connections {
dbconn := dbConnection{
Config: id,
Channel: conn,
ConfigOwner: owner,
ChannelOwner: owner,
}
conns = append(conns, dbconn)
}
// Add connections for current list of channels. Ignore if already exists.
q = fmt.Sprintf("%s%s%s", q, strings.Join(vals, ","), "ON CONFLICT (config_id, config_owner, channel_id, channel_owner) DO NOTHING")
if _, err := tx.Exec(q, v...); err != nil {
if _, err := tx.NamedExec(q, conns); err != nil {
return err
}
@@ -556,3 +562,111 @@ func nullString(s string) sql.NullString {
Valid: true,
}
}
type dbConfig struct {
MFThing string `db:"mainflux_thing"`
Owner string `db:"owner"`
Name sql.NullString `db:"name"`
ClientCert sql.NullString `db:"client_cert"`
ClientKey sql.NullString `db:"client_key"`
CaCert sql.NullString `db:"ca_cert"`
MFKey string `db:"mainflux_key"`
ExternalID string `db:"external_id"`
ExternalKey string `db:"external_key"`
Content sql.NullString `db:"content"`
State bootstrap.State `db:"state"`
}
func toDBConfig(cfg bootstrap.Config) dbConfig {
return dbConfig{
MFThing: cfg.MFThing,
Owner: cfg.Owner,
Name: nullString(cfg.Name),
ClientCert: nullString(cfg.ClientCert),
ClientKey: nullString(cfg.ClientKey),
CaCert: nullString(cfg.CACert),
MFKey: cfg.MFKey,
ExternalID: cfg.ExternalID,
ExternalKey: cfg.ExternalKey,
Content: nullString(cfg.Content),
State: cfg.State,
}
}
func toConfig(dbcfg dbConfig) bootstrap.Config {
cfg := bootstrap.Config{
MFThing: dbcfg.MFThing,
Owner: dbcfg.Owner,
MFKey: dbcfg.MFKey,
ExternalID: dbcfg.ExternalID,
ExternalKey: dbcfg.ExternalKey,
State: dbcfg.State,
}
if dbcfg.Name.Valid {
cfg.Name = dbcfg.Name.String
}
if dbcfg.Content.Valid {
cfg.Content = dbcfg.Content.String
}
if dbcfg.ClientCert.Valid {
cfg.ClientCert = dbcfg.ClientCert.String
}
if dbcfg.ClientKey.Valid {
cfg.ClientKey = dbcfg.ClientKey.String
}
if dbcfg.CaCert.Valid {
cfg.CACert = dbcfg.CaCert.String
}
return cfg
}
type dbChannel struct {
ID string `db:"mainflux_channel"`
Name sql.NullString `db:"name"`
Owner sql.NullString `db:"owner"`
Metadata string `db:"metadata"`
}
func toDBChannel(owner string, ch bootstrap.Channel) (dbChannel, error) {
dbch := dbChannel{
ID: ch.ID,
Name: nullString(ch.Name),
Owner: nullString(owner),
}
metadata, err := json.Marshal(ch.Metadata)
if err != nil {
return dbChannel{}, errors.Wrap(errMarshalChannel, err)
}
dbch.Metadata = string(metadata)
return dbch, nil
}
func toChannel(dbch dbChannel) (bootstrap.Channel, error) {
ch := bootstrap.Channel{
ID: dbch.ID,
}
if dbch.Name.Valid {
ch.Name = dbch.Name.String
}
if err := json.Unmarshal([]byte(dbch.Metadata), &ch.Metadata); err != nil {
return bootstrap.Channel{}, errors.Wrap(errUnmarshalChannel, err)
}
return ch, nil
}
type dbConnection struct {
Config string `db:"config_id"`
Channel string `db:"channel_id"`
ConfigOwner string `db:"config_owner"`
ChannelOwner string `db:"channel_owner"`
}
+186 -184
View File
@@ -1,9 +1,5 @@
//
// Copyright (c) 2018
// Mainflux
//
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
//
package postgres_test
@@ -12,9 +8,10 @@ import (
"strconv"
"testing"
"github.com/gofrs/uuid"
"github.com/mainflux/mainflux/bootstrap"
"github.com/mainflux/mainflux/bootstrap/postgres"
uuid "github.com/satori/go.uuid"
"github.com/mainflux/mainflux/pkg/errors"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@@ -29,8 +26,8 @@ var (
ExternalKey: "external-key",
Owner: "user@email.com",
MFChannels: []bootstrap.Channel{
bootstrap.Channel{ID: "1", Name: "name 1", Metadata: "{\"meta\":1}"},
bootstrap.Channel{ID: "2", Name: "name 2", Metadata: "{\"meta\":2}"},
bootstrap.Channel{ID: "1", Name: "name 1", Metadata: map[string]interface{}{"meta": 1.0}},
bootstrap.Channel{ID: "2", Name: "name 2", Metadata: map[string]interface{}{"meta": 2.0}},
},
Content: "content",
State: bootstrap.Inactive,
@@ -94,7 +91,7 @@ func TestSave(t *testing.T) {
}
for _, tc := range cases {
_, err := repo.Save(tc.config, tc.connections)
assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
}
}
@@ -105,13 +102,18 @@ func TestRetrieveByID(t *testing.T) {
c := config
// Use UUID to prevent conflicts.
id := uuid.NewV4().String()
c.MFKey = id
c.MFThing = id
c.ExternalID = id
c.ExternalKey = id
id, err = repo.Save(c, channels)
uid, err := uuid.NewV4()
require.Nil(t, err, fmt.Sprintf("Got unexpected error: %s.\n", err))
c.MFKey = uid.String()
c.MFThing = uid.String()
c.ExternalID = uid.String()
c.ExternalKey = uid.String()
id, err := repo.Save(c, channels)
require.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err))
nonexistentConfID, err := uuid.NewV4()
require.Nil(t, err, fmt.Sprintf("Got unexpected error: %s.\n", err))
cases := []struct {
desc string
owner string
@@ -133,7 +135,7 @@ func TestRetrieveByID(t *testing.T) {
{
desc: "retrieve a non-existing config",
owner: c.Owner,
id: uuid.NewV4().String(),
id: nonexistentConfID.String(),
err: bootstrap.ErrNotFound,
},
{
@@ -145,7 +147,7 @@ func TestRetrieveByID(t *testing.T) {
}
for _, tc := range cases {
_, err := repo.RetrieveByID(tc.owner, tc.id)
assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
}
}
@@ -157,11 +159,13 @@ func TestRetrieveAll(t *testing.T) {
for i := 0; i < numConfigs; i++ {
c := config
// Use UUID to prevent conflict errors.
id := uuid.NewV4().String()
c.ExternalID = id
uid, err := uuid.NewV4()
require.Nil(t, err, fmt.Sprintf("Got unexpected error: %s.\n", err))
c.ExternalID = uid.String()
c.Name = fmt.Sprintf("name %d", i)
c.MFThing = id
c.MFKey = id
c.MFThing = uid.String()
c.MFKey = uid.String()
if i%2 == 0 {
c.State = bootstrap.Active
@@ -171,7 +175,7 @@ func TestRetrieveAll(t *testing.T) {
c.MFChannels = nil
}
_, err := repo.Save(c, channels)
_, err = repo.Save(c, channels)
require.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err))
}
@@ -235,42 +239,34 @@ func TestRetrieveByExternalID(t *testing.T) {
c := config
// Use UUID to prevent conflicts.
id := uuid.NewV4().String()
c.MFKey = id
c.MFThing = id
c.ExternalID = id
c.ExternalKey = id
uid, err := uuid.NewV4()
require.Nil(t, err, fmt.Sprintf("Got unexpected error: %s.\n", err))
c.MFKey = uid.String()
c.MFThing = uid.String()
c.ExternalID = uid.String()
c.ExternalKey = uid.String()
_, err = repo.Save(c, channels)
require.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err))
cases := []struct {
desc string
externalID string
externalKey string
err error
desc string
externalID string
err error
}{
{
desc: "retrieve with invalid external ID",
externalID: strconv.Itoa(numConfigs + 1),
externalKey: config.ExternalKey,
err: bootstrap.ErrNotFound,
desc: "retrieve with invalid external ID",
externalID: strconv.Itoa(numConfigs + 1),
err: bootstrap.ErrNotFound,
},
{
desc: "retrieve with invalid external key",
externalID: c.ExternalID,
externalKey: "invalid",
err: bootstrap.ErrNotFound,
},
{
desc: "retrieve with external key",
externalID: c.ExternalID,
externalKey: c.ExternalKey,
err: nil,
desc: "retrieve with external key",
externalID: c.ExternalID,
err: nil,
},
}
for _, tc := range cases {
_, err := repo.RetrieveByExternalID(tc.externalKey, tc.externalID)
assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
_, err := repo.RetrieveByExternalID(tc.externalID)
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
}
}
@@ -281,11 +277,12 @@ func TestUpdate(t *testing.T) {
c := config
// Use UUID to prevent conflicts.
id := uuid.NewV4().String()
c.MFKey = id
c.MFThing = id
c.ExternalID = id
c.ExternalKey = id
uid, err := uuid.NewV4()
require.Nil(t, err, fmt.Sprintf("Got unexpected error: %s.\n", err))
c.MFKey = uid.String()
c.MFThing = uid.String()
c.ExternalID = uid.String()
c.ExternalKey = uid.String()
_, err = repo.Save(c, channels)
require.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err))
@@ -314,7 +311,63 @@ func TestUpdate(t *testing.T) {
}
for _, tc := range cases {
err := repo.Update(tc.config)
assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
}
}
func TestUpdateCert(t *testing.T) {
repo := postgres.NewConfigRepository(db, testLog)
err := deleteChannels(repo)
require.Nil(t, err, "Channels cleanup expected to succeed.")
c := config
// Use UUID to prevent conflicts.
uid, err := uuid.NewV4()
require.Nil(t, err, fmt.Sprintf("Got unexpected error: %s.\n", err))
c.MFKey = uid.String()
c.MFThing = uid.String()
c.ExternalID = uid.String()
c.ExternalKey = uid.String()
_, err = repo.Save(c, channels)
require.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err))
c.Content = "new content"
c.Name = "new name"
wrongOwner := c
wrongOwner.Owner = "3"
cases := []struct {
desc string
thingID string
owner string
cert string
certKey string
ca string
err error
}{
{
desc: "update with wrong owner",
thingID: "",
cert: "cert",
certKey: "certKey",
ca: "",
owner: "wrong",
err: bootstrap.ErrNotFound,
},
{
desc: "update a config",
thingID: c.MFThing,
cert: "cert",
certKey: "certKey",
ca: "ca",
owner: c.Owner,
err: nil,
},
}
for _, tc := range cases {
err := repo.UpdateCert(tc.owner, tc.thingID, tc.cert, tc.certKey, tc.ca)
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
}
}
@@ -325,26 +378,28 @@ func TestUpdateConnections(t *testing.T) {
c := config
// Use UUID to prevent conflicts.
id := uuid.NewV4().String()
c.MFKey = id
c.MFThing = id
c.ExternalID = id
c.ExternalKey = id
uid, err := uuid.NewV4()
require.Nil(t, err, fmt.Sprintf("Got unexpected error: %s.\n", err))
c.MFKey = uid.String()
c.MFThing = uid.String()
c.ExternalID = uid.String()
c.ExternalKey = uid.String()
_, err = repo.Save(c, channels)
require.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err))
// Use UUID to prevent conflicts.
id = uuid.NewV4().String()
c.MFKey = id
c.MFThing = id
c.ExternalID = id
c.ExternalKey = id
uid, err = uuid.NewV4()
require.Nil(t, err, fmt.Sprintf("Got unexpected error: %s.\n", err))
c.MFKey = uid.String()
c.MFThing = uid.String()
c.ExternalID = uid.String()
c.ExternalKey = uid.String()
c.MFChannels = []bootstrap.Channel{}
_, err = repo.Save(c, []string{channels[0]})
c2, err := repo.Save(c, []string{channels[0]})
require.Nil(t, err, fmt.Sprintf("Saving a config expected to succeed: %s.\n", err))
cases := []struct {
desc string
key string
owner string
id string
channels []bootstrap.Channel
connections []string
@@ -352,7 +407,7 @@ func TestUpdateConnections(t *testing.T) {
}{
{
desc: "update connections of non-existing config",
key: config.Owner,
owner: config.Owner,
id: "unknown",
channels: nil,
connections: []string{channels[1]},
@@ -360,15 +415,23 @@ func TestUpdateConnections(t *testing.T) {
},
{
desc: "update connections",
key: config.Owner,
owner: config.Owner,
id: c.MFThing,
channels: nil,
connections: []string{channels[1]},
err: nil,
},
{
desc: "update connections with existing channels",
owner: config.Owner,
id: c2,
channels: nil,
connections: channels,
err: nil,
},
{
desc: "update connections no channels",
key: config.Owner,
owner: config.Owner,
id: c.MFThing,
channels: nil,
connections: nil,
@@ -376,8 +439,8 @@ func TestUpdateConnections(t *testing.T) {
},
}
for _, tc := range cases {
err := repo.UpdateConnections(tc.key, tc.id, tc.channels, tc.connections)
assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
err := repo.UpdateConnections(tc.owner, tc.id, tc.channels, tc.connections)
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
}
}
@@ -388,12 +451,13 @@ func TestRemove(t *testing.T) {
c := config
// Use UUID to prevent conflicts.
id := uuid.NewV4().String()
c.MFKey = id
c.MFThing = id
c.ExternalID = id
c.ExternalKey = id
id, err = repo.Save(c, channels)
uid, err := uuid.NewV4()
require.Nil(t, err, fmt.Sprintf("Got unexpected error: %s.\n", err))
c.MFKey = uid.String()
c.MFThing = uid.String()
c.ExternalID = uid.String()
c.ExternalKey = uid.String()
id, err := repo.Save(c, channels)
require.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err))
// Removal works the same for both existing and non-existing
@@ -403,7 +467,7 @@ func TestRemove(t *testing.T) {
require.Nil(t, err, fmt.Sprintf("%d: failed to remove config due to: %s", i, err))
_, err = repo.RetrieveByID(c.Owner, id)
require.Equal(t, bootstrap.ErrNotFound, err, fmt.Sprintf("%d: expected %s got %s", i, bootstrap.ErrNotFound, err))
require.True(t, errors.Contains(err, bootstrap.ErrNotFound), fmt.Sprintf("%d: expected %s got %s", i, bootstrap.ErrNotFound, err))
}
}
@@ -414,11 +478,12 @@ func TestChangeState(t *testing.T) {
c := config
// Use UUID to prevent conflicts.
id := uuid.NewV4().String()
c.MFKey = id
c.MFThing = id
c.ExternalID = id
c.ExternalKey = id
uid, err := uuid.NewV4()
require.Nil(t, err, fmt.Sprintf("Got unexpected error: %s.\n", err))
c.MFKey = uid.String()
c.MFThing = uid.String()
c.ExternalID = uid.String()
c.ExternalKey = uid.String()
saved, err := repo.Save(c, channels)
require.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err))
@@ -437,7 +502,7 @@ func TestChangeState(t *testing.T) {
},
{
desc: "change state with wrong id",
id: uuid.NewV4().String(),
id: "wrong",
owner: c.Owner,
err: bootstrap.ErrNotFound,
},
@@ -458,7 +523,7 @@ func TestChangeState(t *testing.T) {
}
for _, tc := range cases {
err := repo.ChangeState(tc.owner, tc.id, tc.state)
assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
}
}
@@ -469,114 +534,52 @@ func TestListExisting(t *testing.T) {
c := config
// Use UUID to prevent conflicts.
id := uuid.NewV4().String()
c.MFKey = id
c.MFThing = id
c.ExternalID = id
c.ExternalKey = id
uid, err := uuid.NewV4()
require.Nil(t, err, fmt.Sprintf("Got unexpected error: %s.\n", err))
c.MFKey = uid.String()
c.MFThing = uid.String()
c.ExternalID = uid.String()
c.ExternalKey = uid.String()
_, err = repo.Save(c, channels)
require.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err))
var chs []bootstrap.Channel
for _, ch := range config.MFChannels {
ch.Metadata = []byte(ch.Metadata.(string))
chs = append(chs, ch)
}
cases := []struct {
desc string
key string
owner string
connections []string
existing []bootstrap.Channel
}{
{
desc: "list all existing channels",
key: c.Owner,
owner: c.Owner,
connections: channels,
existing: chs,
},
{
desc: "list a subset of existing channels",
key: c.Owner,
owner: c.Owner,
connections: []string{channels[0], "5"},
existing: []bootstrap.Channel{chs[0]},
},
{
desc: "list a subset of existing channels empty",
key: c.Owner,
owner: c.Owner,
connections: []string{"5", "6"},
existing: []bootstrap.Channel{},
},
}
for _, tc := range cases {
existing, err := repo.ListExisting(tc.key, tc.connections)
existing, err := repo.ListExisting(tc.owner, tc.connections)
assert.Nil(t, err, fmt.Sprintf("%s: unexpected error: %s", tc.desc, err))
assert.ElementsMatch(t, tc.existing, existing, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.existing, existing))
}
}
func TestSaveUnknown(t *testing.T) {
repo := postgres.NewConfigRepository(db, testLog)
cases := []struct {
desc string
externalID string
externalKey string
err error
}{
{
desc: "save unknown",
externalID: uuid.NewV4().String(),
externalKey: uuid.NewV4().String(),
err: nil,
},
{
desc: "save invalid unknown",
externalID: uuid.NewV4().String(),
externalKey: "",
err: nil,
},
}
for _, tc := range cases {
err := repo.SaveUnknown(tc.externalKey, tc.externalID)
assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
}
}
func TestRetrieveUnknown(t *testing.T) {
repo := postgres.NewConfigRepository(db, testLog)
for i := 0; i < numConfigs; i++ {
id := uuid.NewV4().String()
repo.SaveUnknown(id, id)
}
cases := []struct {
desc string
offset uint64
limit uint64
size int
}{
{
desc: "retrieve all",
offset: 0,
limit: uint64(numConfigs),
size: numConfigs,
},
{
desc: "retrieve a subset",
offset: 5,
limit: uint64(numConfigs - 5),
size: numConfigs - 5,
},
}
for _, tc := range cases {
ret := repo.RetrieveUnknown(tc.offset, tc.limit)
size := len(ret.Configs)
assert.Equal(t, tc.size, size, fmt.Sprintf("%s: expected %d got %d\n", tc.desc, tc.size, size))
}
}
func TestRemoveThing(t *testing.T) {
repo := postgres.NewConfigRepository(db, testLog)
err := deleteChannels(repo)
@@ -584,11 +587,12 @@ func TestRemoveThing(t *testing.T) {
c := config
// Use UUID to prevent conflicts.
id := uuid.NewV4().String()
c.MFKey = id
c.MFThing = id
c.ExternalID = id
c.ExternalKey = id
uid, err := uuid.NewV4()
require.Nil(t, err, fmt.Sprintf("Got unexpected error: %s.\n", err))
c.MFKey = uid.String()
c.MFThing = uid.String()
c.ExternalID = uid.String()
c.ExternalKey = uid.String()
saved, err := repo.Save(c, channels)
require.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err))
for i := 0; i < 2; i++ {
@@ -604,19 +608,20 @@ func TestUpdateChannel(t *testing.T) {
c := config
// Use UUID to prevent conflicts.
id := uuid.NewV4().String()
c.MFKey = id
c.MFThing = id
c.ExternalID = id
c.ExternalKey = id
uid, err := uuid.NewV4()
require.Nil(t, err, fmt.Sprintf("Got unexpected error: %s.\n", err))
c.MFKey = uid.String()
c.MFThing = uid.String()
c.ExternalID = uid.String()
c.ExternalKey = uid.String()
_, err = repo.Save(c, channels)
require.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err))
id = c.MFChannels[0].ID
id := c.MFChannels[0].ID
update := bootstrap.Channel{
ID: id,
Name: "update name",
Metadata: []byte("{\"update\":\"metadata update\"}"),
Metadata: map[string]interface{}{"update": "metadata update"},
}
err = repo.UpdateChannel(update)
assert.Nil(t, err, fmt.Sprintf("updating config expected to succeed: %s.\n", err))
@@ -640,16 +645,12 @@ func TestRemoveChannel(t *testing.T) {
require.Nil(t, err, "Channels cleanup expected to succeed.")
c := config
for i, ch := range c.MFChannels {
c.MFChannels[i].Metadata = []byte(ch.Metadata.(string))
}
// Use UUID to prevent conflicts.
id := uuid.NewV4().String()
c.MFKey = id
c.MFThing = id
c.ExternalID = id
c.ExternalKey = id
uid, err := uuid.NewV4()
require.Nil(t, err, fmt.Sprintf("Got unexpected error: %s.\n", err))
c.MFKey = uid.String()
c.MFThing = uid.String()
c.ExternalID = uid.String()
c.ExternalKey = uid.String()
_, err = repo.Save(c, channels)
require.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err))
@@ -668,11 +669,12 @@ func TestDisconnectThing(t *testing.T) {
c := config
// Use UUID to prevent conflicts.
id := uuid.NewV4().String()
c.MFKey = id
c.MFThing = id
c.ExternalID = id
c.ExternalKey = id
uid, err := uuid.NewV4()
require.Nil(t, err, fmt.Sprintf("Got unexpected error: %s.\n", err))
c.MFKey = uid.String()
c.MFThing = uid.String()
c.ExternalID = uid.String()
c.ExternalKey = uid.String()
saved, err := repo.Save(c, channels)
require.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err))
+1 -5
View File
@@ -1,9 +1,5 @@
//
// Copyright (c) 2018
// Mainflux
//
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
//
// Package postgres contains repository implementations using PostgreSQL as
// the underlying database.
+18 -10
View File
@@ -1,16 +1,12 @@
//
// Copyright (c) 2018
// Mainflux
//
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
//
package postgres
import (
"database/sql"
"fmt"
"github.com/jmoiron/sqlx"
_ "github.com/lib/pq" // required for SQL access
migrate "github.com/rubenv/sql-migrate"
)
@@ -31,10 +27,10 @@ type Config struct {
// Connect creates a connection to the PostgreSQL instance and applies any
// unapplied database migrations. A non-nil error is returned to indicate
// failure.
func Connect(cfg Config) (*sql.DB, error) {
func Connect(cfg Config) (*sqlx.DB, error) {
url := fmt.Sprintf("host=%s port=%s user=%s dbname=%s password=%s sslmode=%s sslcert=%s sslkey=%s sslrootcert=%s", cfg.Host, cfg.Port, cfg.User, cfg.Name, cfg.Pass, cfg.SSLMode, cfg.SSLCert, cfg.SSLKey, cfg.SSLRootCert)
db, err := sql.Open("postgres", url)
db, err := sqlx.Open("postgres", url)
if err != nil {
return nil, err
}
@@ -46,7 +42,7 @@ func Connect(cfg Config) (*sql.DB, error) {
return db, nil
}
func migrateDB(db *sql.DB) error {
func migrateDB(db *sqlx.DB) error {
migrations := &migrate.MemoryMigrationSource{
Migrations: []*migrate.Migration{
{
@@ -60,6 +56,9 @@ func migrateDB(db *sql.DB) error {
external_id TEXT UNIQUE NOT NULL,
external_key TEXT NOT NULL,
content TEXT,
client_cert TEXT,
client_key TEXT,
ca_cert TEXT,
state BIGINT NOT NULL,
PRIMARY KEY (mainflux_thing, owner)
)`,
@@ -92,10 +91,19 @@ func migrateDB(db *sql.DB) error {
"DROP TABLE unknown_configs",
},
},
{
Id: "configs_2",
Up: []string{
"DROP TABLE IF EXISTS unknown_configs",
},
Down: []string{
"CREATE TABLE IF NOT EXISTS unknown_configs",
},
},
},
}
_, err := migrate.Exec(db, "postgres", migrations, migrate.Up)
_, err := migrate.Exec(db.DB, "postgres", migrations, migrate.Up)
return err
}
+8 -11
View File
@@ -1,22 +1,18 @@
//
// Copyright (c) 2018
// Mainflux
//
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
//
package postgres_test
import (
"database/sql"
"fmt"
"log"
"os"
"testing"
"github.com/jmoiron/sqlx"
"github.com/mainflux/mainflux/bootstrap/postgres"
"github.com/mainflux/mainflux/logger"
"gopkg.in/ory-am/dockertest.v3"
dockertest "github.com/ory/dockertest/v3"
)
const (
@@ -26,7 +22,7 @@ const (
var (
testLog, _ = logger.New(os.Stdout, logger.Info.String())
db *sql.DB
db *sqlx.DB
)
func TestMain(m *testing.M) {
@@ -40,7 +36,7 @@ func TestMain(m *testing.M) {
"POSTGRES_PASSWORD=test",
"POSTGRES_DB=test",
}
container, err := pool.Run("postgres", "10.2-alpine", cfg)
container, err := pool.Run("postgres", "10.8-alpine", cfg)
if err != nil {
log.Fatalf("Could not start container: %s", err)
}
@@ -49,7 +45,7 @@ func TestMain(m *testing.M) {
if err := pool.Retry(func() error {
url := fmt.Sprintf("host=localhost port=%s user=test dbname=test password=test sslmode=disable", port)
db, err = sql.Open("postgres", url)
db, err = sqlx.Open("postgres", url)
if err != nil {
return err
}
@@ -73,10 +69,11 @@ func TestMain(m *testing.M) {
if db, err = postgres.Connect(dbConfig); err != nil {
log.Fatalf("Could not setup test DB connection: %s", err)
}
defer db.Close()
code := m.Run()
// Defers will not be run when using os.Exit
db.Close()
if err := pool.Purge(container); err != nil {
log.Fatalf("Could not purge container: %s", err)
}
+41 -17
View File
@@ -1,17 +1,15 @@
//
// Copyright (c) 2018
// Mainflux
//
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
//
package bootstrap
import (
"errors"
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"encoding/json"
"io"
"net/http"
"github.com/mainflux/mainflux"
)
// bootstrapRes represent Mainflux Response to the Bootatrap request.
@@ -21,7 +19,10 @@ type bootstrapRes struct {
MFThing string `json:"mainflux_id"`
MFKey string `json:"mainflux_key"`
MFChannels []channelRes `json:"mainflux_channels"`
Content string `json:"content"`
Content string `json:"content,omitempty"`
ClientCert string `json:"client_cert,omitempty"`
ClientKey string `json:"client_key,omitempty"`
CACert string `json:"ca_cert,omitempty"`
}
type channelRes struct {
@@ -42,19 +43,17 @@ func (res bootstrapRes) Empty() bool {
return false
}
type reader struct{}
type reader struct {
encKey []byte
}
// NewConfigReader return new reader which is used to generate response
// from the config.
func NewConfigReader() ConfigReader {
return reader{}
func NewConfigReader(encKey []byte) ConfigReader {
return reader{encKey: encKey}
}
func (r reader) ReadConfig(cfg Config) (mainflux.Response, error) {
if len(cfg.MFChannels) < 1 {
return bootstrapRes{}, errors.New("Invalid configuration")
}
func (r reader) ReadConfig(cfg Config, secure bool) (interface{}, error) {
var channels []channelRes
for _, ch := range cfg.MFChannels {
channels = append(channels, channelRes{ID: ch.ID, Name: ch.Name, Metadata: ch.Metadata})
@@ -65,7 +64,32 @@ func (r reader) ReadConfig(cfg Config) (mainflux.Response, error) {
MFThing: cfg.MFThing,
MFChannels: channels,
Content: cfg.Content,
ClientCert: cfg.ClientCert,
ClientKey: cfg.ClientKey,
CACert: cfg.CACert,
}
if secure {
b, err := json.Marshal(res)
if err != nil {
return nil, err
}
return r.encrypt(b)
}
return res, nil
}
func (r reader) encrypt(in []byte) ([]byte, error) {
block, err := aes.NewCipher(r.encKey)
if err != nil {
return nil, err
}
ciphertext := make([]byte, aes.BlockSize+len(in))
iv := ciphertext[:aes.BlockSize]
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
return nil, err
}
stream := cipher.NewCFBEncrypter(block, iv)
stream.XORKeyStream(ciphertext[aes.BlockSize:], in)
return ciphertext, nil
}
+126
View File
@@ -0,0 +1,126 @@
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
package bootstrap_test
import (
"crypto/aes"
"crypto/cipher"
"encoding/json"
"fmt"
"net/http"
"testing"
"github.com/mainflux/mainflux"
"github.com/mainflux/mainflux/bootstrap"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
type readChan struct {
ID string `json:"id"`
Name string `json:"name,omitempty"`
Metadata interface{} `json:"metadata,omitempty"`
}
type readResp struct {
MFThing string `json:"mainflux_id"`
MFKey string `json:"mainflux_key"`
MFChannels []readChan `json:"mainflux_channels"`
Content string `json:"content,omitempty"`
ClientCert string `json:"client_cert,omitempty"`
ClientKey string `json:"client_key,omitempty"`
CACert string `json:"ca_cert,omitempty"`
}
func dec(in []byte) ([]byte, error) {
block, err := aes.NewCipher(encKey)
if err != nil {
return nil, err
}
if len(in) < aes.BlockSize {
return nil, bootstrap.ErrMalformedEntity
}
iv := in[:aes.BlockSize]
in = in[aes.BlockSize:]
stream := cipher.NewCFBDecrypter(block, iv)
stream.XORKeyStream(in, in)
return in, nil
}
func TestReadConfig(t *testing.T) {
cfg := bootstrap.Config{
MFThing: "mf_id",
ClientCert: "client_cert",
ClientKey: "client_key",
CACert: "ca_cert",
MFKey: "mf_key",
MFChannels: []bootstrap.Channel{
bootstrap.Channel{
ID: "mf_id",
Name: "mf_name",
Metadata: map[string]interface{}{"key": "value}"},
},
},
Content: "content",
}
ret := readResp{
MFThing: "mf_id",
MFKey: "mf_key",
MFChannels: []readChan{
{
ID: "mf_id",
Name: "mf_name",
Metadata: map[string]interface{}{"key": "value}"},
},
},
Content: "content",
ClientCert: "client_cert",
ClientKey: "client_key",
CACert: "ca_cert",
}
bin, err := json.Marshal(ret)
require.Nil(t, err, fmt.Sprintf("Marshalling expected to succeed: %s.\n", err))
reader := bootstrap.NewConfigReader(encKey)
cases := []struct {
desc string
config bootstrap.Config
enc []byte
secret bool
err error
}{
{
desc: "read a config",
config: cfg,
enc: bin,
secret: false,
},
{
desc: "read encrypted config",
config: cfg,
enc: bin,
secret: true,
},
}
for _, tc := range cases {
res, err := reader.ReadConfig(tc.config, tc.secret)
require.Nil(t, err, fmt.Sprintf("Reading config to succeed: %s.\n", err))
if tc.secret {
d, err := dec(res.([]byte))
require.Nil(t, err, fmt.Sprintf("Decrypting expected to succeed: %s.\n", err))
assert.Equal(t, tc.enc, d, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.enc, d))
continue
}
b, err := json.Marshal(res)
require.Nil(t, err, fmt.Sprintf("Marshalling expected to succeed: %s.\n", err))
assert.Equal(t, tc.enc, b, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.enc, b))
resp, ok := res.(mainflux.Response)
require.True(t, ok, fmt.Sprintf("If not encrypted, reader should return response."))
assert.False(t, resp.Empty(), fmt.Sprintf("Response should not be empty %s.", err))
assert.Equal(t, http.StatusOK, resp.Code(), fmt.Sprintf("Default config response code should be 200."))
}
}
+6
View File
@@ -0,0 +1,6 @@
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
// Package consumer contains events consumer for events
// published by Things service.
package consumer
+20
View File
@@ -0,0 +1,20 @@
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
package consumer
type removeEvent struct {
id string
}
type updateChannelEvent struct {
id string
name string
metadata map[string]interface{}
}
// Connection event is either connect or disconnect event.
type disconnectEvent struct {
thingID string
channelID string
}
+158
View File
@@ -0,0 +1,158 @@
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
package consumer
import (
"encoding/json"
"fmt"
"github.com/go-redis/redis"
"github.com/mainflux/mainflux/bootstrap"
"github.com/mainflux/mainflux/logger"
)
const (
stream = "mainflux.things"
group = "mainflux.bootstrap"
thingPrefix = "thing."
thingRemove = thingPrefix + "remove"
thingDisconnect = thingPrefix + "disconnect"
channelPrefix = "channel."
channelUpdate = channelPrefix + "update"
channelRemove = channelPrefix + "remove"
exists = "BUSYGROUP Consumer Group name already exists"
)
// Subscriber represents event source for things and channels provisioning.
type Subscriber interface {
// Subscribes to given subject and receives events.
Subscribe(string) error
}
type eventStore struct {
svc bootstrap.Service
client *redis.Client
consumer string
logger logger.Logger
}
// NewEventStore returns new event store instance.
func NewEventStore(svc bootstrap.Service, client *redis.Client, consumer string, log logger.Logger) Subscriber {
return eventStore{
svc: svc,
client: client,
consumer: consumer,
logger: log,
}
}
func (es eventStore) Subscribe(subject string) error {
err := es.client.XGroupCreateMkStream(stream, group, "$").Err()
if err != nil && err.Error() != exists {
return err
}
for {
streams, err := es.client.XReadGroup(&redis.XReadGroupArgs{
Group: group,
Consumer: es.consumer,
Streams: []string{stream, ">"},
Count: 100,
}).Result()
if err != nil || len(streams) == 0 {
continue
}
for _, msg := range streams[0].Messages {
event := msg.Values
var err error
switch event["operation"] {
case thingRemove:
rte := decodeRemoveThing(event)
err = es.handleRemoveThing(rte)
case thingDisconnect:
dte := decodeDisconnectThing(event)
err = es.handleDisconnectThing(dte)
case channelUpdate:
uce := decodeUpdateChannel(event)
err = es.handleUpdateChannel(uce)
case channelRemove:
rce := decodeRemoveChannel(event)
err = es.handleRemoveChannel(rce)
}
if err != nil {
es.logger.Warn(fmt.Sprintf("Failed to handle event sourcing: %s", err.Error()))
break
}
es.client.XAck(stream, group, msg.ID)
}
}
}
func decodeRemoveThing(event map[string]interface{}) removeEvent {
return removeEvent{
id: read(event, "id", ""),
}
}
func decodeUpdateChannel(event map[string]interface{}) updateChannelEvent {
strmeta := read(event, "metadata", "{}")
var metadata map[string]interface{}
if err := json.Unmarshal([]byte(strmeta), metadata); err != nil {
metadata = map[string]interface{}{}
}
return updateChannelEvent{
id: read(event, "id", ""),
name: read(event, "name", ""),
metadata: metadata,
}
}
func decodeRemoveChannel(event map[string]interface{}) removeEvent {
return removeEvent{
id: read(event, "id", ""),
}
}
func decodeDisconnectThing(event map[string]interface{}) disconnectEvent {
return disconnectEvent{
channelID: read(event, "chan_id", ""),
thingID: read(event, "thing_id", ""),
}
}
func (es eventStore) handleRemoveThing(rte removeEvent) error {
return es.svc.RemoveConfigHandler(rte.id)
}
func (es eventStore) handleUpdateChannel(uce updateChannelEvent) error {
channel := bootstrap.Channel{
ID: uce.id,
Name: uce.name,
Metadata: uce.metadata,
}
return es.svc.UpdateChannelHandler(channel)
}
func (es eventStore) handleRemoveChannel(rce removeEvent) error {
return es.svc.RemoveChannelHandler(rce.id)
}
func (es eventStore) handleDisconnectThing(dte disconnectEvent) error {
return es.svc.DisconnectThingHandler(dte.channelID, dte.thingID)
}
func read(event map[string]interface{}, key, def string) string {
val, ok := event[key].(string)
if !ok {
return def
}
return val
}
-17
View File
@@ -1,17 +0,0 @@
package redis
type removeEvent struct {
id string
}
type updateChannelEvent struct {
id string
name string
metadata string
}
// Connection event is either connect or disconnect event.
type disconnectEvent struct {
thingID string
channelID string
}
+6
View File
@@ -0,0 +1,6 @@
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
// Package producer contains the domain events needed to support
// event sourcing of Bootstrap service actions.
package producer
+134
View File
@@ -0,0 +1,134 @@
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
package producer
import (
"strings"
"time"
"github.com/mainflux/mainflux/bootstrap"
)
const (
configPrefix = "config."
configCreate = configPrefix + "create"
configUpdate = configPrefix + "update"
configRemove = configPrefix + "remove"
thingPrefix = "thing."
thingBootstrap = thingPrefix + "bootstrap"
thingStateChange = thingPrefix + "state_change"
thingUpdateConnections = thingPrefix + "update_connections"
)
type event interface {
encode() map[string]interface{}
}
var (
_ event = (*createConfigEvent)(nil)
_ event = (*updateConfigEvent)(nil)
_ event = (*removeConfigEvent)(nil)
_ event = (*bootstrapEvent)(nil)
_ event = (*changeStateEvent)(nil)
_ event = (*updateConnectionsEvent)(nil)
)
type createConfigEvent struct {
mfThing string
owner string
name string
mfChannels []string
externalID string
content string
timestamp time.Time
}
func (cce createConfigEvent) encode() map[string]interface{} {
return map[string]interface{}{
"thing_id": cce.mfThing,
"owner": cce.owner,
"name": cce.name,
"channels": strings.Join(cce.mfChannels, ", "),
"external_id": cce.externalID,
"content": cce.content,
"timestamp": cce.timestamp.Unix(),
"operation": configCreate,
}
}
type updateConfigEvent struct {
mfThing string
name string
content string
timestamp time.Time
}
func (uce updateConfigEvent) encode() map[string]interface{} {
return map[string]interface{}{
"thing_id": uce.mfThing,
"name": uce.name,
"content": uce.content,
"timestamp": uce.timestamp.Unix(),
"operation": configUpdate,
}
}
type removeConfigEvent struct {
mfThing string
timestamp time.Time
}
func (rce removeConfigEvent) encode() map[string]interface{} {
return map[string]interface{}{
"thing_id": rce.mfThing,
"timestamp": rce.timestamp.Unix(),
"operation": configRemove,
}
}
type bootstrapEvent struct {
externalID string
success bool
timestamp time.Time
}
func (be bootstrapEvent) encode() map[string]interface{} {
return map[string]interface{}{
"external_id": be.externalID,
"success": be.success,
"timestamp": be.timestamp.Unix(),
"operation": thingBootstrap,
}
}
type changeStateEvent struct {
mfThing string
state bootstrap.State
timestamp time.Time
}
func (cse changeStateEvent) encode() map[string]interface{} {
return map[string]interface{}{
"thing_id": cse.mfThing,
"state": cse.state.String(),
"timestamp": cse.timestamp.Unix(),
"operation": thingStateChange,
}
}
type updateConnectionsEvent struct {
mfThing string
mfChannels []string
timestamp time.Time
}
func (uce updateConnectionsEvent) encode() map[string]interface{} {
return map[string]interface{}{
"thing_id": uce.mfThing,
"channels": strings.Join(uce.mfChannels, ", "),
"timestamp": uce.timestamp.Unix(),
"operation": thingUpdateConnections,
}
}
+53
View File
@@ -0,0 +1,53 @@
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
package producer_test
import (
"fmt"
"log"
"os"
"testing"
"github.com/go-redis/redis"
dockertest "github.com/ory/dockertest/v3"
)
const (
wrongID = 0
wrongValue = "wrong-value"
)
var redisClient *redis.Client
func TestMain(m *testing.M) {
pool, err := dockertest.NewPool("")
if err != nil {
log.Fatalf("Could not connect to docker: %s", err)
}
container, err := pool.Run("redis", "5.0-alpine", nil)
if err != nil {
log.Fatalf("Could not start container: %s", err)
}
if err := pool.Retry(func() error {
redisClient = redis.NewClient(&redis.Options{
Addr: fmt.Sprintf("localhost:%s", container.GetPort("6379/tcp")),
Password: "",
DB: 0,
})
return redisClient.Ping().Err()
}); err != nil {
log.Fatalf("Could not connect to docker: %s", err)
}
code := m.Run()
if err := pool.Purge(container); err != nil {
log.Fatalf("Could not purge container: %s", err)
}
os.Exit(code)
}
+178
View File
@@ -0,0 +1,178 @@
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
package producer
import (
"time"
"github.com/go-redis/redis"
"github.com/mainflux/mainflux/bootstrap"
)
const (
streamID = "mainflux.bootstrap"
streamLen = 1000
)
var _ bootstrap.Service = (*eventStore)(nil)
type eventStore struct {
svc bootstrap.Service
client *redis.Client
}
// NewEventStoreMiddleware returns wrapper around bootstrap service that sends
// events to event store.
func NewEventStoreMiddleware(svc bootstrap.Service, client *redis.Client) bootstrap.Service {
return eventStore{
svc: svc,
client: client,
}
}
func (es eventStore) Add(token string, cfg bootstrap.Config) (bootstrap.Config, error) {
saved, err := es.svc.Add(token, cfg)
if err != nil {
return saved, err
}
var channels []string
for _, ch := range saved.MFChannels {
channels = append(channels, ch.ID)
}
ev := createConfigEvent{
mfThing: saved.MFThing,
owner: saved.Owner,
name: saved.Name,
mfChannels: channels,
externalID: saved.ExternalID,
content: saved.Content,
timestamp: time.Now(),
}
es.add(ev)
return saved, err
}
func (es eventStore) View(token, id string) (bootstrap.Config, error) {
return es.svc.View(token, id)
}
func (es eventStore) Update(token string, cfg bootstrap.Config) error {
if err := es.svc.Update(token, cfg); err != nil {
return err
}
ev := updateConfigEvent{
mfThing: cfg.MFThing,
name: cfg.Name,
content: cfg.Content,
timestamp: time.Now(),
}
es.add(ev)
return nil
}
func (es eventStore) UpdateCert(token, thingKey, clientCert, clientKey, caCert string) error {
return es.svc.UpdateCert(token, thingKey, clientCert, clientKey, caCert)
}
func (es eventStore) UpdateConnections(token, id string, connections []string) error {
if err := es.svc.UpdateConnections(token, id, connections); err != nil {
return err
}
ev := updateConnectionsEvent{
mfThing: id,
mfChannels: connections,
timestamp: time.Now(),
}
es.add(ev)
return nil
}
func (es eventStore) List(token string, filter bootstrap.Filter, offset, limit uint64) (bootstrap.ConfigsPage, error) {
return es.svc.List(token, filter, offset, limit)
}
func (es eventStore) Remove(token, id string) error {
if err := es.svc.Remove(token, id); err != nil {
return err
}
ev := removeConfigEvent{
mfThing: id,
timestamp: time.Now(),
}
es.add(ev)
return nil
}
func (es eventStore) Bootstrap(externalKey, externalID string, secure bool) (bootstrap.Config, error) {
cfg, err := es.svc.Bootstrap(externalKey, externalID, secure)
ev := bootstrapEvent{
externalID: externalID,
timestamp: time.Now(),
success: true,
}
if err != nil {
ev.success = false
}
es.add(ev)
return cfg, err
}
func (es eventStore) ChangeState(token, id string, state bootstrap.State) error {
if err := es.svc.ChangeState(token, id, state); err != nil {
return err
}
ev := changeStateEvent{
mfThing: id,
state: state,
timestamp: time.Now(),
}
es.add(ev)
return nil
}
func (es eventStore) RemoveConfigHandler(id string) error {
return es.svc.RemoveConfigHandler(id)
}
func (es eventStore) RemoveChannelHandler(id string) error {
return es.svc.RemoveChannelHandler(id)
}
func (es eventStore) UpdateChannelHandler(channel bootstrap.Channel) error {
return es.UpdateChannelHandler(channel)
}
func (es eventStore) DisconnectThingHandler(channelID, thingID string) error {
return es.svc.DisconnectThingHandler(channelID, thingID)
}
func (es eventStore) add(ev event) error {
record := &redis.XAddArgs{
Stream: streamID,
MaxLenApprox: streamLen,
Values: ev.encode(),
}
return es.client.XAdd(record).Err()
}
+547
View File
@@ -0,0 +1,547 @@
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
package producer_test
import (
"fmt"
"net/http/httptest"
"strconv"
"strings"
"testing"
"time"
"github.com/go-redis/redis"
"github.com/mainflux/mainflux"
"github.com/mainflux/mainflux/pkg/errors"
"github.com/opentracing/opentracing-go/mocktracer"
"github.com/mainflux/mainflux/bootstrap"
"github.com/mainflux/mainflux/bootstrap/mocks"
"github.com/mainflux/mainflux/bootstrap/redis/producer"
mfsdk "github.com/mainflux/mainflux/pkg/sdk/go"
"github.com/mainflux/mainflux/things"
httpapi "github.com/mainflux/mainflux/things/api/things/http"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
const (
streamID = "mainflux.bootstrap"
email = "user@example.com"
validToken = "validToken"
channelsNum = 3
defaultTimout = 5
configPrefix = "config."
configCreate = configPrefix + "create"
configUpdate = configPrefix + "update"
configRemove = configPrefix + "remove"
thingPrefix = "thing."
thingStateChange = thingPrefix + "state_change"
thingBootstrap = thingPrefix + "bootstrap"
thingUpdateConnections = thingPrefix + "update_connections"
)
var (
encKey = []byte("1234567891011121")
channel = bootstrap.Channel{
ID: "1",
Name: "name",
Metadata: map[string]interface{}{"name": "value"},
}
config = bootstrap.Config{
ExternalID: "external_id",
ExternalKey: "external_key",
MFChannels: []bootstrap.Channel{channel},
Content: "config",
}
)
func newService(auth mainflux.AuthServiceClient, url string) bootstrap.Service {
configs := mocks.NewConfigsRepository()
config := mfsdk.Config{
BaseURL: url,
}
sdk := mfsdk.NewSDK(config)
return bootstrap.New(auth, configs, sdk, encKey)
}
func newThingsService(auth mainflux.AuthServiceClient) things.Service {
channels := make(map[string]things.Channel, channelsNum)
for i := 0; i < channelsNum; i++ {
id := strconv.Itoa(i + 1)
channels[id] = things.Channel{
ID: id,
Owner: email,
Metadata: map[string]interface{}{"meta": "data"},
}
}
return mocks.NewThingsService(map[string]things.Thing{}, channels, auth)
}
func newThingsServer(svc things.Service) *httptest.Server {
mux := httpapi.MakeHandler(mocktracer.New(), svc)
return httptest.NewServer(mux)
}
func TestAdd(t *testing.T) {
redisClient.FlushAll().Err()
users := mocks.NewUsersService(map[string]string{validToken: email})
server := newThingsServer(newThingsService(users))
svc := newService(users, server.URL)
svc = producer.NewEventStoreMiddleware(svc, redisClient)
var channels []string
for _, ch := range config.MFChannels {
channels = append(channels, ch.ID)
}
invalidConfig := config
invalidConfig.MFChannels = []bootstrap.Channel{bootstrap.Channel{ID: "empty"}}
cases := []struct {
desc string
config bootstrap.Config
token string
err error
event map[string]interface{}
}{
{
desc: "create config successfully",
config: config,
token: validToken,
err: nil,
event: map[string]interface{}{
"thing_id": "1",
"owner": email,
"name": config.Name,
"channels": strings.Join(channels, ", "),
"external_id": config.ExternalID,
"content": config.Content,
"timestamp": time.Now().Unix(),
"operation": configCreate,
},
},
{
desc: "create invalid config",
config: invalidConfig,
token: validToken,
err: bootstrap.ErrMalformedEntity,
event: nil,
},
}
lastID := "0"
for _, tc := range cases {
_, err := svc.Add(tc.token, tc.config)
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
streams := redisClient.XRead(&redis.XReadArgs{
Streams: []string{streamID, lastID},
Count: 1,
Block: time.Second,
}).Val()
var event map[string]interface{}
if len(streams) > 0 && len(streams[0].Messages) > 0 {
msg := streams[0].Messages[0]
event = msg.Values
lastID = msg.ID
}
test(t, tc.event, event, tc.desc)
}
}
func TestView(t *testing.T) {
users := mocks.NewUsersService(map[string]string{validToken: email})
server := newThingsServer(newThingsService(users))
svc := newService(users, server.URL)
saved, err := svc.Add(validToken, config)
require.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err))
svcConfig, svcErr := svc.View(validToken, saved.MFThing)
svc = producer.NewEventStoreMiddleware(svc, redisClient)
esConfig, esErr := svc.View(validToken, saved.MFThing)
assert.Equal(t, svcConfig, esConfig, fmt.Sprintf("event sourcing changed service behavior: expected %v got %v", svcConfig, esConfig))
assert.Equal(t, svcErr, esErr, fmt.Sprintf("event sourcing changed service behavior: expected %v got %v", svcErr, esErr))
}
func TestUpdate(t *testing.T) {
redisClient.FlushAll().Err()
users := mocks.NewUsersService(map[string]string{validToken: email})
server := newThingsServer(newThingsService(users))
svc := newService(users, server.URL)
svc = producer.NewEventStoreMiddleware(svc, redisClient)
c := config
ch := channel
ch.ID = "2"
c.MFChannels = append(c.MFChannels, ch)
saved, err := svc.Add(validToken, c)
require.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err))
redisClient.FlushAll().Err()
modified := saved
modified.Content = "new-config"
modified.Name = "new name"
nonExisting := config
nonExisting.MFThing = "unknown"
cases := []struct {
desc string
config bootstrap.Config
token string
err error
event map[string]interface{}
}{
{
desc: "update config successfully",
config: modified,
token: validToken,
err: nil,
event: map[string]interface{}{
"thing_id": modified.MFThing,
"name": modified.Name,
"content": modified.Content,
"timestamp": time.Now().Unix(),
"operation": configUpdate,
},
},
{
desc: "update non-existing config",
config: nonExisting,
token: validToken,
err: bootstrap.ErrNotFound,
event: nil,
},
}
lastID := "0"
for _, tc := range cases {
err := svc.Update(tc.token, tc.config)
assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
streams := redisClient.XRead(&redis.XReadArgs{
Streams: []string{streamID, lastID},
Count: 1,
Block: time.Second,
}).Val()
var event map[string]interface{}
if len(streams) > 0 && len(streams[0].Messages) > 0 {
msg := streams[0].Messages[0]
event = msg.Values
lastID = msg.ID
}
test(t, tc.event, event, tc.desc)
}
}
func TestUpdateConnections(t *testing.T) {
redisClient.FlushAll().Err()
users := mocks.NewUsersService(map[string]string{validToken: email})
server := newThingsServer(newThingsService(users))
svc := newService(users, server.URL)
svc = producer.NewEventStoreMiddleware(svc, redisClient)
saved, err := svc.Add(validToken, config)
require.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err))
redisClient.FlushAll().Err()
cases := []struct {
desc string
id string
token string
connections []string
err error
event map[string]interface{}
}{
{
desc: "update connections successfully",
id: saved.MFThing,
token: validToken,
connections: []string{"2"},
err: nil,
event: map[string]interface{}{
"thing_id": saved.MFThing,
"channels": "2",
"timestamp": time.Now().Unix(),
"operation": thingUpdateConnections,
},
},
{
desc: "update connections unsuccessfully",
id: saved.MFThing,
token: validToken,
connections: []string{"256"},
err: bootstrap.ErrMalformedEntity,
event: nil,
},
}
lastID := "0"
for _, tc := range cases {
err := svc.UpdateConnections(tc.token, tc.id, tc.connections)
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
streams := redisClient.XRead(&redis.XReadArgs{
Streams: []string{streamID, lastID},
Count: 1,
Block: time.Second,
}).Val()
var event map[string]interface{}
if len(streams) > 0 && len(streams[0].Messages) > 0 {
msg := streams[0].Messages[0]
event = msg.Values
lastID = msg.ID
}
test(t, tc.event, event, tc.desc)
}
}
func TestList(t *testing.T) {
users := mocks.NewUsersService(map[string]string{validToken: email})
server := newThingsServer(newThingsService(users))
svc := newService(users, server.URL)
_, err := svc.Add(validToken, config)
require.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err))
offset := uint64(0)
limit := uint64(10)
svcConfigs, svcErr := svc.List(validToken, bootstrap.Filter{}, offset, limit)
svc = producer.NewEventStoreMiddleware(svc, redisClient)
esConfigs, esErr := svc.List(validToken, bootstrap.Filter{}, offset, limit)
assert.Equal(t, svcConfigs, esConfigs, fmt.Sprintf("event sourcing changed service behavior: expected %v got %v", svcConfigs, esConfigs))
assert.Equal(t, svcErr, esErr, fmt.Sprintf("event sourcing changed service behavior: expected %v got %v", svcErr, esErr))
}
func TestRemove(t *testing.T) {
redisClient.FlushAll().Err()
users := mocks.NewUsersService(map[string]string{validToken: email})
server := newThingsServer(newThingsService(users))
svc := newService(users, server.URL)
svc = producer.NewEventStoreMiddleware(svc, redisClient)
c := config
saved, err := svc.Add(validToken, c)
require.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err))
redisClient.FlushAll().Err()
cases := []struct {
desc string
id string
token string
err error
event map[string]interface{}
}{
{
desc: "remove config successfully",
id: saved.MFThing,
token: validToken,
err: nil,
event: map[string]interface{}{
"thing_id": saved.MFThing,
"timestamp": time.Now().Unix(),
"operation": configRemove,
},
},
{
desc: "remove config with invalid credentials",
id: saved.MFThing,
token: "",
err: bootstrap.ErrUnauthorizedAccess,
event: nil,
},
}
lastID := "0"
for _, tc := range cases {
err := svc.Remove(tc.token, tc.id)
assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
streams := redisClient.XRead(&redis.XReadArgs{
Streams: []string{streamID, lastID},
Count: 1,
Block: time.Second,
}).Val()
var event map[string]interface{}
if len(streams) > 0 && len(streams[0].Messages) > 0 {
msg := streams[0].Messages[0]
event = msg.Values
lastID = msg.ID
}
test(t, tc.event, event, tc.desc)
}
}
func TestBootstrap(t *testing.T) {
redisClient.FlushAll().Err()
users := mocks.NewUsersService(map[string]string{validToken: email})
server := newThingsServer(newThingsService(users))
svc := newService(users, server.URL)
svc = producer.NewEventStoreMiddleware(svc, redisClient)
c := config
saved, err := svc.Add(validToken, c)
require.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err))
redisClient.FlushAll().Err()
cases := []struct {
desc string
externalID string
externalKey string
err error
event map[string]interface{}
}{
{
desc: "bootstrap successfully",
externalID: saved.ExternalID,
externalKey: saved.ExternalKey,
err: nil,
event: map[string]interface{}{
"external_id": saved.ExternalID,
"success": "1",
"timestamp": time.Now().Unix(),
"operation": thingBootstrap,
},
},
{
desc: "bootstrap with an error",
externalID: saved.ExternalID,
externalKey: "external",
err: bootstrap.ErrNotFound,
event: map[string]interface{}{
"external_id": saved.ExternalID,
"success": "0",
"timestamp": time.Now().Unix(),
"operation": thingBootstrap,
},
},
}
lastID := "0"
for _, tc := range cases {
_, err := svc.Bootstrap(tc.externalKey, tc.externalID, false)
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
streams := redisClient.XRead(&redis.XReadArgs{
Streams: []string{streamID, lastID},
Count: 1,
Block: time.Second,
}).Val()
var event map[string]interface{}
if len(streams) > 0 && len(streams[0].Messages) > 0 {
msg := streams[0].Messages[0]
event = msg.Values
lastID = msg.ID
}
test(t, tc.event, event, tc.desc)
}
}
func TestChangeState(t *testing.T) {
redisClient.FlushAll().Err()
users := mocks.NewUsersService(map[string]string{validToken: email})
server := newThingsServer(newThingsService(users))
svc := newService(users, server.URL)
svc = producer.NewEventStoreMiddleware(svc, redisClient)
c := config
saved, err := svc.Add(validToken, c)
require.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err))
redisClient.FlushAll().Err()
cases := []struct {
desc string
id string
token string
state bootstrap.State
err error
event map[string]interface{}
}{
{
desc: "change state to active",
id: saved.MFThing,
token: validToken,
state: bootstrap.Active,
err: nil,
event: map[string]interface{}{
"thing_id": saved.MFThing,
"state": bootstrap.Active.String(),
"timestamp": time.Now().Unix(),
"operation": thingStateChange,
},
},
{
desc: "change state invalid credentials",
id: saved.MFThing,
token: "",
state: bootstrap.Inactive,
err: bootstrap.ErrUnauthorizedAccess,
event: nil,
},
}
lastID := "0"
for _, tc := range cases {
err := svc.ChangeState(tc.token, tc.id, tc.state)
assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
streams := redisClient.XRead(&redis.XReadArgs{
Streams: []string{streamID, lastID},
Count: 1,
Block: time.Second,
}).Val()
var event map[string]interface{}
if len(streams) > 0 && len(streams[0].Messages) > 0 {
msg := streams[0].Messages[0]
event = msg.Values
lastID = msg.ID
}
test(t, tc.event, event, tc.desc)
}
}
func test(t *testing.T, expected, actual map[string]interface{}, description string) {
if expected != nil && actual != nil {
ts1 := expected["timestamp"].(int64)
ts2, err := strconv.ParseInt(actual["timestamp"].(string), 10, 64)
require.Nil(t, err, fmt.Sprintf("%s: expected to get a valid timestamp, got %s", description, err))
val := ts1 == ts2 || ts2 <= ts1+defaultTimout
assert.True(t, val, fmt.Sprintf("%s: timestamp is not in valid range", description))
delete(expected, "timestamp")
delete(actual, "timestamp")
assert.Equal(t, expected, actual, fmt.Sprintf("%s: expected %v got %v\n", description, expected, actual))
}
}
-148
View File
@@ -1,148 +0,0 @@
package redis
import (
"fmt"
"github.com/go-redis/redis"
"github.com/mainflux/mainflux/bootstrap"
"github.com/mainflux/mainflux/logger"
)
const (
stream = "mainflux.things"
group = "mainflux.bootstrap"
thingPrefix = "thing."
thingRemove = thingPrefix + "remove"
thingDisconnect = thingPrefix + "disconnect"
channelPrefix = "channel."
channelUpdate = channelPrefix + "update"
channelRemove = channelPrefix + "remove"
exists = "BUSYGROUP Consumer Group name already exists"
)
// EventStore represents event source for things and channels provisioning.
type EventStore interface {
// Subscribes to given subject and receives events.
Subscribe(string) error
}
type eventStore struct {
svc bootstrap.Service
client *redis.Client
consumer string
logger logger.Logger
}
// NewEventStore returns new event store instance.
func NewEventStore(svc bootstrap.Service, client *redis.Client, consumer string, log logger.Logger) EventStore {
return eventStore{
svc: svc,
client: client,
consumer: consumer,
logger: log,
}
}
func (es eventStore) Subscribe(subject string) error {
err := es.client.XGroupCreateMkStream(stream, group, "$").Err()
if err != nil && err.Error() != exists {
return err
}
for {
streams, err := es.client.XReadGroup(&redis.XReadGroupArgs{
Group: group,
Consumer: es.consumer,
Streams: []string{stream, ">"},
Count: 100,
}).Result()
if err != nil || len(streams) == 0 {
continue
}
for _, msg := range streams[0].Messages {
event := msg.Values
var err error
switch event["operation"] {
case thingRemove:
rte := decodeRemoveThing(event)
err = es.handleRemoveThing(rte)
case thingDisconnect:
dte := decodeDisconnectThing(event)
err = es.handleDisconnectThing(dte)
case channelUpdate:
uce := decodeUpdateChannel(event)
err = es.handleUpdateChannel(uce)
case channelRemove:
rce := decodeRemoveChannel(event)
err = es.handleRemoveChannel(rce)
}
if err != nil {
es.logger.Warn(fmt.Sprintf("Failed to handle event sourcing: %s", err.Error()))
break
}
es.client.XAck(stream, group, msg.ID)
}
}
}
func decodeRemoveThing(event map[string]interface{}) removeEvent {
return removeEvent{
id: read(event, "id", ""),
}
}
func decodeUpdateChannel(event map[string]interface{}) updateChannelEvent {
return updateChannelEvent{
id: read(event, "id", ""),
name: read(event, "name", ""),
metadata: read(event, "metadata", ""),
}
}
func decodeRemoveChannel(event map[string]interface{}) removeEvent {
return removeEvent{
id: read(event, "id", ""),
}
}
func decodeDisconnectThing(event map[string]interface{}) disconnectEvent {
return disconnectEvent{
channelID: read(event, "chan_id", ""),
thingID: read(event, "thing_id", ""),
}
}
func (es eventStore) handleRemoveThing(rte removeEvent) error {
return es.svc.RemoveConfigHandler(rte.id)
}
func (es eventStore) handleUpdateChannel(uce updateChannelEvent) error {
channel := bootstrap.Channel{
ID: uce.id,
Name: uce.name,
Metadata: uce.metadata,
}
return es.svc.UpdateChannelHandler(channel)
}
func (es eventStore) handleRemoveChannel(rce removeEvent) error {
return es.svc.RemoveChannelHandler(rce.id)
}
func (es eventStore) handleDisconnectThing(dte disconnectEvent) error {
return es.svc.DisconnectThingHandler(dte.channelID, dte.thingID)
}
func read(event map[string]interface{}, key, def string) string {
val, ok := event[key].(string)
if !ok {
return def
}
return val
}
+219 -166
View File
@@ -1,25 +1,18 @@
//
// Copyright (c) 2018
// Mainflux
//
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
//
package bootstrap
import (
"context"
"encoding/json"
"errors"
"crypto/aes"
"crypto/cipher"
"encoding/hex"
"time"
"github.com/mainflux/mainflux"
mfsdk "github.com/mainflux/mainflux/sdk/go"
)
const (
thingType = "device"
chanName = "channel"
"github.com/mainflux/mainflux/pkg/errors"
mfsdk "github.com/mainflux/mainflux/pkg/sdk/go"
)
var (
@@ -38,7 +31,30 @@ var (
// ErrThings indicates failure to communicate with Mainflux Things service.
// It can be due to networking error or invalid/unauthorized request.
ErrThings = errors.New("error receiving response from Things service")
ErrThings = errors.New("failed to receive response from Things service")
// ErrExternalKeyNotFound indicates a non-existent bootstrap configuration for given external key
ErrExternalKeyNotFound = errors.New("failed to get bootstrap configuration for given external key")
// ErrSecureBootstrap indicates error in getting bootstrap configuration for given encrypted external key
ErrSecureBootstrap = errors.New("failed to get bootstrap configuration for given encrypted external key")
// ErrBootstrap indicates error in getting bootstrap configuration.
ErrBootstrap = errors.New("failed to read bootstrap configuration")
errAddBootstrap = errors.New("failed to add bootstrap configuration")
errUpdateConnections = errors.New("failed to update connections")
errRemoveBootstrap = errors.New("failed to remove bootstrap configuration")
errChangeState = errors.New("failed to change state of bootstrap configuration")
errUpdateChannel = errors.New("failed to update channel")
errRemoveConfig = errors.New("failed to remove bootstrap configuration")
errRemoveChannel = errors.New("failed to remove channel")
errCreateThing = errors.New("failed to create thing")
errDisconnectThing = errors.New("failed to disconnect thing")
errThingNotFound = errors.New("thing not found")
errCheckChannels = errors.New("failed to check if channels exists")
errConnectionChannels = errors.New("failed to check channels connections")
errUpdateCert = errors.New("failed to update cert")
)
var _ Service = (*bootstrapService)(nil)
@@ -46,45 +62,49 @@ var _ Service = (*bootstrapService)(nil)
// Service specifies an API that must be fulfilled by the domain service
// implementation, and all of its decorators (e.g. logging & metrics).
type Service interface {
// Add adds new Thing Config to the user identified by the provided key.
Add(string, Config) (Config, error)
// Add adds new Thing Config to the user identified by the provided token.
Add(token string, cfg Config) (Config, error)
// View returns Thing Config with given ID belonging to the user identified by the given key.
View(string, string) (Config, error)
// View returns Thing Config with given ID belonging to the user identified by the given token.
View(token, id string) (Config, error)
// Update updates editable fields of the provided Config.
Update(string, Config) error
Update(token string, cfg Config) error
// UpdateCert updates an existing Config certificate and token.
// A non-nil error is returned to indicate operation failure.
UpdateCert(token, thingID, clientCert, clientKey, caCert string) error
// UpdateConnections updates list of Channels related to given Config.
UpdateConnections(string, string, []string) error
UpdateConnections(token, id string, connections []string) error
// List returns subset of Configs with given search params that belong to the
// user identified by the given key.
List(string, Filter, uint64, uint64) (ConfigsPage, error)
// user identified by the given token.
List(token string, filter Filter, offset, limit uint64) (ConfigsPage, error)
// Remove removes Config with specified key that belongs to the user identified by the given key.
Remove(string, string) error
// Remove removes Config with specified token that belongs to the user identified by the given token.
Remove(token, id string) error
// Bootstrap returns Config to the Thing with provided external ID using external key.
Bootstrap(string, string) (Config, error)
Bootstrap(externalKey, externalID string, secure bool) (Config, error)
// ChangeState changes state of the Thing with given ID and owner.
ChangeState(string, string, State) error
ChangeState(token, id string, state State) error
// Methods RemoveConfig, UpdateChannel, and RemoveChannel are used as
// handlers for events. That's why these methods surpass ownership check.
// RemoveConfigHandler removes Configuration with id received from an event.
RemoveConfigHandler(string) error
// UpdateChannelHandler updates Channel with data received from an event.
UpdateChannelHandler(Channel) error
UpdateChannelHandler(channel Channel) error
// RemoveConfigHandler removes Configuration with id received from an event.
RemoveConfigHandler(id string) error
// RemoveChannelHandler removes Channel with id received from an event.
RemoveChannelHandler(string) error
RemoveChannelHandler(id string) error
// DisconnectHandler changes state of the Config when connect/disconnect event occurs.
DisconnectThingHandler(string, string) error
DisconnectThingHandler(channelID, thingID string) error
}
// ConfigReader is used to parse Config into format which will be encoded
@@ -92,62 +112,66 @@ type Service interface {
// is to provide convenient way to generate custom configuration response
// based on the specific Config which will be consumed by the client.
type ConfigReader interface {
ReadConfig(Config) (mainflux.Response, error)
ReadConfig(Config, bool) (interface{}, error)
}
type bootstrapService struct {
users mainflux.UsersServiceClient
auth mainflux.AuthServiceClient
configs ConfigRepository
sdk mfsdk.SDK
encKey []byte
reader ConfigReader
}
// New returns new Bootstrap service.
func New(users mainflux.UsersServiceClient, configs ConfigRepository, sdk mfsdk.SDK) Service {
func New(auth mainflux.AuthServiceClient, configs ConfigRepository, sdk mfsdk.SDK, encKey []byte) Service {
return &bootstrapService{
configs: configs,
sdk: sdk,
users: users,
auth: auth,
encKey: encKey,
}
}
func (bs bootstrapService) Add(key string, cfg Config) (Config, error) {
owner, err := bs.identify(key)
func (bs bootstrapService) Add(token string, cfg Config) (Config, error) {
owner, err := bs.identify(token)
if err != nil {
return Config{}, err
}
toConnect := bs.toIDList(cfg.MFChannels)
// Check if channels exist. This is the way to prevent invalid configuration to be saved.
// Check if channels exist. This is the way to prevent fetching channels that already exist.
existing, err := bs.configs.ListExisting(owner, toConnect)
if err != nil {
return Config{}, err
return Config{}, errors.Wrap(errCheckChannels, err)
}
cfg.MFChannels, err = bs.connectionChannels(toConnect, bs.toIDList(existing), key)
cfg.MFChannels, err = bs.connectionChannels(toConnect, bs.toIDList(existing), token)
if err != nil {
return Config{}, err
return Config{}, errors.Wrap(errConnectionChannels, err)
}
id := cfg.MFThing
mfThing, err := bs.thing(key, id)
mfThing, err := bs.thing(token, id)
if err != nil {
return Config{}, err
return Config{}, errors.Wrap(errAddBootstrap, err)
}
cfg.MFThing = mfThing.ID
cfg.Owner = owner
cfg.State = Inactive
cfg.MFKey = mfThing.Key
saved, err := bs.configs.Save(cfg, toConnect)
saved, err := bs.configs.Save(cfg, toConnect)
if err != nil {
if id == "" {
// Fail silently.
bs.sdk.DeleteThing(cfg.MFThing, key)
if errT := bs.sdk.DeleteThing(cfg.MFThing, token); errT != nil {
err = errors.Wrap(err, errT)
}
}
return Config{}, err
return Config{}, errors.Wrap(errAddBootstrap, err)
}
cfg.MFThing = saved
@@ -156,30 +180,17 @@ func (bs bootstrapService) Add(key string, cfg Config) (Config, error) {
return cfg, nil
}
func (bs bootstrapService) View(key, id string) (Config, error) {
owner, err := bs.identify(key)
func (bs bootstrapService) View(token, id string) (Config, error) {
owner, err := bs.identify(token)
if err != nil {
return Config{}, err
}
cfg, err := bs.configs.RetrieveByID(owner, id)
if err != nil {
return Config{}, err
}
for i, ch := range cfg.MFChannels {
if meta, ok := ch.Metadata.([]byte); ok {
if err := json.Unmarshal(meta, &cfg.MFChannels[i].Metadata); err != nil {
return Config{}, err
}
}
}
return cfg, nil
return bs.configs.RetrieveByID(owner, id)
}
func (bs bootstrapService) Update(key string, cfg Config) error {
owner, err := bs.identify(key)
func (bs bootstrapService) Update(token string, cfg Config) error {
owner, err := bs.identify(token)
if err != nil {
return err
}
@@ -189,21 +200,39 @@ func (bs bootstrapService) Update(key string, cfg Config) error {
return bs.configs.Update(cfg)
}
func (bs bootstrapService) UpdateConnections(key, id string, connections []string) error {
owner, err := bs.identify(key)
func (bs bootstrapService) UpdateCert(token, thingID, clientCert, clientKey, caCert string) error {
owner, err := bs.identify(token)
if err != nil {
return err
}
if err := bs.configs.UpdateCert(owner, thingID, clientCert, clientKey, caCert); err != nil {
return errors.Wrap(errUpdateCert, err)
}
return nil
}
func (bs bootstrapService) UpdateConnections(token, id string, connections []string) error {
owner, err := bs.identify(token)
if err != nil {
return err
}
cfg, err := bs.configs.RetrieveByID(owner, id)
if err != nil {
return err
return errors.Wrap(errUpdateConnections, err)
}
add, remove := bs.updateList(cfg, connections)
channels, err := bs.updateChannels(add, key)
// Check if channels exist. This is the way to prevent fetching channels that already exist.
existing, err := bs.configs.ListExisting(owner, connections)
if err != nil {
return err
return errors.Wrap(errUpdateConnections, err)
}
channels, err := bs.connectionChannels(connections, bs.toIDList(existing), token)
if err != nil {
return errors.Wrap(errUpdateConnections, err)
}
cfg.MFChannels = channels
@@ -215,8 +244,8 @@ func (bs bootstrapService) UpdateConnections(key, id string, connections []strin
}
for _, c := range disconnect {
if err := bs.sdk.DisconnectThing(id, c, key); err != nil {
if err == mfsdk.ErrNotFound {
if err := bs.sdk.DisconnectThing(id, c, token); err != nil {
if errors.Contains(err, mfsdk.ErrFailedDisconnect) {
continue
}
return ErrThings
@@ -224,8 +253,12 @@ func (bs bootstrapService) UpdateConnections(key, id string, connections []strin
}
for _, c := range connect {
if err := bs.sdk.ConnectThing(id, c, key); err != nil {
if err == mfsdk.ErrNotFound {
conIDs := mfsdk.ConnectionIDs{
ChannelIDs: []string{c},
ThingIDs: []string{id},
}
if err := bs.sdk.Connect(conIDs, token); err != nil {
if errors.Contains(err, mfsdk.ErrFailedConnect) {
return ErrMalformedEntity
}
return ErrThings
@@ -235,140 +268,188 @@ func (bs bootstrapService) UpdateConnections(key, id string, connections []strin
return bs.configs.UpdateConnections(owner, id, channels, connections)
}
func (bs bootstrapService) List(key string, filter Filter, offset, limit uint64) (ConfigsPage, error) {
owner, err := bs.identify(key)
func (bs bootstrapService) List(token string, filter Filter, offset, limit uint64) (ConfigsPage, error) {
owner, err := bs.identify(token)
if err != nil {
return ConfigsPage{}, err
}
if filter.Unknown {
return bs.configs.RetrieveUnknown(offset, limit), nil
}
return bs.configs.RetrieveAll(owner, filter, offset, limit), nil
}
func (bs bootstrapService) Remove(key, id string) error {
owner, err := bs.identify(key)
func (bs bootstrapService) Remove(token, id string) error {
owner, err := bs.identify(token)
if err != nil {
return err
}
return bs.configs.Remove(owner, id)
if err := bs.configs.Remove(owner, id); err != nil {
return errors.Wrap(errRemoveBootstrap, err)
}
return nil
}
func (bs bootstrapService) Bootstrap(externalKey, externalID string) (Config, error) {
cfg, err := bs.configs.RetrieveByExternalID(externalKey, externalID)
func (bs bootstrapService) Bootstrap(externalKey, externalID string, secure bool) (Config, error) {
cfg, err := bs.configs.RetrieveByExternalID(externalID)
if err != nil {
if err == ErrNotFound {
bs.configs.SaveUnknown(externalKey, externalID)
}
return Config{}, ErrNotFound
return cfg, errors.Wrap(ErrBootstrap, err)
}
for i, ch := range cfg.MFChannels {
if meta, ok := ch.Metadata.([]byte); ok {
if err := json.Unmarshal(meta, &cfg.MFChannels[i].Metadata); err != nil {
return Config{}, err
}
if secure {
dec, err := bs.dec(externalKey)
if err != nil {
return Config{}, errors.Wrap(ErrSecureBootstrap, err)
}
externalKey = dec
}
if cfg.ExternalKey != externalKey {
return Config{}, errors.Wrap(ErrExternalKeyNotFound, ErrNotFound)
}
return cfg, nil
}
func (bs bootstrapService) ChangeState(key, id string, state State) error {
owner, err := bs.identify(key)
func (bs bootstrapService) ChangeState(token, id string, state State) error {
owner, err := bs.identify(token)
if err != nil {
return err
}
thing, err := bs.configs.RetrieveByID(owner, id)
cfg, err := bs.configs.RetrieveByID(owner, id)
if err != nil {
return err
return errors.Wrap(errChangeState, err)
}
if thing.State == state {
if cfg.State == state {
return nil
}
switch state {
case Active:
for _, c := range thing.MFChannels {
if err := bs.sdk.ConnectThing(thing.MFThing, c.ID, key); err != nil {
for _, c := range cfg.MFChannels {
conIDs := mfsdk.ConnectionIDs{
ChannelIDs: []string{c.ID},
ThingIDs: []string{cfg.MFThing},
}
if err := bs.sdk.Connect(conIDs, token); err != nil {
return ErrThings
}
}
case Inactive:
for _, c := range thing.MFChannels {
if err := bs.sdk.DisconnectThing(thing.MFThing, c.ID, key); err != nil {
if err == mfsdk.ErrNotFound {
for _, c := range cfg.MFChannels {
if err := bs.sdk.DisconnectThing(cfg.MFThing, c.ID, token); err != nil {
if errors.Contains(err, mfsdk.ErrFailedDisconnect) {
continue
}
return ErrThings
}
}
}
return bs.configs.ChangeState(owner, id, state)
if err := bs.configs.ChangeState(owner, id, state); err != nil {
return errors.Wrap(errChangeState, err)
}
return nil
}
func (bs bootstrapService) UpdateChannelHandler(channel Channel) error {
return bs.configs.UpdateChannel(channel)
if err := bs.configs.UpdateChannel(channel); err != nil {
return errors.Wrap(errUpdateChannel, err)
}
return nil
}
func (bs bootstrapService) RemoveConfigHandler(id string) error {
return bs.configs.RemoveThing(id)
if err := bs.configs.RemoveThing(id); err != nil {
return errors.Wrap(errRemoveConfig, err)
}
return nil
}
func (bs bootstrapService) RemoveChannelHandler(id string) error {
return bs.configs.RemoveChannel(id)
if err := bs.configs.RemoveChannel(id); err != nil {
return errors.Wrap(errRemoveChannel, err)
}
return nil
}
func (bs bootstrapService) DisconnectThingHandler(channelID, thingID string) error {
return bs.configs.DisconnectThing(channelID, thingID)
if err := bs.configs.DisconnectThing(channelID, thingID); err != nil {
return errors.Wrap(errDisconnectThing, err)
}
return nil
}
func (bs bootstrapService) identify(token string) (string, error) {
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
res, err := bs.users.Identify(ctx, &mainflux.Token{Value: token})
res, err := bs.auth.Identify(ctx, &mainflux.Token{Value: token})
if err != nil {
return "", ErrUnauthorizedAccess
}
return res.GetValue(), nil
return res.GetEmail(), nil
}
// Method thing retrieves Mainflux Thing creating one if an empty ID is passed.
func (bs bootstrapService) thing(key, id string) (mfsdk.Thing, error) {
func (bs bootstrapService) thing(token, id string) (mfsdk.Thing, error) {
thingID := id
var err error
if id == "" {
thingID, err = bs.sdk.CreateThing(mfsdk.Thing{Type: thingType}, key)
thingID, err = bs.sdk.CreateThing(mfsdk.Thing{}, token)
if err != nil {
return mfsdk.Thing{}, err
return mfsdk.Thing{}, errors.Wrap(errCreateThing, err)
}
}
thing, err := bs.sdk.Thing(thingID, key)
thing, err := bs.sdk.Thing(thingID, token)
if err != nil {
if err == mfsdk.ErrNotFound {
return mfsdk.Thing{}, ErrNotFound
if errors.Contains(err, mfsdk.ErrFailedFetch) {
return mfsdk.Thing{}, errors.Wrap(errThingNotFound, ErrNotFound)
}
if id != "" {
bs.sdk.DeleteThing(thingID, key)
if errT := bs.sdk.DeleteThing(thingID, token); errT != nil {
err = errors.Wrap(err, errT)
}
}
return mfsdk.Thing{}, ErrThings
return mfsdk.Thing{}, errors.Wrap(ErrThings, err)
}
return thing, nil
}
func (bs bootstrapService) connectionChannels(channels, existing []string, token string) ([]Channel, error) {
add := make(map[string]bool, len(channels))
for _, ch := range channels {
add[ch] = true
}
for _, ch := range existing {
if add[ch] == true {
delete(add, ch)
}
}
var ret []Channel
for id := range add {
ch, err := bs.sdk.Channel(id, token)
if err != nil {
return nil, errors.Wrap(ErrMalformedEntity, err)
}
ret = append(ret, Channel{
ID: ch.ID,
Name: ch.Name,
Metadata: ch.Metadata,
})
}
return ret, nil
}
// Method updateList accepts config and channel IDs and returns three lists:
// 1) IDs of Channels to be added
// 2) IDs of Channels to be removed
@@ -406,49 +487,21 @@ func (bs bootstrapService) toIDList(channels []Channel) []string {
return ret
}
func (bs bootstrapService) updateChannels(add []string, key string) ([]Channel, error) {
var ret []Channel
for _, id := range add {
ch, err := bs.sdk.Channel(id, key)
if err != nil {
return []Channel{}, ErrMalformedEntity
}
ret = append(ret, Channel{
ID: ch.ID,
Name: ch.Name,
Metadata: ch.Metadata,
})
func (bs bootstrapService) dec(in string) (string, error) {
ciphertext, err := hex.DecodeString(in)
if err != nil {
return "", ErrNotFound
}
return ret, nil
}
func (bs bootstrapService) connectionChannels(channels, existing []string, key string) ([]Channel, error) {
add := make(map[string]bool, len(channels))
for _, ch := range channels {
add[ch] = true
}
for _, ch := range existing {
if add[ch] == true {
delete(add, ch)
}
}
var ret []Channel
for id := range add {
ch, err := bs.sdk.Channel(id, key)
if err != nil {
return nil, ErrMalformedEntity
}
ret = append(ret, Channel{
ID: ch.ID,
Name: ch.Name,
Metadata: ch.Metadata,
})
}
return ret, nil
block, err := aes.NewCipher(bs.encKey)
if err != nil {
return "", err
}
if len(ciphertext) < aes.BlockSize {
return "", ErrMalformedEntity
}
iv := ciphertext[:aes.BlockSize]
ciphertext = ciphertext[aes.BlockSize:]
stream := cipher.NewCFBDecrypter(block, iv)
stream.XORKeyStream(ciphertext, ciphertext)
return string(ciphertext), nil
}
+204 -128
View File
@@ -1,25 +1,29 @@
//
// Copyright (c) 2018
// Mainflux
//
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
//
package bootstrap_test
import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"encoding/hex"
"fmt"
"io"
"net/http/httptest"
"strconv"
"testing"
"github.com/opentracing/opentracing-go/mocktracer"
"github.com/gofrs/uuid"
"github.com/mainflux/mainflux"
"github.com/mainflux/mainflux/bootstrap"
"github.com/mainflux/mainflux/bootstrap/mocks"
mfsdk "github.com/mainflux/mainflux/sdk/go"
"github.com/mainflux/mainflux/pkg/errors"
mfsdk "github.com/mainflux/mainflux/pkg/sdk/go"
"github.com/mainflux/mainflux/things"
httpapi "github.com/mainflux/mainflux/things/api/http"
uuid "github.com/satori/go.uuid"
httpapi "github.com/mainflux/mainflux/things/api/things/http"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@@ -29,16 +33,16 @@ const (
invalidToken = "invalidToken"
email = "test@example.com"
unknown = "unknown"
unknownID = "1"
unknownKey = "2"
channelsNum = 3
)
var (
encKey = []byte("1234567891011121")
channel = bootstrap.Channel{
ID: "1",
Name: "name",
Metadata: `{"name":"value"}`,
Metadata: map[string]interface{}{"name": "value"},
}
config = bootstrap.Config{
@@ -49,35 +53,50 @@ var (
}
)
func newService(users mainflux.UsersServiceClient, url string) bootstrap.Service {
things := mocks.NewConfigsRepository(map[string]string{unknownID: unknownKey})
func newService(auth mainflux.AuthServiceClient, url string) bootstrap.Service {
things := mocks.NewConfigsRepository()
config := mfsdk.Config{
BaseURL: url,
}
sdk := mfsdk.NewSDK(config)
return bootstrap.New(users, things, sdk)
return bootstrap.New(auth, things, sdk, encKey)
}
func newThingsService(users mainflux.UsersServiceClient) things.Service {
func newThingsService(auth mainflux.AuthServiceClient) things.Service {
channels := make(map[string]things.Channel, channelsNum)
for i := 0; i < channelsNum; i++ {
id := strconv.Itoa(i + 1)
channels[id] = things.Channel{
ID: id,
Owner: email,
Metadata: `{"meta":"data"}`,
Metadata: map[string]interface{}{"meta": "data"},
}
}
return mocks.NewThingsService(map[string]things.Thing{}, channels, users)
return mocks.NewThingsService(map[string]things.Thing{}, channels, auth)
}
func newThingsServer(svc things.Service) *httptest.Server {
mux := httpapi.MakeHandler(svc)
mux := httpapi.MakeHandler(mocktracer.New(), svc)
return httptest.NewServer(mux)
}
func enc(in []byte) ([]byte, error) {
block, err := aes.NewCipher(encKey)
if err != nil {
return nil, err
}
ciphertext := make([]byte, aes.BlockSize+len(in))
iv := ciphertext[:aes.BlockSize]
if _, err := io.ReadFull(rand.Reader, iv); err != nil {
return nil, err
}
stream := cipher.NewCFBEncrypter(block, iv)
stream.XORKeyStream(ciphertext[aes.BlockSize:], in)
return ciphertext, nil
}
func TestAdd(t *testing.T) {
users := mocks.NewUsersService(map[string]string{validToken: email})
@@ -95,38 +114,38 @@ func TestAdd(t *testing.T) {
cases := []struct {
desc string
config bootstrap.Config
key string
token string
err error
}{
{
desc: "add a new config",
config: config,
key: validToken,
token: validToken,
err: nil,
},
{
desc: "add a config with an invalid ID",
config: neID,
key: validToken,
token: validToken,
err: bootstrap.ErrNotFound,
},
{
desc: "add a config with wrong credentials",
config: config,
key: invalidToken,
token: invalidToken,
err: bootstrap.ErrUnauthorizedAccess,
},
{
desc: "add a config with invalid list of channels",
config: wrongChannels,
key: validToken,
token: validToken,
err: bootstrap.ErrMalformedEntity,
},
}
for _, tc := range cases {
_, err := svc.Add(tc.key, tc.config)
assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
_, err := svc.Add(tc.token, tc.config)
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
}
}
@@ -140,34 +159,34 @@ func TestView(t *testing.T) {
require.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err))
cases := []struct {
desc string
id string
key string
err error
desc string
id string
token string
err error
}{
{
desc: "view an existing config",
id: saved.MFThing,
key: validToken,
err: nil,
desc: "view an existing config",
id: saved.MFThing,
token: validToken,
err: nil,
},
{
desc: "view a non-existing config",
id: unknown,
key: validToken,
err: bootstrap.ErrNotFound,
desc: "view a non-existing config",
id: unknown,
token: validToken,
err: bootstrap.ErrNotFound,
},
{
desc: "view a config with wrong credentials",
id: config.MFThing,
key: invalidToken,
err: bootstrap.ErrUnauthorizedAccess,
desc: "view a config with wrong credentials",
id: config.MFThing,
token: invalidToken,
err: bootstrap.ErrUnauthorizedAccess,
},
}
for _, tc := range cases {
_, err := svc.View(tc.key, tc.id)
assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
_, err := svc.View(tc.token, tc.id)
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
}
}
@@ -194,32 +213,90 @@ func TestUpdate(t *testing.T) {
cases := []struct {
desc string
config bootstrap.Config
key string
token string
err error
}{
{
desc: "update a config with state Created",
config: modifiedCreated,
key: validToken,
token: validToken,
err: nil,
},
{
desc: "update a non-existing config",
config: nonExisting,
key: validToken,
token: validToken,
err: bootstrap.ErrNotFound,
},
{
desc: "update a config with wrong credentials",
config: saved,
key: invalidToken,
token: invalidToken,
err: bootstrap.ErrUnauthorizedAccess,
},
}
for _, tc := range cases {
err := svc.Update(tc.key, tc.config)
assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
err := svc.Update(tc.token, tc.config)
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
}
}
func TestUpdateCert(t *testing.T) {
users := mocks.NewUsersService(map[string]string{validToken: email})
server := newThingsServer(newThingsService(users))
svc := newService(users, server.URL)
c := config
ch := channel
ch.ID = "2"
c.MFChannels = append(c.MFChannels, ch)
saved, err := svc.Add(validToken, c)
require.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err))
cases := []struct {
desc string
token string
thingKey string
clientCert string
clientKey string
caCert string
err error
}{
{
desc: "update certs for the valid config",
thingKey: saved.MFKey,
clientCert: "newCert",
clientKey: "newKey",
caCert: "newCert",
token: validToken,
err: nil,
},
{
desc: "update cert for a non-existing config",
thingKey: "empty",
clientCert: "newCert",
clientKey: "newKey",
caCert: "newCert",
token: validToken,
err: bootstrap.ErrNotFound,
},
{
desc: "update config cert with wrong credentials",
thingKey: saved.MFKey,
clientCert: "newCert",
clientKey: "newKey",
caCert: "newCert",
token: invalidToken,
err: bootstrap.ErrUnauthorizedAccess,
},
}
for _, tc := range cases {
err := svc.UpdateCert(tc.token, tc.thingKey, tc.clientCert, tc.clientKey, tc.caCert)
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
}
}
@@ -236,7 +313,9 @@ func TestUpdateConnections(t *testing.T) {
created, err := svc.Add(validToken, c)
require.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err))
c.ExternalID = uuid.NewV4().String()
externalID, err := uuid.NewV4()
require.Nil(t, err, fmt.Sprintf("Got unexpected error: %s.\n", err))
c.ExternalID = externalID.String()
active, err := svc.Add(validToken, c)
require.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err))
err = svc.ChangeState(validToken, active.MFThing, bootstrap.Active)
@@ -247,42 +326,42 @@ func TestUpdateConnections(t *testing.T) {
cases := []struct {
desc string
key string
token string
id string
connections []string
err error
}{
{
desc: "update connections for config with state Created",
key: validToken,
desc: "update connections for config with state Inactive",
token: validToken,
id: created.MFThing,
connections: []string{"2"},
err: nil,
},
{
desc: "update connections for config with state Active",
key: validToken,
token: validToken,
id: active.MFThing,
connections: []string{"3"},
err: nil,
},
{
desc: "update connections for non-existing config",
key: validToken,
token: validToken,
id: "",
connections: []string{"3"},
err: bootstrap.ErrNotFound,
},
{
desc: "update connections with invalid channels",
key: validToken,
token: validToken,
id: created.MFThing,
connections: []string{"wrong"},
err: bootstrap.ErrMalformedEntity,
},
{
desc: "update connections a config with wrong credentials",
key: invalidToken,
token: invalidToken,
id: created.MFKey,
connections: []string{"2", "3"},
err: bootstrap.ErrUnauthorizedAccess,
@@ -290,8 +369,8 @@ func TestUpdateConnections(t *testing.T) {
}
for _, tc := range cases {
err := svc.UpdateConnections(tc.key, tc.id, tc.connections)
assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
err := svc.UpdateConnections(tc.token, tc.id, tc.connections)
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
}
}
@@ -305,9 +384,10 @@ func TestList(t *testing.T) {
var saved []bootstrap.Config
for i := 0; i < numThings; i++ {
c := config
id := uuid.NewV4().String()
c.ExternalID = id
c.ExternalKey = id
id, err := uuid.NewV4()
require.Nil(t, err, fmt.Sprintf("Got unexpected error: %s.\n", err))
c.ExternalID = id.String()
c.ExternalKey = id.String()
c.Name = fmt.Sprintf("%s-%d", config.Name, i)
s, err := svc.Add(validToken, c)
saved = append(saved, s)
@@ -318,18 +398,13 @@ func TestList(t *testing.T) {
require.Nil(t, err, fmt.Sprintf("Changing config state expected to succeed: %s.\n", err))
saved[41].State = bootstrap.Active
unknownConfig := bootstrap.Config{
ExternalID: unknownID,
ExternalKey: unknownKey,
}
cases := []struct {
desc string
config bootstrap.ConfigsPage
filter bootstrap.Filter
offset uint64
limit uint64
key string
token string
err error
}{
{
@@ -341,7 +416,7 @@ func TestList(t *testing.T) {
Configs: saved[0:10],
},
filter: bootstrap.Filter{},
key: validToken,
token: validToken,
offset: 0,
limit: 10,
err: nil,
@@ -355,7 +430,7 @@ func TestList(t *testing.T) {
Configs: saved[95:96],
},
filter: bootstrap.Filter{PartialMatch: map[string]string{"name": "95"}},
key: validToken,
token: validToken,
offset: 0,
limit: 100,
err: nil,
@@ -364,7 +439,7 @@ func TestList(t *testing.T) {
desc: "list configs unauthorized",
config: bootstrap.ConfigsPage{},
filter: bootstrap.Filter{},
key: invalidToken,
token: invalidToken,
offset: 0,
limit: 10,
err: bootstrap.ErrUnauthorizedAccess,
@@ -378,7 +453,7 @@ func TestList(t *testing.T) {
Configs: saved[95:],
},
filter: bootstrap.Filter{},
key: validToken,
token: validToken,
offset: 95,
limit: 10,
err: nil,
@@ -392,32 +467,18 @@ func TestList(t *testing.T) {
Configs: []bootstrap.Config{saved[41]},
},
filter: bootstrap.Filter{FullMatch: map[string]string{"state": bootstrap.Active.String()}},
key: validToken,
token: validToken,
offset: 35,
limit: 20,
err: nil,
},
{
desc: "list unknown configs",
config: bootstrap.ConfigsPage{
Total: 1,
Offset: 0,
Limit: 20,
Configs: []bootstrap.Config{unknownConfig},
},
filter: bootstrap.Filter{Unknown: true},
key: validToken,
offset: 0,
limit: 20,
err: nil,
},
}
for _, tc := range cases {
result, err := svc.List(tc.key, tc.filter, tc.offset, tc.limit)
result, err := svc.List(tc.token, tc.filter, tc.offset, tc.limit)
assert.ElementsMatch(t, tc.config.Configs, result.Configs, fmt.Sprintf("%s: expected %v got %v", tc.desc, tc.config.Configs, result.Configs))
assert.Equal(t, tc.config.Total, result.Total, fmt.Sprintf("%s: expected %v got %v", tc.desc, tc.config.Total, result.Total))
assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
}
}
@@ -431,40 +492,40 @@ func TestRemove(t *testing.T) {
require.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err))
cases := []struct {
desc string
id string
key string
err error
desc string
id string
token string
err error
}{
{
desc: "view a config with wrong credentials",
id: saved.MFThing,
key: invalidToken,
err: bootstrap.ErrUnauthorizedAccess,
desc: "view a config with wrong credentials",
id: saved.MFThing,
token: invalidToken,
err: bootstrap.ErrUnauthorizedAccess,
},
{
desc: "remove an existing config",
id: saved.MFThing,
key: validToken,
err: nil,
desc: "remove an existing config",
id: saved.MFThing,
token: validToken,
err: nil,
},
{
desc: "remove removed config",
id: saved.MFThing,
key: validToken,
err: nil,
desc: "remove removed config",
id: saved.MFThing,
token: validToken,
err: nil,
},
{
desc: "remove non-existing config",
id: unknown,
key: validToken,
err: nil,
desc: "remove non-existing config",
id: unknown,
token: validToken,
err: nil,
},
}
for _, tc := range cases {
err := svc.Remove(tc.key, tc.id)
assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
err := svc.Remove(tc.token, tc.id)
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
}
}
@@ -477,12 +538,16 @@ func TestBootstrap(t *testing.T) {
saved, err := svc.Add(validToken, config)
require.Nil(t, err, fmt.Sprintf("Saving config expected to succeed: %s.\n", err))
e, err := enc([]byte(saved.ExternalKey))
require.Nil(t, err, fmt.Sprintf("Encrypting external key expected to succeed: %s.\n", err))
cases := []struct {
desc string
config bootstrap.Config
externalKey string
externalID string
err error
encrypted bool
}{
{
desc: "bootstrap using invalid external id",
@@ -490,6 +555,7 @@ func TestBootstrap(t *testing.T) {
externalID: "invalid",
externalKey: saved.ExternalKey,
err: bootstrap.ErrNotFound,
encrypted: false,
},
{
desc: "bootstrap using invalid external key",
@@ -497,6 +563,7 @@ func TestBootstrap(t *testing.T) {
externalID: saved.ExternalID,
externalKey: "invalid",
err: bootstrap.ErrNotFound,
encrypted: false,
},
{
desc: "bootstrap an existing config",
@@ -504,13 +571,22 @@ func TestBootstrap(t *testing.T) {
externalID: saved.ExternalID,
externalKey: saved.ExternalKey,
err: nil,
encrypted: false,
},
{
desc: "bootstrap encrypted",
config: saved,
externalID: saved.ExternalID,
externalKey: hex.EncodeToString(e),
err: nil,
encrypted: true,
},
}
for _, tc := range cases {
config, err := svc.Bootstrap(tc.externalKey, tc.externalID)
config, err := svc.Bootstrap(tc.externalKey, tc.externalID, tc.encrypted)
assert.Equal(t, tc.config, config, fmt.Sprintf("%s: expected %v got %v\n", tc.desc, tc.config, config))
assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
}
}
@@ -527,49 +603,49 @@ func TestChangeState(t *testing.T) {
desc string
state bootstrap.State
id string
key string
token string
err error
}{
{
desc: "change state with wrong credentials",
state: bootstrap.Active,
id: saved.MFThing,
key: invalidToken,
token: invalidToken,
err: bootstrap.ErrUnauthorizedAccess,
},
{
desc: "change state of non-existing config",
state: bootstrap.Active,
id: unknown,
key: validToken,
token: validToken,
err: bootstrap.ErrNotFound,
},
{
desc: "change state to Active",
state: bootstrap.Active,
id: saved.MFThing,
key: validToken,
token: validToken,
err: nil,
},
{
desc: "change state to current state",
state: bootstrap.Active,
id: saved.MFThing,
key: validToken,
token: validToken,
err: nil,
},
{
desc: "change state to Inactive",
state: bootstrap.Inactive,
id: saved.MFThing,
key: validToken,
token: validToken,
err: nil,
},
}
for _, tc := range cases {
err := svc.ChangeState(tc.key, tc.id, tc.state)
assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
err := svc.ChangeState(tc.token, tc.id, tc.state)
assert.True(t, errors.Contains(err, tc.err), err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
}
}
@@ -584,7 +660,7 @@ func TestUpdateChannelHandler(t *testing.T) {
ch := bootstrap.Channel{
ID: channel.ID,
Name: "new name",
Metadata: "new meta",
Metadata: map[string]interface{}{"meta": "new"},
}
cases := []struct {
@@ -606,7 +682,7 @@ func TestUpdateChannelHandler(t *testing.T) {
for _, tc := range cases {
err := svc.UpdateChannelHandler(tc.channel)
assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
}
}
@@ -638,7 +714,7 @@ func TestRemoveChannelHandler(t *testing.T) {
for _, tc := range cases {
err := svc.RemoveChannelHandler(tc.id)
assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
}
}
@@ -657,7 +733,7 @@ func TestRemoveCoinfigHandler(t *testing.T) {
err error
}{
{
desc: "remove an existing conifg",
desc: "remove an existing config",
id: saved.MFThing,
err: nil,
},
@@ -670,7 +746,7 @@ func TestRemoveCoinfigHandler(t *testing.T) {
for _, tc := range cases {
err := svc.RemoveConfigHandler(tc.id)
assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
}
}
@@ -696,7 +772,7 @@ func TestDisconnectThingsHandler(t *testing.T) {
err: nil,
},
{
desc: "disconnect dicsonnected",
desc: "disconnect disconnected",
channelID: channel.ID,
thingID: saved.MFThing,
err: nil,
@@ -705,6 +781,6 @@ func TestDisconnectThingsHandler(t *testing.T) {
for _, tc := range cases {
err := svc.DisconnectThingHandler(tc.channelID, tc.thingID)
assert.Equal(t, tc.err, err, fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
assert.True(t, errors.Contains(err, tc.err), fmt.Sprintf("%s: expected %s got %s\n", tc.desc, tc.err, err))
}
}
+3 -7
View File
@@ -1,9 +1,5 @@
//
// Copyright (c) 2018
// Mainflux
//
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
//
package bootstrap
@@ -18,9 +14,9 @@ const (
// State represents corresponding Mainflux Thing state. The possible Config States
// as well as description of what that State represents are given in the table:
// | State | What it means |
// | State | What it means |
// |----------+--------------------------------------------------------------------------------|
// | Inactive | Thing is created, but isn't able to communicate over Mainflux |
// | Inactive | Thing is created, but isn't able to communicate over Mainflux |
// | Active | Thing is able to communicate using Mainflux |
type State int
-438
View File
@@ -1,438 +0,0 @@
swagger: "2.0"
info:
title: Mainflux Bootstrap service
description: HTTP API for managing platform devices and applications configuration.
version: "1.0.0"
consumes:
- "application/json"
produces:
- "application/json"
paths:
/things/configs:
post:
summary: Adds new config
description: |
Adds new config to the list of config owned by user identified using
the provided access token.
tags:
- configs
parameters:
- $ref: "#/parameters/Authorization"
- name: config
description: JSON-formatted document describing the new config.
in: body
schema:
$ref: "#/definitions/ConfigReq"
required: true
responses:
201:
description: Config registered.
headers:
Location:
type: string
description: Created config's relative URL (i.e. /things/configs/{configId}).
400:
description: Failed due to malformed JSON.
403:
description: Missing or invalid access token provided.
415:
description: Missing or invalid content type.
500:
$ref: "#/responses/ServiceError"
get:
summary: Retrieves managed configs
description: |
Retrieves a list of managed configs. Due to performance concerns, data
is retrieved in subsets. The API configs must ensure that the entire
dataset is consumed either by making subsequent requests, or by
increasing the subset size of the initial request.
tags:
- configs
parameters:
- $ref: "#/parameters/Authorization"
- $ref: "#/parameters/Limit"
- $ref: "#/parameters/Offset"
- $ref: "#/parameters/State"
- $ref: "#/parameters/Name"
responses:
200:
description: |
Data retrieved. Configs from this list don't contain channels.
schema:
$ref: "#/definitions/ConfigList"
400:
description: Failed due to malformed query parameters.
403:
description: Missing or invalid access token provided.
500:
$ref: "#/responses/ServiceError"
/things/bootstrap/{externalId}:
get:
summary: Retrieves configuration
description: |
Retrieves a configuration with given external ID and external key.
tags:
- configs
parameters:
- $ref: "#/parameters/ConfigAuthorization"
- $ref: "#/parameters/ExternalId"
responses:
200:
description: Data retrieved.
schema:
$ref: "#/definitions/BootstrapRes"
404:
description: |
Failed to retrieve corresponding config. Thing which attempted
to bootstrap is saved as an unknown Thing and can be listed and
added to the service later.
500:
$ref: "#/responses/ServiceError"
/things/configs/{configId}:
get:
summary: Retrieves config info (with channels)
tags:
- configs
parameters:
- $ref: "#/parameters/Authorization"
- $ref: "#/parameters/ConfigId"
responses:
200:
description: Data retrieved.
schema:
$ref: "#/definitions/ConfigRes"
403:
description: Missing or invalid access token provided.
404:
description: Config does not exist.
500:
$ref: "#/responses/ServiceError"
put:
summary: Updates config info
description: |
Update is performed by replacing the current resource data with values
provided in a request payload. Note that the owner, ID, external ID,
external key, Mainflux Thing ID and key cannot be changed.
tags:
- configs
parameters:
- $ref: "#/parameters/Authorization"
- $ref: "#/parameters/ConfigId"
- name: config
description: JSON-formatted document describing the updated thing.
in: body
schema:
$ref: "#/definitions/ConfigUpdateReq"
required: true
responses:
200:
description: Config updated.
400:
description: Failed due to malformed JSON.
403:
description: Missing or invalid access token provided.
404:
description: Config does not exist.
415:
description: Missing or invalid content type.
500:
$ref: "#/responses/ServiceError"
delete:
summary: Removes a Config
description: |
Removes a Config. In case of successfull removal the service will ensure
that the removed config is disconnected from all of the Maifnlux channels.
tags:
- confgis
parameters:
- $ref: "#/parameters/Authorization"
- $ref: "#/parameters/ConfigId"
responses:
204:
description: Config removed.
400:
description: Failed due to malformed config's ID.
403:
description: Missing or invalid access token provided.
500:
$ref: "#/responses/ServiceError"
/things/configs/connections/{configId}:
put:
summary: Updates channels the thing is connected to
description: |
Update connections performs update of the channel list corresponding
Thing is connected to.
tags:
- configs
parameters:
- $ref: "#/parameters/Authorization"
- $ref: "#/parameters/ConfigId"
- name: channels
description: Array if IDs the thing is be connected to.
in: body
schema:
type: array
items:
type: string
required: true
responses:
200:
description: Config updated.
400:
description: Failed due to malformed JSON.
403:
description: Missing or invalid access token provided.
404:
description: Config does not exist.
415:
description: Missing or invalid content type.
500:
$ref: "#/responses/ServiceError"
/things/state/{configId}:
put:
summary: Updates Config state.
description: |
Updating state represents enabling/disabling Config, i.e. connecting
and disconnecting corresponding Mainflux Thing to the list of Channels.
tags:
- configs
parameters:
- $ref: "#/parameters/Authorization"
- $ref: "#/parameters/ConfigId"
- name: state
description: New state of the Config.
in: body
schema:
type: object
properties:
state:
type: integer
enum:
- inactive
- active
responses:
204:
description: Config removed.
400:
description: Failed due to malformed config's ID.
403:
description: Missing or invalid access token provided.
500:
$ref: "#/responses/ServiceError"
/things/unknown/configs:
get:
summary: Get a list of unsucessfully bootstrapped Things
description: |
Retrieves a list of unknown configs. Due to performance concerns, data
is retrieved in subsets. The API configs must ensure that the entire
dataset is consumed either by making subsequent requests, or by
increasing the subset size of the initial request.
tags:
- configs
parameters:
- $ref: "#/parameters/Authorization"
- $ref: "#/parameters/Limit"
- $ref: "#/parameters/Offset"
responses:
200:
description: Data retrieved.
schema:
$ref: "#/definitions/ConfigList"
400:
description: Failed due to malformed query parameters.
403:
description: Missing or invalid access token provided.
500:
$ref: "#/responses/ServiceError"
parameters:
Authorization:
name: Authorization
description: User's access token.
in: header
type: string
required: true
ConfigAuthorization:
name: configAuthorization
description: Configuration external key.
in: header
type: string
required: true
ConfigId:
name: configId
description: Unique Config identifier.
in: path
type: integer
minimum: 1
required: true
ExternalId:
name: externalId
description: Unique Config identifier provided by external entity.
in: path
type: string
required: true
Limit:
name: limit
description: Size of the subset to retrieve.
in: query
type: integer
default: 10
maximum: 100
minimum: 1
required: false
Offset:
name: offset
description: Number of items to skip during retrieval.
in: query
type: integer
default: 0
minimum: 0
required: false
State:
name: state
description: A state of items
in: query
type: integer
enum:
- inactive
- active
required: false
Name:
name: name
description: Name of the config. Search by name is partial-match and case-insensitive.
in: query
type: string
required: false
responses:
ServiceError:
description: Unexpected server-side error occured.
definitions:
ConfigList:
type: object
properties:
total:
type: integer
description: Total number of results.
minimum: 0
offset:
type: integer
description: Number of items to skip during retrieval.
minimum: 0
default: 0
limit:
type: integer
description: Size of the subset to retrieve.
maximum: 100
default: 10
configs:
type: array
minItems: 0
uniqueItems: true
items:
$ref: "#/definitions/ConfigRes"
required:
- confgis
State:
type: integer
enum:
- active
- inactive
ConfigRes:
type: object
properties:
mainflux_thing:
type: string
description: Corresponding Mainflux Thing ID.
mainflux_key:
type: string
description: Corresponding Mainflux Thing key.
mainflux_channels:
type: array
minItems: 0
items:
type: object
properties:
id:
type: string
description: ID of the Channel.
name:
type: string
description: Name of the Channel.
metadata:
type: object
description: Custom metadata related to the Channel.
external_id:
type: string
description: External ID (MAC address or some uinque identifier).
external_key:
type: string
description: External key.
content:
type: string
description: Free-form custom configuration
state:
$ref: '#/definitions/State'
required:
- external_id
- external_key
BootstrapRes:
type: object
properties:
mainflux_thing:
type: string
description: Corresponding Mainflux Thing ID.
mainflux_key:
type: string
description: Corresponding Mainflux Thing key.
mainflux_channels:
type: array
minItems: 0
items:
type: string
content:
type: string
description: Free-form custom configuration
required:
- mainflux_thing
- mainflux_key
- mainflux_channels
- content
ConfigReq:
type: object
properties:
external_id:
type: string
description: External ID (MAC address or some uinque identifier).
external_key:
type: string
description: External key.
thing_id:
type: string
description: ID of the corresponding Mainflux Thing.
channels:
type: array
minItems: 0
items:
type: string
content:
type: string
required:
- external_id
- external_key
ConfigUpdateReq:
type: object
properties:
channels:
type: array
minItems: 0
items:
type: string
content:
type: string
name:
type: string
required:
- content
- name
+53
View File
@@ -0,0 +1,53 @@
# Certs Service
Issues certificates for things. `Certs` service can create certificates to be used when `Mainflux` is deployed to support mTLS.
Certificate service can create certificates in two modes:
1. Development mode - to be used when no PKI is deployed, this works similar to the [make thing_cert](../docker/ssl/Makefile)
2. PKI mode - certificates issued by PKI, when you deploy `Vault` as PKI certificate management `cert` service will proxy requests to `Vault` previously checking access rights and saving info on successfully created certificate.
## Development mode
If `MF_CERTS_VAULT_HOST` is empty than Development mode is on.
To issue a certificate:
```bash
TOK=`curl -s --insecure -S -X POST http://localhost/tokens -H 'Content-Type: application/json' -d '{"email":"edge@email.com","password":"12345678"}' | jq -r '.token'`
curl -s -S -X POST http://localhost:8204/certs -H "Authorization: $TOK" -H 'Content-Type: application/json' -d '{"thing_id":<thing_id>, "key_bits":2048, "key_type":"rsa"}'
```
```json
{
"ThingID": "",
"ClientCert": "-----BEGIN CERTIFICATE-----\nMIIDmTCCAoGgAwIBAgIRANmkAPbTR1UYeYO0Id/4+8gwDQYJKoZIhvcNAQELBQAw\nVzESMBAGA1UEAwwJbG9jYWxob3N0MREwDwYDVQQKDAhNYWluZmx1eDEMMAoGA1UE\nCwwDSW9UMSAwHgYJKoZIhvcNAQkBFhFpbmZvQG1haW5mbHV4LmNvbTAeFw0yMDA2\nMzAxNDIxMDlaFw0yMDA5MjMyMjIxMDlaMFUxETAPBgNVBAoTCE1haW5mbHV4MREw\nDwYDVQQLEwhtYWluZmx1eDEtMCsGA1UEAxMkYjAwZDBhNzktYjQ2YS00NTk3LTli\nNGYtMjhkZGJhNTBjYTYyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA\ntgS2fLUWG3CCQz/l6VRQRJfRvWmdxK0mW6zIXGeeOILYZeaLiuiUnohwMJ4RiMqT\nuJbInAIuO/Tt5osfrCFFzPEOLYJ5nZBBaJfTIAxqf84Ou1oeMRll4wpzgeKx0rJO\nXMAARwn1bT9n3uky5QQGSLy4PyyILzSXH/1yCQQctdQB/Ar/UI1TaYoYlGzh7dHT\nWpcxq1HYgCyAtcrQrGD0rEwUn82UBCrnya+bygNqu0oDzIFQwa1G8jxSgXk0mFS1\nWrk7rBipsvp8HQhdnvbEVz4k4AAKcQxesH4DkRx/EXmU2UvN3XysvcJ2bL+UzMNI\njNhAe0pgPbB82F6zkYZ/XQIDAQABo2IwYDAOBgNVHQ8BAf8EBAMCB4AwHQYDVR0l\nBBYwFAYIKwYBBQUHAwIGCCsGAQUFBwMBMA4GA1UdDgQHBAUBAgMEBjAfBgNVHSME\nGDAWgBRs4xR91qEjNRGmw391xS7x6Tc+8jANBgkqhkiG9w0BAQsFAAOCAQEAW/dS\nV4vNLTZwBnPVHUX35pRFxPKvscY+vnnpgyDtITgZHYe0KL+Bs3IHuywtqaezU5x1\nkZo+frE1OcpRvp7HJtDiT06yz+18qOYZMappCWCeAFWtZkMhlvnm3TqTkgui6Xgl\nGj5xnPb15AOlsDE2dkv5S6kEwJGHdVX6AOWfB4ubUq5S9e4ABYzXGUty6Hw/ZUmJ\nhCTRVJ7cQJVTJsl1o7CYT8JBvUUG75LirtoFE4M4JwsfsKZXzrQffTf1ynqI3dN/\nHWySEbvTSWcRcA3MSmOTxGt5/zwCglHDlWPKMrXtjTW7NPuGL5/P9HSB9HGVVeET\nDUMdvYwgj0cUCEu3LA==\n-----END CERTIFICATE-----\n",
"IssuingCA": "",
"CAChain": null,
"ClientKey": "-----BEGIN RSA PRIVATE KEY-----\nMIIEowIBAAKCAQEAtgS2fLUWG3CCQz/l6VRQRJfRvWmdxK0mW6zIXGeeOILYZeaL\niuiUnohwMJ4RiMqTuJbInAIuO/Tt5osfrCFFzPEOLYJ5nZBBaJfTIAxqf84Ou1oe\nMRll4wpzgeKx0rJOXMAARwn1bT9n3uky5QQGSLy4PyyILzSXH/1yCQQctdQB/Ar/\nUI1TaYoYlGzh7dHTWpcxq1HYgCyAtcrQrGD0rEwUn82UBCrnya+bygNqu0oDzIFQ\nwa1G8jxSgXk0mFS1Wrk7rBipsvp8HQhdnvbEVz4k4AAKcQxesH4DkRx/EXmU2UvN\n3XysvcJ2bL+UzMNIjNhAe0pgPbB82F6zkYZ/XQIDAQABAoIBAALoal3tqq+/iWU3\npR2oKiweXMxw3oNg3McEKKNJSH7QoFJob3xFoPIzbc9pBxCvY9LEHepYIpL0o8RW\nHqhqU6olg7t4ZSb+Qf1Ax6+wYxctnJCjrO3N4RHSfevqSjr6fEQBEUARSal4JNmr\n0hNUkCEjWrIvrPFMHsn1C5hXR3okJQpGsad4oCGZDp2eZ/NDyvmLBLci9/5CJdRv\n6roOF5ShWweKcz1+pfy666Q8RiUI7H1zXjPaL4yqkv8eg/WPOO0dYF2Ri2Grk9OY\n1qTM0W1vi9zfncinZ0DpgtwMTFQezGwhUyJHSYHmjVBA4AaYIyOQAI/2dl5fXM+O\n9JfXpOUCgYEA10xAtMc/8KOLbHCprpc4pbtOqfchq/M04qPKxQNAjqvLodrWZZgF\nexa+B3eWWn5MxmQMx18AjBCPwbNDK8Rkd9VqzdWempaSblgZ7y1a0rRNTXzN5DFP\noiuRQV4wszCuj5XSdPn+lxApaI/4+TQ0oweIZCpGW39XKePPoB5WZiMCgYEA2G3W\niJncRpmxWwrRPi1W26E9tWOT5s9wYgXWMc+PAVUd/qdDRuMBHpu861Qoghp/MJog\nBYqt2rQqU0OxvIXlXPrXPHXrCLOFwybRCBVREZrg4BZNnjyDTLOu9C+0M3J9ImCh\n3vniYqb7S0gRmoDM0R3Zu4+ajfP2QOGLXw1qHH8CgYEAl0EQ7HBW8V5UYzi7XNcM\nixKOb0YZt83DR74+hC6GujTjeLBfkzw8DX+qvWA8lxLIKVC80YxivAQemryv4h21\nX6Llx/nd1UkXUsI+ZhP9DK5y6I9XroseIRZuk/fyStFWsbVWB6xiOgq2rKkJBzqw\nCCEQpx40E6/gsqNDiIAHvvUCgYBkkjXc6FJ55DWMLuyozfzMtpKsVYeG++InSrsM\nDn1PizQS/7q9mAMPLCOP312rh5CPDy/OI3FCbfI1GwHerwG0QUP/bnQ3aOTBmKoN\n7YnsemIA/5w16bzBycWE5x3/wjXv4aOWr9vJJ/siMm0rtKp4ijyBcevKBxHpeGWB\nWAR1FQKBgGIqAxGnBpip9E24gH894BaGHHMpQCwAxARev6sHKUy27eFUd6ipoTva\n4Wv36iz3gxU4R5B0gyfnxBNiUab/z90cb5+6+FYO13kqjxRRZWffohk5nHlmFN9K\nea7KQHTfTdRhOLUzW2yVqLi9pzfTfA6Yqf3U1YD3bgnWrp1VQnjo\n-----END RSA PRIVATE KEY-----\n",
"PrivateKeyType": "",
"Serial": "",
"Expire": "0001-01-01T00:00:00Z"
}
```
## PKI mode
When `MF_CERTS_VAULT_HOST` is set it is presumed that `Vault` is installed and `certs` service will issue certificates using `Vault` API.
First you'll need to set up `Vault`.
To setup `Vault` follow steps in [Build Your Own Certificate Authority (CA)](https://learn.hashicorp.com/tutorials/vault/pki-engine).
To setup certs service with `Vault` following environment variables must be set:
```
MF_CERTS_VAULT_HOST=vault-domain.com
MF_CERTS_VAULT_PKI_PATH=<vault_pki_path>
MF_CERTS_VAULT_ROLE=<vault_role>
MF_CERTS_VAULT_TOKEN=<vault_acces_token>
```
For lab purposes you can use docker-compose and script for setting up PKI in [https://github.com/mteodor/vault](https://github.com/mteodor/vault)
Issuing certificate is same as in **Development** mode.
In this mode certificates can also be revoked:
```bash
curl -s -S -X DELETE http://localhost:8204/certs/revoke -H "Authorization: $TOK" -H 'Content-Type: application/json' -d '{"thing_id":"c30b8842-507c-4bcd-973c-74008cef3be5"}'
```
+5
View File
@@ -0,0 +1,5 @@
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
// Package api contains implementation of certs service HTTP API.
package api
+75
View File
@@ -0,0 +1,75 @@
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
package api
import (
"context"
"github.com/go-kit/kit/endpoint"
"github.com/mainflux/mainflux/certs"
)
func issueCert(svc certs.Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
req := request.(addCertsReq)
if err := req.validate(); err != nil {
return nil, err
}
res, err := svc.IssueCert(ctx, req.token, req.ThingID, req.Valid, req.KeyBits, req.KeyType)
if err != nil {
return certsRes{}, err
}
return certsRes{
CertSerial: res.Serial,
ThingID: res.ThingID,
CertKey: res.ClientKey,
Cert: res.ClientCert,
CACert: res.IssuingCA,
}, nil
}
}
func listCerts(svc certs.Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
req := request.(listReq)
if err := req.validate(); err != nil {
return nil, err
}
page, err := svc.ListCerts(ctx, req.token, req.thingID, req.offset, req.limit)
if err != nil {
return certsPageRes{}, err
}
res := certsPageRes{
pageRes: pageRes{
Total: page.Total,
Offset: page.Offset,
Limit: page.Limit,
},
Certs: []certsRes{},
}
for _, cert := range page.Certs {
view := certsRes{
CertSerial: cert.Serial,
ThingID: cert.ThingID,
CertKey: cert.ClientKey,
Cert: cert.ClientCert,
CACert: cert.IssuingCA,
}
res.Certs = append(res.Certs, view)
}
return res, nil
}
}
func revokeCert(svc certs.Service) endpoint.Endpoint {
return func(ctx context.Context, request interface{}) (interface{}, error) {
req := request.(revokeReq)
if err := req.validate(); err != nil {
return nil, err
}
return svc.RevokeCert(ctx, req.token, req.certID)
}
}
+64
View File
@@ -0,0 +1,64 @@
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
package api
import (
"context"
"fmt"
"time"
"github.com/mainflux/mainflux/certs"
log "github.com/mainflux/mainflux/logger"
)
var _ certs.Service = (*loggingMiddleware)(nil)
type loggingMiddleware struct {
logger log.Logger
svc certs.Service
}
// NewLoggingMiddleware adds logging facilities to the core service.
func NewLoggingMiddleware(svc certs.Service, logger log.Logger) certs.Service {
return &loggingMiddleware{logger, svc}
}
func (lm *loggingMiddleware) IssueCert(ctx context.Context, token, thingID, daysValid string, keyBits int, keyType string) (c certs.Cert, err error) {
defer func(begin time.Time) {
message := fmt.Sprintf("Method issue_cert for token: %s and thing: %s took %s to complete", token, thingID, time.Since(begin))
if err != nil {
lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err))
return
}
lm.logger.Info(fmt.Sprintf("%s without errors.", message))
}(time.Now())
return lm.svc.IssueCert(ctx, token, thingID, daysValid, keyBits, keyType)
}
func (lm *loggingMiddleware) ListCerts(ctx context.Context, token, thingID string, offset, limit uint64) (cp certs.Page, err error) {
defer func(begin time.Time) {
message := fmt.Sprintf("Method list_certs for token: %s and thing id: %s took %s to complete", token, thingID, time.Since(begin))
if err != nil {
lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err))
return
}
lm.logger.Info(fmt.Sprintf("%s without errors.", message))
}(time.Now())
return lm.svc.ListCerts(ctx, token, thingID, offset, limit)
}
func (lm *loggingMiddleware) RevokeCert(ctx context.Context, token, thingID string) (c certs.Revoke, err error) {
defer func(begin time.Time) {
message := fmt.Sprintf("Method revoke_cert for token: %s and thing: %s took %s to complete", token, thingID, time.Since(begin))
if err != nil {
lm.logger.Warn(fmt.Sprintf("%s with error: %s.", message, err))
return
}
lm.logger.Info(fmt.Sprintf("%s without errors.", message))
}(time.Now())
return lm.svc.RevokeCert(ctx, token, thingID)
}
+56
View File
@@ -0,0 +1,56 @@
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
package api
import (
"context"
"time"
"github.com/go-kit/kit/metrics"
"github.com/mainflux/mainflux/certs"
)
var _ certs.Service = (*metricsMiddleware)(nil)
type metricsMiddleware struct {
counter metrics.Counter
latency metrics.Histogram
svc certs.Service
}
// MetricsMiddleware instruments core service by tracking request count and latency.
func MetricsMiddleware(svc certs.Service, counter metrics.Counter, latency metrics.Histogram) certs.Service {
return &metricsMiddleware{
counter: counter,
latency: latency,
svc: svc,
}
}
func (ms *metricsMiddleware) IssueCert(ctx context.Context, token, thingID string, daysValid string, keyBits int, keyType string) (certs.Cert, error) {
defer func(begin time.Time) {
ms.counter.With("method", "issue_cert").Add(1)
ms.latency.With("method", "issue_cert").Observe(time.Since(begin).Seconds())
}(time.Now())
return ms.svc.IssueCert(ctx, token, thingID, daysValid, keyBits, keyType)
}
func (ms *metricsMiddleware) ListCerts(ctx context.Context, token, thingID string, offset, limit uint64) (certs.Page, error) {
defer func(begin time.Time) {
ms.counter.With("method", "list_certs").Add(1)
ms.latency.With("method", "list_certs").Observe(time.Since(begin).Seconds())
}(time.Now())
return ms.svc.ListCerts(ctx, token, thingID, offset, limit)
}
func (ms *metricsMiddleware) RevokeCert(ctx context.Context, token, thingID string) (certs.Revoke, error) {
defer func(begin time.Time) {
ms.counter.With("method", "revoke_cert").Add(1)
ms.latency.With("method", "revoke_cert").Observe(time.Since(begin).Seconds())
}(time.Now())
return ms.svc.RevokeCert(ctx, token, thingID)
}
+53
View File
@@ -0,0 +1,53 @@
// Copyright (c) Mainflux
// SPDX-License-Identifier: Apache-2.0
package api
import "github.com/mainflux/mainflux/certs"
const maxLimitSize = 100
type addCertsReq struct {
token string
ThingID string `json:"thing_id"`
KeyBits int `json:"key_bits"`
KeyType string `json:"key_type"`
Valid string `json:"valid"`
}
func (req addCertsReq) validate() error {
if req.ThingID == "" && req.token == "" {
return errUnauthorized
}
return nil
}
type listReq struct {
thingID string
token string
offset uint64
limit uint64
}
func (req *listReq) validate() error {
if req.token == "" {
return certs.ErrUnauthorizedAccess
}
if req.limit == 0 || req.limit > maxLimitSize {
return certs.ErrMalformedEntity
}
return nil
}
type revokeReq struct {
token string
certID string
}
func (req *revokeReq) validate() error {
if req.token == "" || req.certID == "" {
return certs.ErrUnauthorizedAccess
}
return nil
}

Some files were not shown because too many files have changed in this diff Show More