mirror of
				https://github.com/Telecominfraproject/wlan-cloud-ucentralsec.git
				synced 2025-11-03 04:07:45 +00:00 
			
		
		
		
	Compare commits
	
		
			613 Commits
		
	
	
		
			release/v2
			...
			v2.6.0-RC2
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					8e8656a6ae | ||
| 
						 | 
					8863d2ecc9 | ||
| 
						 | 
					5e1937ec4f | ||
| 
						 | 
					e679dc7458 | ||
| 
						 | 
					7e1a962b57 | ||
| 
						 | 
					8ad2e12f12 | ||
| 
						 | 
					23d16e619a | ||
| 
						 | 
					760cad9a14 | ||
| 
						 | 
					94997a1f9f | ||
| 
						 | 
					9060fef03d | ||
| 
						 | 
					2f8eb90c5a | ||
| 
						 | 
					c0d0435efa | ||
| 
						 | 
					6942de0475 | ||
| 
						 | 
					cce2528ec4 | ||
| 
						 | 
					3be0fd45d9 | ||
| 
						 | 
					8b1a80ce09 | ||
| 
						 | 
					5e12f00558 | ||
| 
						 | 
					1d534cb974 | ||
| 
						 | 
					a7e9c96f8d | ||
| 
						 | 
					cb3f7a0872 | ||
| 
						 | 
					6ad434c02f | ||
| 
						 | 
					70ddeaf3c1 | ||
| 
						 | 
					56d7c7c767 | ||
| 
						 | 
					62e3ada15c | ||
| 
						 | 
					2beef2daba | ||
| 
						 | 
					4b131465fb | ||
| 
						 | 
					cafc243e55 | ||
| 
						 | 
					5c44134f9d | ||
| 
						 | 
					8ed86d3582 | ||
| 
						 | 
					7e3fb701a0 | ||
| 
						 | 
					d7792f28de | ||
| 
						 | 
					5a23df748d | ||
| 
						 | 
					e06a42c197 | ||
| 
						 | 
					702d7df822 | ||
| 
						 | 
					a369f37780 | ||
| 
						 | 
					46fdf66141 | ||
| 
						 | 
					9929dd0e5c | ||
| 
						 | 
					bd33ccb870 | ||
| 
						 | 
					bb1383b1f7 | ||
| 
						 | 
					2d074f455e | ||
| 
						 | 
					9bc6372a42 | ||
| 
						 | 
					9d654535a4 | ||
| 
						 | 
					fd8201e961 | ||
| 
						 | 
					8bbe084640 | ||
| 
						 | 
					ab22a75fc5 | ||
| 
						 | 
					b74a006f0b | ||
| 
						 | 
					c9eeb12491 | ||
| 
						 | 
					e17f6cfd6c | ||
| 
						 | 
					7b9013b049 | ||
| 
						 | 
					159bd40563 | ||
| 
						 | 
					db9a184014 | ||
| 
						 | 
					1ba4bda798 | ||
| 
						 | 
					40fe54d18a | ||
| 
						 | 
					b705c9b138 | ||
| 
						 | 
					51868e5bee | ||
| 
						 | 
					87596762a8 | ||
| 
						 | 
					af686c46bd | ||
| 
						 | 
					6afd6ea3a6 | ||
| 
						 | 
					07ec6d990b | ||
| 
						 | 
					77fe6ed89e | ||
| 
						 | 
					6b6f29087d | ||
| 
						 | 
					5da5e3b38e | ||
| 
						 | 
					7591b8cd44 | ||
| 
						 | 
					097fe2e436 | ||
| 
						 | 
					c602b81d55 | ||
| 
						 | 
					2cbbde4904 | ||
| 
						 | 
					37aa710173 | ||
| 
						 | 
					4fc7ae5b85 | ||
| 
						 | 
					f933d42354 | ||
| 
						 | 
					7ffd0bf2ad | ||
| 
						 | 
					a699beda84 | ||
| 
						 | 
					704a51290e | ||
| 
						 | 
					f9912bb2c9 | ||
| 
						 | 
					710d807977 | ||
| 
						 | 
					5fbad76c83 | ||
| 
						 | 
					8076467b20 | ||
| 
						 | 
					ce1764919f | ||
| 
						 | 
					44457d0f55 | ||
| 
						 | 
					d869f6bb78 | ||
| 
						 | 
					40705e01e1 | ||
| 
						 | 
					60bd8fd2b2 | ||
| 
						 | 
					c36d9157c4 | ||
| 
						 | 
					ceb6a6fc17 | ||
| 
						 | 
					afc8a59267 | ||
| 
						 | 
					c19ce8a92c | ||
| 
						 | 
					d69e773263 | ||
| 
						 | 
					39ce81dc84 | ||
| 
						 | 
					17144ed439 | ||
| 
						 | 
					7a070009b1 | ||
| 
						 | 
					627c3c49df | ||
| 
						 | 
					602921827a | ||
| 
						 | 
					d8579d9500 | ||
| 
						 | 
					bc05845015 | ||
| 
						 | 
					f300e64b06 | ||
| 
						 | 
					adde8a2f85 | ||
| 
						 | 
					149bdefcc0 | ||
| 
						 | 
					39f694b6f8 | ||
| 
						 | 
					d6d776e806 | ||
| 
						 | 
					ee92e13b15 | ||
| 
						 | 
					daf6acb083 | ||
| 
						 | 
					1f3ee2a08a | ||
| 
						 | 
					e9b301a242 | ||
| 
						 | 
					657e6b660a | ||
| 
						 | 
					bd59686006 | ||
| 
						 | 
					e138431304 | ||
| 
						 | 
					d5665e24a1 | ||
| 
						 | 
					a4b28cd8d5 | ||
| 
						 | 
					54900100c3 | ||
| 
						 | 
					197952817d | ||
| 
						 | 
					92b1bcb9ba | ||
| 
						 | 
					426bcef5ee | ||
| 
						 | 
					24986190c4 | ||
| 
						 | 
					1a18c6b295 | ||
| 
						 | 
					6e72c28b3e | ||
| 
						 | 
					bdda1aff35 | ||
| 
						 | 
					dd138314b9 | ||
| 
						 | 
					8cd7a99c55 | ||
| 
						 | 
					ed393b08a5 | ||
| 
						 | 
					93d1681198 | ||
| 
						 | 
					4bb41f022a | ||
| 
						 | 
					006ca731f0 | ||
| 
						 | 
					a3e9114882 | ||
| 
						 | 
					7577693620 | ||
| 
						 | 
					9f59239318 | ||
| 
						 | 
					c754cbdc31 | ||
| 
						 | 
					ab28e87245 | ||
| 
						 | 
					e71eff25d5 | ||
| 
						 | 
					672b0d1d00 | ||
| 
						 | 
					7b3fd5f42a | ||
| 
						 | 
					280d4f5e41 | ||
| 
						 | 
					b32870d41b | ||
| 
						 | 
					dad8f68f71 | ||
| 
						 | 
					368ea4e4f3 | ||
| 
						 | 
					6690aa7cf5 | ||
| 
						 | 
					33d12a6bad | ||
| 
						 | 
					b1805a9352 | ||
| 
						 | 
					b126f46c35 | ||
| 
						 | 
					faaaf61bf4 | ||
| 
						 | 
					7448074b5f | ||
| 
						 | 
					1737486466 | ||
| 
						 | 
					d1a9315b15 | ||
| 
						 | 
					d1eedc02ef | ||
| 
						 | 
					5355ac822f | ||
| 
						 | 
					31f496733f | ||
| 
						 | 
					b1b3ee7887 | ||
| 
						 | 
					06fbace243 | ||
| 
						 | 
					65295f58ff | ||
| 
						 | 
					a3885b8b1c | ||
| 
						 | 
					52115100aa | ||
| 
						 | 
					36c0209961 | ||
| 
						 | 
					fe09ddfb5a | ||
| 
						 | 
					7ae8f200a4 | ||
| 
						 | 
					560205b610 | ||
| 
						 | 
					23106fc89c | ||
| 
						 | 
					fdced9af89 | ||
| 
						 | 
					e7f51b7be1 | ||
| 
						 | 
					809b4bb79d | ||
| 
						 | 
					02566e8e0b | ||
| 
						 | 
					ad894aeb17 | ||
| 
						 | 
					f59d3af832 | ||
| 
						 | 
					16adc66042 | ||
| 
						 | 
					c1a0c0e86d | ||
| 
						 | 
					d3bc539fff | ||
| 
						 | 
					f8c637a0aa | ||
| 
						 | 
					ed511e346f | ||
| 
						 | 
					b48557e907 | ||
| 
						 | 
					8f2bcc4622 | ||
| 
						 | 
					7a20fc0423 | ||
| 
						 | 
					490284c0e0 | ||
| 
						 | 
					969b675200 | ||
| 
						 | 
					0f68c74e43 | ||
| 
						 | 
					8fc1a1bfed | ||
| 
						 | 
					b97635b980 | ||
| 
						 | 
					0914c1d23c | ||
| 
						 | 
					aed24a0358 | ||
| 
						 | 
					8e774109af | ||
| 
						 | 
					4c2ce84b81 | ||
| 
						 | 
					423b645c18 | ||
| 
						 | 
					c5e73a76b3 | ||
| 
						 | 
					e88b7fddea | ||
| 
						 | 
					6d39fd2b08 | ||
| 
						 | 
					ff81d899d1 | ||
| 
						 | 
					62de3cea24 | ||
| 
						 | 
					bab4f4d6e3 | ||
| 
						 | 
					e629220094 | ||
| 
						 | 
					3754da24a1 | ||
| 
						 | 
					6594edd8c6 | ||
| 
						 | 
					7b767ae03f | ||
| 
						 | 
					80af312318 | ||
| 
						 | 
					d72bb0b831 | ||
| 
						 | 
					d3d446f88e | ||
| 
						 | 
					3d50837e9e | ||
| 
						 | 
					5e58797503 | ||
| 
						 | 
					adf08db227 | ||
| 
						 | 
					2b4417a586 | ||
| 
						 | 
					3c057bda39 | ||
| 
						 | 
					cc321786f5 | ||
| 
						 | 
					f70c215ed2 | ||
| 
						 | 
					f6c07de827 | ||
| 
						 | 
					67e52c8e81 | ||
| 
						 | 
					0b03e32782 | ||
| 
						 | 
					0a00c39d14 | ||
| 
						 | 
					81b9da9228 | ||
| 
						 | 
					fcf2976989 | ||
| 
						 | 
					a4757454ef | ||
| 
						 | 
					21fb969c57 | ||
| 
						 | 
					d1ee91d78d | ||
| 
						 | 
					70d6373459 | ||
| 
						 | 
					dea728234e | ||
| 
						 | 
					da1e33b09d | ||
| 
						 | 
					50c0ae1b24 | ||
| 
						 | 
					a75db95a23 | ||
| 
						 | 
					e48250eb5e | ||
| 
						 | 
					2fd563e4b1 | ||
| 
						 | 
					001fe7d7cc | ||
| 
						 | 
					33101f516e | ||
| 
						 | 
					98c800060b | ||
| 
						 | 
					0f1ab81817 | ||
| 
						 | 
					850b26c878 | ||
| 
						 | 
					119886994e | ||
| 
						 | 
					15a7d10e5c | ||
| 
						 | 
					03a7c616f0 | ||
| 
						 | 
					2f3e802cee | ||
| 
						 | 
					1b182f8076 | ||
| 
						 | 
					151bcc9406 | ||
| 
						 | 
					6c5863d96a | ||
| 
						 | 
					b552d916d6 | ||
| 
						 | 
					8034e39bed | ||
| 
						 | 
					709c1d4f6b | ||
| 
						 | 
					275b10ba20 | ||
| 
						 | 
					a29ddcc9f5 | ||
| 
						 | 
					f8d0f5e06a | ||
| 
						 | 
					c5f70fdda7 | ||
| 
						 | 
					ce54855f3f | ||
| 
						 | 
					f659da3b8e | ||
| 
						 | 
					96bb22033e | ||
| 
						 | 
					a9d36f2460 | ||
| 
						 | 
					bf7785534d | ||
| 
						 | 
					31a550514a | ||
| 
						 | 
					634b079f45 | ||
| 
						 | 
					99c77c5dd0 | ||
| 
						 | 
					2fb80c68dd | ||
| 
						 | 
					0652c13139 | ||
| 
						 | 
					feb5ff1f2c | ||
| 
						 | 
					5a9a62ff7e | ||
| 
						 | 
					bd331913e1 | ||
| 
						 | 
					5756e31ae6 | ||
| 
						 | 
					884b91af63 | ||
| 
						 | 
					fed43efadf | ||
| 
						 | 
					48d3e56b79 | ||
| 
						 | 
					a0f9abea88 | ||
| 
						 | 
					3e0ecda9d6 | ||
| 
						 | 
					ad38d8e84c | ||
| 
						 | 
					27c581750c | ||
| 
						 | 
					e09ee35940 | ||
| 
						 | 
					3c4d9612d3 | ||
| 
						 | 
					6485b2426c | ||
| 
						 | 
					3d3dbc6b4d | ||
| 
						 | 
					8965b3c590 | ||
| 
						 | 
					77941c4775 | ||
| 
						 | 
					ef9cd80df6 | ||
| 
						 | 
					042f7619ec | ||
| 
						 | 
					8a371d7eaf | ||
| 
						 | 
					ac92c7da85 | ||
| 
						 | 
					7a8449efbf | ||
| 
						 | 
					8365aa5463 | ||
| 
						 | 
					1c4f961971 | ||
| 
						 | 
					19c92edfcc | ||
| 
						 | 
					3fd6e6b849 | ||
| 
						 | 
					bc6d8a8a79 | ||
| 
						 | 
					29da9b4b8e | ||
| 
						 | 
					b3f1f35bb4 | ||
| 
						 | 
					a9bd44b3b2 | ||
| 
						 | 
					01f457dd0c | ||
| 
						 | 
					f27f036e62 | ||
| 
						 | 
					27f8d06cab | ||
| 
						 | 
					7960cda46e | ||
| 
						 | 
					9907f91c49 | ||
| 
						 | 
					b3b98d90ca | ||
| 
						 | 
					ef947c3e33 | ||
| 
						 | 
					a5eb0d792b | ||
| 
						 | 
					151585f6e5 | ||
| 
						 | 
					7fc7daf595 | ||
| 
						 | 
					ebe2df4dc7 | ||
| 
						 | 
					9dcc19f4fe | ||
| 
						 | 
					984ba88341 | ||
| 
						 | 
					b14eba63c3 | ||
| 
						 | 
					b8c02906ea | ||
| 
						 | 
					f3c3539f62 | ||
| 
						 | 
					5fef83d726 | ||
| 
						 | 
					874e28ffff | ||
| 
						 | 
					f84b8c4b5c | ||
| 
						 | 
					ae6a986e57 | ||
| 
						 | 
					04cffd13c8 | ||
| 
						 | 
					28635dcbdd | ||
| 
						 | 
					6a67bf80af | ||
| 
						 | 
					9460cc1e5b | ||
| 
						 | 
					6d20c8408f | ||
| 
						 | 
					fd21917a93 | ||
| 
						 | 
					c47e9bc98d | ||
| 
						 | 
					30431ab954 | ||
| 
						 | 
					76175d8bbb | ||
| 
						 | 
					36e16e3c8e | ||
| 
						 | 
					cc4e5848a5 | ||
| 
						 | 
					8425950da7 | ||
| 
						 | 
					cf903a57ab | ||
| 
						 | 
					d38e4ca2fc | ||
| 
						 | 
					584254cb07 | ||
| 
						 | 
					dcf7ff5f48 | ||
| 
						 | 
					1039a53925 | ||
| 
						 | 
					a80bcd16d8 | ||
| 
						 | 
					fce18f6238 | ||
| 
						 | 
					f8c6dad974 | ||
| 
						 | 
					eec8662a3c | ||
| 
						 | 
					f26c6e1454 | ||
| 
						 | 
					08a50db13c | ||
| 
						 | 
					7b741048ff | ||
| 
						 | 
					7fcd451e3b | ||
| 
						 | 
					c545be6ae9 | ||
| 
						 | 
					b1f489bf4f | ||
| 
						 | 
					92910fe0c5 | ||
| 
						 | 
					eda30b3dc3 | ||
| 
						 | 
					51dd7bdfa7 | ||
| 
						 | 
					2eccf1ef06 | ||
| 
						 | 
					b37edca02c | ||
| 
						 | 
					e831619673 | ||
| 
						 | 
					78ae79b1d5 | ||
| 
						 | 
					2bc6d7c325 | ||
| 
						 | 
					0f6a95a330 | ||
| 
						 | 
					83a1d80d77 | ||
| 
						 | 
					84bcb28328 | ||
| 
						 | 
					4d03faf523 | ||
| 
						 | 
					73096153b4 | ||
| 
						 | 
					1cf9894f7d | ||
| 
						 | 
					f54cd95fc4 | ||
| 
						 | 
					882226ccdb | ||
| 
						 | 
					e5f10ccd17 | ||
| 
						 | 
					c500ae36b1 | ||
| 
						 | 
					6ead810ec6 | ||
| 
						 | 
					c6ac384cdb | ||
| 
						 | 
					f28e07a615 | ||
| 
						 | 
					c7c5401bc2 | ||
| 
						 | 
					6264c7f3bb | ||
| 
						 | 
					c078bdb555 | ||
| 
						 | 
					81131b8038 | ||
| 
						 | 
					4633db416d | ||
| 
						 | 
					6c14337333 | ||
| 
						 | 
					784fc3256b | ||
| 
						 | 
					2c513d8374 | ||
| 
						 | 
					d202b9e365 | ||
| 
						 | 
					b869da3b09 | ||
| 
						 | 
					f31195e854 | ||
| 
						 | 
					ec4ab520d8 | ||
| 
						 | 
					a9ade83094 | ||
| 
						 | 
					977742d802 | ||
| 
						 | 
					b69b90b243 | ||
| 
						 | 
					ec0aa4e15a | ||
| 
						 | 
					5fc313aa50 | ||
| 
						 | 
					a3975829e4 | ||
| 
						 | 
					e8cf7a6f21 | ||
| 
						 | 
					d27441d793 | ||
| 
						 | 
					ae5c32a8ec | ||
| 
						 | 
					96c684fe5e | ||
| 
						 | 
					8609990551 | ||
| 
						 | 
					4566bb942c | ||
| 
						 | 
					e5d6f42433 | ||
| 
						 | 
					524f79e825 | ||
| 
						 | 
					be46b46340 | ||
| 
						 | 
					5ff0841112 | ||
| 
						 | 
					a6c115adb5 | ||
| 
						 | 
					4d474fe92c | ||
| 
						 | 
					4b46e0c9c9 | ||
| 
						 | 
					de0ddc769b | ||
| 
						 | 
					4a11602fb9 | ||
| 
						 | 
					ef1f7098a6 | ||
| 
						 | 
					38eebe6162 | ||
| 
						 | 
					5124ed016c | ||
| 
						 | 
					fb632b6ce1 | ||
| 
						 | 
					775d0c0a65 | ||
| 
						 | 
					fb2ddaa136 | ||
| 
						 | 
					f51e00c50c | ||
| 
						 | 
					da49bebb15 | ||
| 
						 | 
					916d5cdf13 | ||
| 
						 | 
					5eecfbfd30 | ||
| 
						 | 
					32a5c81f1d | ||
| 
						 | 
					a72189f854 | ||
| 
						 | 
					2be40d5d17 | ||
| 
						 | 
					f8407f7b7c | ||
| 
						 | 
					2ec792a74b | ||
| 
						 | 
					72f0b11f81 | ||
| 
						 | 
					00965b78c7 | ||
| 
						 | 
					b2c06affa8 | ||
| 
						 | 
					7b467850b6 | ||
| 
						 | 
					be89574363 | ||
| 
						 | 
					e4d3855251 | ||
| 
						 | 
					57bd712634 | ||
| 
						 | 
					797f4e9c80 | ||
| 
						 | 
					309a856cd0 | ||
| 
						 | 
					937a20beea | ||
| 
						 | 
					2217a428c1 | ||
| 
						 | 
					ec82bdec24 | ||
| 
						 | 
					40faa84d2b | ||
| 
						 | 
					cb3efcecb5 | ||
| 
						 | 
					e11d955529 | ||
| 
						 | 
					5a4eafee7d | ||
| 
						 | 
					2df45c26a4 | ||
| 
						 | 
					311786f8d8 | ||
| 
						 | 
					829de62967 | ||
| 
						 | 
					55d1f9571d | ||
| 
						 | 
					80a520c4cc | ||
| 
						 | 
					040623fc8c | ||
| 
						 | 
					dc6eaaeb89 | ||
| 
						 | 
					4953aa02dc | ||
| 
						 | 
					e794647469 | ||
| 
						 | 
					e87bdc9440 | ||
| 
						 | 
					a8f59e0fb5 | ||
| 
						 | 
					2730a8c5e4 | ||
| 
						 | 
					b3ef448628 | ||
| 
						 | 
					13fe7d295b | ||
| 
						 | 
					ef1eb190a2 | ||
| 
						 | 
					370d4aca47 | ||
| 
						 | 
					a575d95b91 | ||
| 
						 | 
					0477ab5349 | ||
| 
						 | 
					730f8d169a | ||
| 
						 | 
					be7f50ccbb | ||
| 
						 | 
					8d681988a9 | ||
| 
						 | 
					8842f23a8e | ||
| 
						 | 
					5e5150a73f | ||
| 
						 | 
					9e79b73e20 | ||
| 
						 | 
					eb4dfc25f2 | ||
| 
						 | 
					bedec254c5 | ||
| 
						 | 
					96a566a2b5 | ||
| 
						 | 
					ad2eb1711e | ||
| 
						 | 
					7244bcb455 | ||
| 
						 | 
					1db5201418 | ||
| 
						 | 
					1bc635f553 | ||
| 
						 | 
					257ac42d7c | ||
| 
						 | 
					acb38e5313 | ||
| 
						 | 
					7940f0bd85 | ||
| 
						 | 
					62c06d0bad | ||
| 
						 | 
					494a199610 | ||
| 
						 | 
					5307b0b35a | ||
| 
						 | 
					c58728f38e | ||
| 
						 | 
					1f09c3b619 | ||
| 
						 | 
					d9c6388502 | ||
| 
						 | 
					5e35906aec | ||
| 
						 | 
					773618ae07 | ||
| 
						 | 
					cca4441ac7 | ||
| 
						 | 
					730ca7b292 | ||
| 
						 | 
					5b4dbb088f | ||
| 
						 | 
					bc11a19ee4 | ||
| 
						 | 
					c835e4d0b9 | ||
| 
						 | 
					f1a2ba90f6 | ||
| 
						 | 
					5b96ef396f | ||
| 
						 | 
					c204d34bf4 | ||
| 
						 | 
					4b982bf64b | ||
| 
						 | 
					37298cc600 | ||
| 
						 | 
					03619cc900 | ||
| 
						 | 
					f4fc6975e1 | ||
| 
						 | 
					1f1d596c5a | ||
| 
						 | 
					a5802bf631 | ||
| 
						 | 
					6471eabc82 | ||
| 
						 | 
					ab6fbaca11 | ||
| 
						 | 
					1e8e5c18b2 | ||
| 
						 | 
					3cf23af068 | ||
| 
						 | 
					1a0b549731 | ||
| 
						 | 
					a835d2e571 | ||
| 
						 | 
					ff7455af24 | ||
| 
						 | 
					48610bac5d | ||
| 
						 | 
					7bd5b4d4e6 | ||
| 
						 | 
					e1a51c2a91 | ||
| 
						 | 
					cd0222f765 | ||
| 
						 | 
					12fddd8bc4 | ||
| 
						 | 
					9095d831db | ||
| 
						 | 
					4e8f97df9b | ||
| 
						 | 
					28808eae93 | ||
| 
						 | 
					6c24a23863 | ||
| 
						 | 
					5931c91054 | ||
| 
						 | 
					9d956c13f7 | ||
| 
						 | 
					ea1adde361 | ||
| 
						 | 
					eaac1f1625 | ||
| 
						 | 
					c5f4c067bb | ||
| 
						 | 
					31a9e4564b | ||
| 
						 | 
					a9affc29bb | ||
| 
						 | 
					65fc0a1d10 | ||
| 
						 | 
					82c01ce438 | ||
| 
						 | 
					5f900883e8 | ||
| 
						 | 
					e97b8e64be | ||
| 
						 | 
					6c90c75708 | ||
| 
						 | 
					a3d86c7cf9 | ||
| 
						 | 
					50b6ac9522 | ||
| 
						 | 
					15b947a34d | ||
| 
						 | 
					160bd00a99 | ||
| 
						 | 
					3c7daa537a | ||
| 
						 | 
					c5bab1d749 | ||
| 
						 | 
					96c3244be0 | ||
| 
						 | 
					7e4b515f60 | ||
| 
						 | 
					a63f80e497 | ||
| 
						 | 
					2eae6cc73c | ||
| 
						 | 
					96f215b3c2 | ||
| 
						 | 
					9551384358 | ||
| 
						 | 
					b21c5c5e00 | ||
| 
						 | 
					031d35256c | ||
| 
						 | 
					5738fa47bb | ||
| 
						 | 
					fe17650333 | ||
| 
						 | 
					7636568fb4 | ||
| 
						 | 
					c0ef77eb53 | ||
| 
						 | 
					00742a5d0a | ||
| 
						 | 
					a96f673380 | ||
| 
						 | 
					53ecdb471e | ||
| 
						 | 
					f80a0c5007 | ||
| 
						 | 
					9e7d7ba67d | ||
| 
						 | 
					b508c0d054 | ||
| 
						 | 
					79788dab44 | ||
| 
						 | 
					8dec946c45 | ||
| 
						 | 
					43ea5ac424 | ||
| 
						 | 
					328ff158cb | ||
| 
						 | 
					2b89d843c3 | ||
| 
						 | 
					45a50483be | ||
| 
						 | 
					c8ae94a062 | ||
| 
						 | 
					7b19143d6f | ||
| 
						 | 
					bc0c889098 | ||
| 
						 | 
					6f8f81866f | ||
| 
						 | 
					f213c99816 | ||
| 
						 | 
					423aca9892 | ||
| 
						 | 
					4840ff887f | ||
| 
						 | 
					61140868b5 | ||
| 
						 | 
					56308dfa5e | ||
| 
						 | 
					8ff25257ca | ||
| 
						 | 
					9ca6853791 | ||
| 
						 | 
					064c486158 | ||
| 
						 | 
					0e58d04b32 | ||
| 
						 | 
					d695614567 | ||
| 
						 | 
					ed13053648 | ||
| 
						 | 
					5cb9e7566e | ||
| 
						 | 
					b00938eab0 | ||
| 
						 | 
					b9495264ee | ||
| 
						 | 
					22ac42221e | ||
| 
						 | 
					559ce2dc88 | ||
| 
						 | 
					75fbabdc0b | ||
| 
						 | 
					b5b7d27abd | ||
| 
						 | 
					1a7bf8dba7 | ||
| 
						 | 
					35bc0d8a5c | ||
| 
						 | 
					b8ff262e01 | ||
| 
						 | 
					c577a4d23a | ||
| 
						 | 
					0f26f359dd | ||
| 
						 | 
					117e820d1e | ||
| 
						 | 
					670e61640f | ||
| 
						 | 
					75aaf4f45b | ||
| 
						 | 
					7161175f03 | ||
| 
						 | 
					cc83b29756 | ||
| 
						 | 
					581cc76625 | ||
| 
						 | 
					184c30d7bb | ||
| 
						 | 
					6057b421ac | ||
| 
						 | 
					fcd8157020 | ||
| 
						 | 
					cd7a6f4ebd | ||
| 
						 | 
					615bf04df6 | ||
| 
						 | 
					819c32edcf | ||
| 
						 | 
					d805fd2d50 | ||
| 
						 | 
					217c680fce | ||
| 
						 | 
					796eed2e2f | ||
| 
						 | 
					29226c81e4 | ||
| 
						 | 
					f7cb82b2f2 | ||
| 
						 | 
					9119b65516 | ||
| 
						 | 
					1540df93e8 | ||
| 
						 | 
					66832e1581 | ||
| 
						 | 
					84238702cf | ||
| 
						 | 
					fd63f7fd31 | ||
| 
						 | 
					8b427e30a2 | ||
| 
						 | 
					af6dff3f57 | ||
| 
						 | 
					7070da80f7 | ||
| 
						 | 
					36f046e589 | ||
| 
						 | 
					166fa840d2 | ||
| 
						 | 
					f4865c933a | ||
| 
						 | 
					6531d550c2 | ||
| 
						 | 
					1e8bf5063f | ||
| 
						 | 
					be8b55f5fd | ||
| 
						 | 
					c28f0cf929 | ||
| 
						 | 
					405ca345be | ||
| 
						 | 
					85ffd8b68c | ||
| 
						 | 
					20227b0cd9 | ||
| 
						 | 
					e66a498889 | ||
| 
						 | 
					a65d22ccb3 | ||
| 
						 | 
					0013f47cbf | ||
| 
						 | 
					69da5c17cf | ||
| 
						 | 
					a199d4e095 | ||
| 
						 | 
					448b5949d8 | ||
| 
						 | 
					82a6d61724 | ||
| 
						 | 
					21ba9f2bb1 | ||
| 
						 | 
					9debb06f21 | ||
| 
						 | 
					1af2afc530 | ||
| 
						 | 
					fc454ad4f9 | ||
| 
						 | 
					99c8eb2900 | ||
| 
						 | 
					18591e2add | ||
| 
						 | 
					0e0cb8a0c7 | ||
| 
						 | 
					f7e791c125 | ||
| 
						 | 
					9e6ef8bb1b | ||
| 
						 | 
					956ec15532 | ||
| 
						 | 
					8721354284 | ||
| 
						 | 
					659fbf9dc1 | ||
| 
						 | 
					1209b772ee | ||
| 
						 | 
					e0e8f5fae6 | ||
| 
						 | 
					d6e5f379a0 | ||
| 
						 | 
					4dda1ee5b3 | ||
| 
						 | 
					abd65c347c | ||
| 
						 | 
					767c0fb9f5 | ||
| 
						 | 
					dc3d6042d5 | ||
| 
						 | 
					fcedf63ef9 | ||
| 
						 | 
					30861ed934 | ||
| 
						 | 
					ee537b3383 | ||
| 
						 | 
					7d9d985142 | ||
| 
						 | 
					daa060c849 | ||
| 
						 | 
					f25047cbe7 | 
							
								
								
									
										112
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										112
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							@@ -13,6 +13,7 @@ on:
 | 
			
		||||
  pull_request:
 | 
			
		||||
    branches:
 | 
			
		||||
      - main
 | 
			
		||||
      - 'release/*'
 | 
			
		||||
 | 
			
		||||
defaults:
 | 
			
		||||
  run:
 | 
			
		||||
@@ -25,45 +26,78 @@ jobs:
 | 
			
		||||
      DOCKER_REGISTRY_URL: tip-tip-wlan-cloud-ucentral.jfrog.io
 | 
			
		||||
      DOCKER_REGISTRY_USERNAME: ucentral
 | 
			
		||||
    steps:
 | 
			
		||||
    - uses: actions/checkout@v2
 | 
			
		||||
 | 
			
		||||
    - name: Build Docker image
 | 
			
		||||
      run: docker build -t wlan-cloud-owsec:${{ github.sha }} .
 | 
			
		||||
 | 
			
		||||
    - name: Tag Docker image
 | 
			
		||||
      run: |
 | 
			
		||||
        TAGS="${{ github.sha }}"
 | 
			
		||||
 | 
			
		||||
        if [[ ${GITHUB_REF} == "refs/heads/"* ]]
 | 
			
		||||
        then
 | 
			
		||||
          CURRENT_TAG=$(echo ${GITHUB_REF#refs/heads/} | tr '/' '-')
 | 
			
		||||
          TAGS="$TAGS $CURRENT_TAG"
 | 
			
		||||
        else
 | 
			
		||||
          if [[ ${GITHUB_REF} == "refs/tags/"* ]]
 | 
			
		||||
          then
 | 
			
		||||
            CURRENT_TAG=$(echo ${GITHUB_REF#refs/tags/} | tr '/' '-')
 | 
			
		||||
            TAGS="$TAGS $CURRENT_TAG"
 | 
			
		||||
          else # PR build
 | 
			
		||||
            CURRENT_TAG=$(echo ${GITHUB_HEAD_REF#refs/heads/} | tr '/' '-')
 | 
			
		||||
            TAGS="$TAGS $CURRENT_TAG"
 | 
			
		||||
          fi
 | 
			
		||||
        fi
 | 
			
		||||
 | 
			
		||||
        echo "Result tags: $TAGS"
 | 
			
		||||
 | 
			
		||||
        for tag in $TAGS; do
 | 
			
		||||
          docker tag wlan-cloud-owsec:${{ github.sha }} ${{ env.DOCKER_REGISTRY_URL }}/owsec:$tag
 | 
			
		||||
        done
 | 
			
		||||
 | 
			
		||||
    - name: Log into Docker registry
 | 
			
		||||
      if: startsWith(github.ref, 'refs/tags/') || startsWith(github.ref, 'refs/pull/') || github.ref == 'refs/heads/main'
 | 
			
		||||
      uses: docker/login-action@v1
 | 
			
		||||
    - name: Checkout actions repo
 | 
			
		||||
      uses: actions/checkout@v2
 | 
			
		||||
      with:
 | 
			
		||||
        registry: ${{ env.DOCKER_REGISTRY_URL }}
 | 
			
		||||
        username: ${{ env.DOCKER_REGISTRY_USERNAME }}
 | 
			
		||||
        password: ${{ secrets.DOCKER_REGISTRY_PASSWORD }}
 | 
			
		||||
        repository: Telecominfraproject/.github
 | 
			
		||||
        path: github
 | 
			
		||||
 | 
			
		||||
    - name: Push Docker images
 | 
			
		||||
      if: startsWith(github.ref, 'refs/tags/') || startsWith(github.ref, 'refs/pull/') || github.ref == 'refs/heads/main'
 | 
			
		||||
    - name: Build and push Docker image
 | 
			
		||||
      uses: ./github/composite-actions/docker-image-build
 | 
			
		||||
      with:
 | 
			
		||||
        image_name: owsec
 | 
			
		||||
        registry: tip-tip-wlan-cloud-ucentral.jfrog.io
 | 
			
		||||
        registry_user: ucentral
 | 
			
		||||
        registry_password: ${{ secrets.DOCKER_REGISTRY_PASSWORD }}
 | 
			
		||||
 | 
			
		||||
    - name: Notify on failure via Slack
 | 
			
		||||
      if: failure() && github.ref == 'refs/heads/main'
 | 
			
		||||
      uses: rtCamp/action-slack-notify@v2
 | 
			
		||||
      env:
 | 
			
		||||
        SLACK_USERNAME: GitHub Actions failure notifier
 | 
			
		||||
        SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}
 | 
			
		||||
        SLACK_COLOR: "${{ job.status }}"
 | 
			
		||||
        SLACK_ICON: https://raw.githubusercontent.com/quintessence/slack-icons/master/images/github-logo-slack-icon.png
 | 
			
		||||
        SLACK_TITLE: Docker build failed for OWSec service
 | 
			
		||||
 | 
			
		||||
  trigger-testing:
 | 
			
		||||
    if: startsWith(github.ref, 'refs/pull/')
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    needs: docker
 | 
			
		||||
    steps:
 | 
			
		||||
    - name: Get base branch name and set as output
 | 
			
		||||
      id: get_base_branch
 | 
			
		||||
      run: |
 | 
			
		||||
        docker images | grep ${{ env.DOCKER_REGISTRY_URL }}/owsec | awk -F ' ' '{print $1":"$2}' | xargs -I {} docker push {}
 | 
			
		||||
        echo ::set-output name=branch::$(echo ${GITHUB_BASE_REF##*/})
 | 
			
		||||
        echo ::set-output name=owgw_branch::$(echo ${GITHUB_BASE_REF##*/} | sed 's/main/master/g')
 | 
			
		||||
 | 
			
		||||
    - name: Checkout actions repo
 | 
			
		||||
      uses: actions/checkout@v2
 | 
			
		||||
      with:
 | 
			
		||||
        repository: Telecominfraproject/.github
 | 
			
		||||
        path: github
 | 
			
		||||
 | 
			
		||||
    - name: Trigger testing of OpenWifi Docker Compose deployment and wait for result
 | 
			
		||||
      uses: ./github/composite-actions/trigger-workflow-and-wait
 | 
			
		||||
      env:
 | 
			
		||||
        BASE_BRANCH: ${{ steps.get_base_branch.outputs.branch }}
 | 
			
		||||
        OWGW_BASE_BRANCH: ${{ steps.get_base_branch.outputs.owgw_branch }}
 | 
			
		||||
      with:
 | 
			
		||||
        owner: Telecominfraproject
 | 
			
		||||
        repo: wlan-testing
 | 
			
		||||
        workflow: ow_docker-compose.yml
 | 
			
		||||
        token: ${{ secrets.WLAN_TESTING_PAT }}
 | 
			
		||||
        ref: master
 | 
			
		||||
        inputs: '{"deployment_version": "${{ env.BASE_BRANCH }}", "owgw_version": "${{ env.OWGW_BASE_BRANCH }}", "owsec_version": "${{ github.sha }}", "owfms_version": "${{ env.BASE_BRANCH }}", "owprov_version": "${{ env.BASE_BRANCH }}", "owanalytics_version": "${{ env.BASE_BRANCH }}", "owsub_version": "${{ env.BASE_BRANCH }}", "microservice": "owsec"}'
 | 
			
		||||
 | 
			
		||||
  trigger-deploy-to-dev:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    if: github.ref == 'refs/heads/main'
 | 
			
		||||
    needs:
 | 
			
		||||
      - docker
 | 
			
		||||
    steps:
 | 
			
		||||
    - name: Checkout actions repo
 | 
			
		||||
      uses: actions/checkout@v2
 | 
			
		||||
      with:
 | 
			
		||||
        repository: Telecominfraproject/.github
 | 
			
		||||
        path: github
 | 
			
		||||
 | 
			
		||||
    - name: Trigger deployment of the latest version to dev instance and wait for result
 | 
			
		||||
      uses: ./github/composite-actions/trigger-workflow-and-wait
 | 
			
		||||
      with:
 | 
			
		||||
        owner: Telecominfraproject
 | 
			
		||||
        repo: wlan-testing
 | 
			
		||||
        workflow: ucentralgw-dev-deployment.yaml
 | 
			
		||||
        token: ${{ secrets.WLAN_TESTING_PAT }}
 | 
			
		||||
        ref: master
 | 
			
		||||
        inputs: '{"force_latest": "true"}'
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										1
									
								
								.github/workflows/cleanup.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.github/workflows/cleanup.yml
									
									
									
									
										vendored
									
									
								
							@@ -4,6 +4,7 @@ on:
 | 
			
		||||
  pull_request:
 | 
			
		||||
    branches:
 | 
			
		||||
      - main
 | 
			
		||||
      - 'release/*'
 | 
			
		||||
    types: [ closed ]
 | 
			
		||||
 | 
			
		||||
defaults:
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										24
									
								
								.github/workflows/enforce-jira-issue-key.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								.github/workflows/enforce-jira-issue-key.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,24 @@
 | 
			
		||||
name: Ensure Jira issue is linked
 | 
			
		||||
 | 
			
		||||
on:
 | 
			
		||||
  pull_request:
 | 
			
		||||
    types: [opened, edited, reopened, synchronize]
 | 
			
		||||
    branches:
 | 
			
		||||
      - 'release/*'
 | 
			
		||||
 | 
			
		||||
jobs:
 | 
			
		||||
  check_for_issue_key:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Checkout actions repo
 | 
			
		||||
        uses: actions/checkout@v2
 | 
			
		||||
        with:
 | 
			
		||||
          repository: Telecominfraproject/.github
 | 
			
		||||
          path: github
 | 
			
		||||
 | 
			
		||||
      - name: Run JIRA check
 | 
			
		||||
        uses: ./github/composite-actions/enforce-jira-issue-key
 | 
			
		||||
        with:
 | 
			
		||||
          jira_base_url: ${{ secrets.TIP_JIRA_URL }}
 | 
			
		||||
          jira_user_email: ${{ secrets.TIP_JIRA_USER_EMAIL }}
 | 
			
		||||
          jira_api_token: ${{ secrets.TIP_JIRA_API_TOKEN }}
 | 
			
		||||
							
								
								
									
										46
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,46 @@
 | 
			
		||||
name: Release chart package
 | 
			
		||||
 | 
			
		||||
on:
 | 
			
		||||
  push:
 | 
			
		||||
    tags:
 | 
			
		||||
      - 'v*'
 | 
			
		||||
 | 
			
		||||
defaults:
 | 
			
		||||
  run:
 | 
			
		||||
    shell: bash
 | 
			
		||||
 | 
			
		||||
jobs:
 | 
			
		||||
  helm-package:
 | 
			
		||||
    runs-on: ubuntu-20.04
 | 
			
		||||
    env:
 | 
			
		||||
      HELM_REPO_URL: https://tip.jfrog.io/artifactory/tip-wlan-cloud-ucentral-helm/
 | 
			
		||||
      HELM_REPO_USERNAME: ucentral
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Checkout uCentral assembly chart repo
 | 
			
		||||
        uses: actions/checkout@v2
 | 
			
		||||
        with:
 | 
			
		||||
          path: wlan-cloud-ucentralsec
 | 
			
		||||
 | 
			
		||||
      - name: Build package
 | 
			
		||||
        working-directory: wlan-cloud-ucentralsec/helm
 | 
			
		||||
        run: |
 | 
			
		||||
          helm plugin install https://github.com/aslafy-z/helm-git --version 0.10.0
 | 
			
		||||
          helm repo add bitnami https://charts.bitnami.com/bitnami
 | 
			
		||||
          helm repo update
 | 
			
		||||
          helm dependency update
 | 
			
		||||
          mkdir dist
 | 
			
		||||
          helm package . -d dist
 | 
			
		||||
 | 
			
		||||
      - name: Generate GitHub release body
 | 
			
		||||
        working-directory: wlan-cloud-ucentralsec/helm
 | 
			
		||||
        run: |
 | 
			
		||||
          pip3 install yq -q
 | 
			
		||||
          echo "Docker image - tip-tip-wlan-cloud-ucentral.jfrog.io/owsec:$GITHUB_REF_NAME" > release.txt
 | 
			
		||||
          echo "Helm charted may be attached to this release" >> release.txt
 | 
			
		||||
          echo "Deployment artifacts may be found in https://github.com/Telecominfraproject/wlan-cloud-ucentral-deploy/tree/$GITHUB_REF_NAME" >> release.txt
 | 
			
		||||
 | 
			
		||||
      - name: Create GitHub release
 | 
			
		||||
        uses: softprops/action-gh-release@v1
 | 
			
		||||
        with:
 | 
			
		||||
          body_path: wlan-cloud-ucentralsec/helm/release.txt
 | 
			
		||||
          files: wlan-cloud-ucentralsec/helm/dist/*
 | 
			
		||||
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -18,3 +18,4 @@ _deps
 | 
			
		||||
*.csr
 | 
			
		||||
/cmake-build/
 | 
			
		||||
/smake-build-debug/
 | 
			
		||||
test_scripts/curl/result.json
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										133
									
								
								CMakeLists.txt
									
									
									
									
									
								
							
							
						
						
									
										133
									
								
								CMakeLists.txt
									
									
									
									
									
								
							@@ -1,5 +1,5 @@
 | 
			
		||||
cmake_minimum_required(VERSION 3.13)
 | 
			
		||||
project(owsec VERSION 2.2.0)
 | 
			
		||||
project(owsec VERSION 2.6.0)
 | 
			
		||||
 | 
			
		||||
set(CMAKE_CXX_STANDARD 17)
 | 
			
		||||
 | 
			
		||||
@@ -20,69 +20,120 @@ endif()
 | 
			
		||||
 | 
			
		||||
# Auto build increment. You must define BUILD_INCREMENT with cmake -DBUILD_INCREMENT=1
 | 
			
		||||
if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/build)
 | 
			
		||||
    file(READ build BUILD_NUM)
 | 
			
		||||
    file(READ ${CMAKE_CURRENT_SOURCE_DIR}/build BUILD_NUM)
 | 
			
		||||
    if(BUILD_INCREMENT)
 | 
			
		||||
        MATH(EXPR BUILD_NUM "${BUILD_NUM}+1")
 | 
			
		||||
        file(WRITE build ${BUILD_NUM})
 | 
			
		||||
        file(WRITE ${CMAKE_CURRENT_SOURCE_DIR}/build ${BUILD_NUM})
 | 
			
		||||
    endif()
 | 
			
		||||
else()
 | 
			
		||||
    set(BUILD_NUM 1)
 | 
			
		||||
    file(WRITE build ${BUILD_NUM})
 | 
			
		||||
    file(WRITE ${CMAKE_CURRENT_SOURCE_DIR}/build ${BUILD_NUM})
 | 
			
		||||
endif()
 | 
			
		||||
 | 
			
		||||
find_package(Git QUIET)
 | 
			
		||||
if(GIT_FOUND AND EXISTS "${PROJECT_SOURCE_DIR}/.git")
 | 
			
		||||
    execute_process(COMMAND ${GIT_EXECUTABLE} describe --always --tags
 | 
			
		||||
            WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
 | 
			
		||||
            RESULT_VARIABLE GIT_RESULT
 | 
			
		||||
            OUTPUT_VARIABLE GIT_HASH)
 | 
			
		||||
    if(NOT GIT_RESULT EQUAL "0")
 | 
			
		||||
        message(FATAL_ERROR "git describe --always --tags failed with ${GIT_RESULT}")
 | 
			
		||||
    endif()
 | 
			
		||||
    string(REGEX REPLACE "\n$" "" GIT_HASH "${GIT_HASH}")
 | 
			
		||||
endif()
 | 
			
		||||
 | 
			
		||||
add_definitions(-DAWS_CUSTOM_MEMORY_MANAGEMENT)
 | 
			
		||||
 | 
			
		||||
set(BUILD_SHARED_LIBS 1)
 | 
			
		||||
 | 
			
		||||
add_definitions(-DAPP_VERSION="${CMAKE_PROJECT_VERSION}" -DBUILD_NUMBER="${BUILD_NUM}")
 | 
			
		||||
add_definitions(-DTIP_SECURITY_SERVICE="1")
 | 
			
		||||
 | 
			
		||||
set(Boost_USE_STATIC_LIBS OFF)
 | 
			
		||||
set(Boost_USE_MULTITHREADED ON)
 | 
			
		||||
set(Boost_USE_STATIC_RUNTIME OFF)
 | 
			
		||||
find_package(Boost REQUIRED system)
 | 
			
		||||
add_compile_options(-Wall -Wextra)
 | 
			
		||||
if(ASAN)
 | 
			
		||||
    add_compile_options(-fsanitize=address)
 | 
			
		||||
    add_link_options(-fsanitize=address)
 | 
			
		||||
endif()
 | 
			
		||||
 | 
			
		||||
find_package(OpenSSL REQUIRED)
 | 
			
		||||
find_package(ZLIB REQUIRED)
 | 
			
		||||
 | 
			
		||||
find_package(fmt  REQUIRED)
 | 
			
		||||
find_package(AWSSDK     REQUIRED COMPONENTS sns)
 | 
			
		||||
find_package(nlohmann_json  REQUIRED)
 | 
			
		||||
find_package(CppKafka REQUIRED)
 | 
			
		||||
find_package(PostgreSQL REQUIRED)
 | 
			
		||||
find_package(MySQL REQUIRED)
 | 
			
		||||
find_package(Poco REQUIRED COMPONENTS JSON Crypto JWT Net Util NetSSL Data DataSQLite DataPostgreSQL DataMySQL)
 | 
			
		||||
 | 
			
		||||
include_directories(/usr/local/include  /usr/local/opt/openssl/include src include/kafka /usr/local/opt/mysql-client/include)
 | 
			
		||||
 | 
			
		||||
configure_file(src/ow_version.h.in ${PROJECT_SOURCE_DIR}/src/ow_version.h @ONLY)
 | 
			
		||||
 | 
			
		||||
add_executable( owsec
 | 
			
		||||
                build
 | 
			
		||||
                src/Daemon.h src/Daemon.cpp
 | 
			
		||||
                src/MicroService.cpp src/MicroService.h
 | 
			
		||||
                src/SubSystemServer.cpp src/SubSystemServer.h
 | 
			
		||||
                src/RESTAPI_oauth2Handler.h src/RESTAPI_oauth2Handler.cpp
 | 
			
		||||
                src/RESTAPI_handler.h src/RESTAPI_handler.cpp
 | 
			
		||||
                src/RESTAPI_server.cpp src/RESTAPI_server.h
 | 
			
		||||
                src/RESTAPI_SecurityObjects.cpp src/RESTAPI_SecurityObjects.h
 | 
			
		||||
                src/RESTAPI_system_command.h src/RESTAPI_system_command.cpp
 | 
			
		||||
                src/RESTAPI_protocol.h
 | 
			
		||||
                src/AuthService.h src/AuthService.cpp
 | 
			
		||||
                src/KafkaManager.h src/KafkaManager.cpp
 | 
			
		||||
                src/StorageService.cpp src/StorageService.h
 | 
			
		||||
                src/Utils.cpp src/Utils.h
 | 
			
		||||
                src/storage_setup.cpp
 | 
			
		||||
                src/storage_tables.cpp src/SMTPMailerService.cpp src/SMTPMailerService.h
 | 
			
		||||
                src/RESTAPI_users_handler.cpp src/RESTAPI_users_handler.h
 | 
			
		||||
                src/RESTAPI_user_handler.cpp src/RESTAPI_user_handler.h
 | 
			
		||||
                src/RESTAPI_action_links.cpp src/RESTAPI_action_links.h src/storage_users.cpp
 | 
			
		||||
                src/RESTAPI_InternalServer.cpp src/RESTAPI_InternalServer.h
 | 
			
		||||
                src/RESTAPI_validateToken_handler.cpp src/RESTAPI_validateToken_handler.h
 | 
			
		||||
                src/RESTAPI_systemEndpoints_handler.cpp src/RESTAPI_systemEndpoints_handler.h
 | 
			
		||||
                src/RESTAPI_AssetServer.cpp src/RESTAPI_AssetServer.h
 | 
			
		||||
                src/RESTAPI_avatarHandler.cpp src/RESTAPI_avatarHandler.h
 | 
			
		||||
                src/storage_avatar.cpp src/storage_avatar.h src/storage_users.h
 | 
			
		||||
                src/OpenWifiTypes.h src/RESTAPI_email_handler.cpp src/RESTAPI_email_handler.h
 | 
			
		||||
                src/storage_tokens.cpp
 | 
			
		||||
                src/RESTAPI_GenericServer.h src/RESTAPI_GenericServer.cpp
 | 
			
		||||
                src/RESTAPI_errors.h
 | 
			
		||||
                )
 | 
			
		||||
        build
 | 
			
		||||
        src/ow_version.h.in
 | 
			
		||||
        src/framework/CountryCodes.h
 | 
			
		||||
        src/framework/KafkaTopics.h
 | 
			
		||||
        src/framework/MicroService.h
 | 
			
		||||
        src/framework/orm.h
 | 
			
		||||
        src/framework/StorageClass.h
 | 
			
		||||
        src/framework/ow_constants.h
 | 
			
		||||
        src/framework/WebSocketClientNotifications.h
 | 
			
		||||
        src/seclibs/qrcode/qrcodegen.hpp src/seclibs/qrcode/qrcodegen.cpp
 | 
			
		||||
        src/seclibs/cpptotp/bytes.cpp src/seclibs/cpptotp/bytes.h
 | 
			
		||||
        src/seclibs/cpptotp/otp.cpp src/seclibs/cpptotp/otp.h
 | 
			
		||||
        src/seclibs/cpptotp/sha1.cpp src/seclibs/cpptotp/sha1.h
 | 
			
		||||
        src/RESTObjects/RESTAPI_SecurityObjects.h src/RESTObjects/RESTAPI_SecurityObjects.cpp
 | 
			
		||||
        src/RESTObjects/RESTAPI_ProvObjects.cpp src/RESTObjects/RESTAPI_ProvObjects.h
 | 
			
		||||
        src/RESTObjects/RESTAPI_GWobjects.h src/RESTObjects/RESTAPI_GWobjects.cpp
 | 
			
		||||
        src/RESTObjects/RESTAPI_FMSObjects.h src/RESTObjects/RESTAPI_FMSObjects.cpp
 | 
			
		||||
        src/RESTAPI/RESTAPI_oauth2_handler.h src/RESTAPI/RESTAPI_oauth2_handler.cpp
 | 
			
		||||
        src/RESTAPI/RESTAPI_users_handler.cpp src/RESTAPI/RESTAPI_users_handler.h
 | 
			
		||||
        src/RESTAPI/RESTAPI_user_handler.cpp src/RESTAPI/RESTAPI_user_handler.h
 | 
			
		||||
        src/RESTAPI/RESTAPI_action_links.cpp src/RESTAPI/RESTAPI_action_links.h
 | 
			
		||||
        src/RESTAPI/RESTAPI_validate_token_handler.cpp src/RESTAPI/RESTAPI_validate_token_handler.h
 | 
			
		||||
        src/RESTAPI/RESTAPI_system_endpoints_handler.cpp src/RESTAPI/RESTAPI_system_endpoints_handler.h
 | 
			
		||||
        src/RESTAPI/RESTAPI_asset_server.cpp src/RESTAPI/RESTAPI_asset_server.h
 | 
			
		||||
        src/RESTAPI/RESTAPI_avatar_handler.cpp src/RESTAPI/RESTAPI_avatar_handler.h
 | 
			
		||||
        src/RESTAPI/RESTAPI_subavatar_handler.cpp src/RESTAPI/RESTAPI_subavatar_handler.h
 | 
			
		||||
        src/RESTAPI/RESTAPI_email_handler.cpp src/RESTAPI/RESTAPI_email_handler.h
 | 
			
		||||
        src/RESTAPI/RESTAPI_sms_handler.cpp src/RESTAPI/RESTAPI_sms_handler.h
 | 
			
		||||
        src/RESTAPI/RESTAPI_suboauth2_handler.h src/RESTAPI/RESTAPI_suboauth2_handler.cpp
 | 
			
		||||
        src/RESTAPI/RESTAPI_subuser_handler.h src/RESTAPI/RESTAPI_subuser_handler.cpp
 | 
			
		||||
        src/RESTAPI/RESTAPI_subusers_handler.h src/RESTAPI/RESTAPI_subusers_handler.cpp
 | 
			
		||||
        src/RESTAPI/RESTAPI_validate_sub_token_handler.cpp src/RESTAPI/RESTAPI_validate_sub_token_handler.h
 | 
			
		||||
        src/RESTAPI/RESTAPI_submfa_handler.cpp src/RESTAPI/RESTAPI_submfa_handler.h
 | 
			
		||||
        src/RESTAPI/RESTAPI_preferences.cpp src/RESTAPI/RESTAPI_preferences.h
 | 
			
		||||
        src/RESTAPI/RESTAPI_subpreferences.cpp src/RESTAPI/RESTAPI_subpreferences.h
 | 
			
		||||
        src/RESTAPI/RESTAPI_routers.cpp
 | 
			
		||||
        src/Daemon.h src/Daemon.cpp
 | 
			
		||||
        src/SpecialUserHelpers.h
 | 
			
		||||
        src/AuthService.h src/AuthService.cpp
 | 
			
		||||
        src/StorageService.cpp src/StorageService.h
 | 
			
		||||
        src/SMTPMailerService.cpp src/SMTPMailerService.h
 | 
			
		||||
        src/SMSSender.cpp src/SMSSender.h
 | 
			
		||||
        src/MFAServer.cpp src/MFAServer.h
 | 
			
		||||
        src/SMS_provider_aws.cpp src/SMS_provider_aws.h
 | 
			
		||||
        src/SMS_provider.cpp src/SMS_provider.h
 | 
			
		||||
        src/SMS_provider_twilio.cpp src/SMS_provider_twilio.h
 | 
			
		||||
        src/ActionLinkManager.cpp src/ActionLinkManager.h
 | 
			
		||||
        src/ACLProcessor.h
 | 
			
		||||
        src/framework/OpenWifiTypes.h
 | 
			
		||||
        src/storage/orm_users.cpp src/storage/orm_users.h
 | 
			
		||||
        src/storage/orm_tokens.cpp src/storage/orm_tokens.h
 | 
			
		||||
        src/storage/orm_preferences.cpp src/storage/orm_preferences.h
 | 
			
		||||
        src/storage/orm_actionLinks.cpp src/storage/orm_actionLinks.h
 | 
			
		||||
        src/storage/orm_avatar.cpp src/storage/orm_avatar.h
 | 
			
		||||
        src/SpecialUserHelpers.h
 | 
			
		||||
        src/RESTAPI/RESTAPI_db_helpers.h src/storage/orm_logins.cpp src/storage/orm_logins.h src/RESTAPI/RESTAPI_totp_handler.cpp src/RESTAPI/RESTAPI_totp_handler.h src/TotpCache.h src/RESTAPI/RESTAPI_subtotp_handler.cpp src/RESTAPI/RESTAPI_subtotp_handler.h src/RESTAPI/RESTAPI_signup_handler.cpp src/RESTAPI/RESTAPI_signup_handler.h)
 | 
			
		||||
 | 
			
		||||
if(NOT SMALL_BUILD)
 | 
			
		||||
    target_link_libraries(owsec PUBLIC
 | 
			
		||||
            ${Poco_LIBRARIES} ${Boost_LIBRARIES} ${MySQL_LIBRARIES}  ${ZLIB_LIBRARIES}
 | 
			
		||||
            ${Poco_LIBRARIES}
 | 
			
		||||
            ${MySQL_LIBRARIES}
 | 
			
		||||
            ${ZLIB_LIBRARIES}
 | 
			
		||||
            CppKafka::cppkafka
 | 
			
		||||
            ${AWSSDK_LINK_LIBRARIES}
 | 
			
		||||
            fmt::fmt
 | 
			
		||||
            )
 | 
			
		||||
    if(UNIX AND NOT APPLE)
 | 
			
		||||
        target_link_libraries(owsec PUBLIC PocoJSON)
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										105
									
								
								Dockerfile
									
									
									
									
									
								
							
							
						
						
									
										105
									
								
								Dockerfile
									
									
									
									
									
								
							@@ -1,15 +1,39 @@
 | 
			
		||||
FROM alpine AS builder
 | 
			
		||||
FROM alpine:3.15 AS build-base
 | 
			
		||||
 | 
			
		||||
RUN apk add --update --no-cache \
 | 
			
		||||
    openssl openssh \
 | 
			
		||||
    ncurses-libs \
 | 
			
		||||
    bash util-linux coreutils curl \
 | 
			
		||||
    make cmake gcc g++ libstdc++ libgcc git zlib-dev \
 | 
			
		||||
    openssl-dev boost-dev unixodbc-dev postgresql-dev mariadb-dev \
 | 
			
		||||
    apache2-utils yaml-dev apr-util-dev \
 | 
			
		||||
    librdkafka-dev
 | 
			
		||||
    make cmake g++ git \
 | 
			
		||||
    unixodbc-dev postgresql-dev mariadb-dev \
 | 
			
		||||
    librdkafka-dev boost-dev openssl-dev \
 | 
			
		||||
    zlib-dev nlohmann-json \
 | 
			
		||||
    curl-dev
 | 
			
		||||
 | 
			
		||||
FROM build-base AS poco-build
 | 
			
		||||
 | 
			
		||||
ADD https://api.github.com/repos/stephb9959/poco/git/refs/heads/master version.json
 | 
			
		||||
RUN git clone https://github.com/stephb9959/poco /poco
 | 
			
		||||
 | 
			
		||||
WORKDIR /poco
 | 
			
		||||
RUN mkdir cmake-build
 | 
			
		||||
WORKDIR cmake-build
 | 
			
		||||
RUN cmake ..
 | 
			
		||||
RUN cmake --build . --config Release -j8
 | 
			
		||||
RUN cmake --build . --target install
 | 
			
		||||
 | 
			
		||||
FROM build-base AS fmtlib-build
 | 
			
		||||
 | 
			
		||||
ADD https://api.github.com/repos/fmtlib/fmt/git/refs/heads/master version.json
 | 
			
		||||
RUN git clone https://github.com/fmtlib/fmt /fmtlib
 | 
			
		||||
 | 
			
		||||
WORKDIR /fmtlib
 | 
			
		||||
RUN mkdir cmake-build
 | 
			
		||||
WORKDIR cmake-build
 | 
			
		||||
RUN cmake ..
 | 
			
		||||
RUN make
 | 
			
		||||
RUN make install
 | 
			
		||||
 | 
			
		||||
FROM build-base AS cppkafka-build
 | 
			
		||||
 | 
			
		||||
ADD https://api.github.com/repos/stephb9959/cppkafka/git/refs/heads/master version.json
 | 
			
		||||
RUN git clone https://github.com/stephb9959/cppkafka /cppkafka
 | 
			
		||||
 | 
			
		||||
WORKDIR /cppkafka
 | 
			
		||||
@@ -19,24 +43,61 @@ RUN cmake ..
 | 
			
		||||
RUN cmake --build . --config Release -j8
 | 
			
		||||
RUN cmake --build . --target install
 | 
			
		||||
 | 
			
		||||
WORKDIR /poco
 | 
			
		||||
FROM build-base AS json-schema-validator-build
 | 
			
		||||
 | 
			
		||||
ADD https://api.github.com/repos/pboettch/json-schema-validator/git/refs/heads/master version.json
 | 
			
		||||
RUN git clone https://github.com/pboettch/json-schema-validator /json-schema-validator
 | 
			
		||||
 | 
			
		||||
WORKDIR /json-schema-validator
 | 
			
		||||
RUN mkdir cmake-build
 | 
			
		||||
WORKDIR cmake-build
 | 
			
		||||
RUN cmake ..
 | 
			
		||||
RUN make
 | 
			
		||||
RUN make install
 | 
			
		||||
 | 
			
		||||
FROM build-base AS aws-sdk-cpp-build
 | 
			
		||||
 | 
			
		||||
ADD https://api.github.com/repos/aws/aws-sdk-cpp/git/refs/heads/main version.json
 | 
			
		||||
RUN git clone --recurse-submodules https://github.com/aws/aws-sdk-cpp /aws-sdk-cpp
 | 
			
		||||
 | 
			
		||||
WORKDIR /aws-sdk-cpp
 | 
			
		||||
RUN mkdir cmake-build
 | 
			
		||||
WORKDIR cmake-build
 | 
			
		||||
RUN cmake .. -DBUILD_ONLY="sns;s3" \
 | 
			
		||||
             -DCMAKE_BUILD_TYPE=Release \
 | 
			
		||||
             -DCMAKE_CXX_FLAGS="-Wno-error=stringop-overflow -Wno-error=uninitialized" \
 | 
			
		||||
             -DAUTORUN_UNIT_TESTS=OFF
 | 
			
		||||
RUN cmake --build . --config Release -j8
 | 
			
		||||
RUN cmake --build . --target install
 | 
			
		||||
 | 
			
		||||
FROM build-base AS owsec-build
 | 
			
		||||
 | 
			
		||||
ADD CMakeLists.txt build /owsec/
 | 
			
		||||
ADD cmake /owsec/cmake
 | 
			
		||||
ADD src /owsec/src
 | 
			
		||||
ADD .git /owsec/.git
 | 
			
		||||
 | 
			
		||||
COPY --from=poco-build /usr/local/include /usr/local/include
 | 
			
		||||
COPY --from=poco-build /usr/local/lib /usr/local/lib
 | 
			
		||||
COPY --from=cppkafka-build /usr/local/include /usr/local/include
 | 
			
		||||
COPY --from=cppkafka-build /usr/local/lib /usr/local/lib
 | 
			
		||||
COPY --from=json-schema-validator-build /usr/local/include /usr/local/include
 | 
			
		||||
COPY --from=json-schema-validator-build /usr/local/lib /usr/local/lib
 | 
			
		||||
COPY --from=aws-sdk-cpp-build /usr/local/include /usr/local/include
 | 
			
		||||
COPY --from=aws-sdk-cpp-build /usr/local/lib /usr/local/lib
 | 
			
		||||
 | 
			
		||||
COPY --from=fmtlib-build /usr/local/include /usr/local/include
 | 
			
		||||
COPY --from=fmtlib-build /usr/local/lib /usr/local/lib
 | 
			
		||||
 | 
			
		||||
WORKDIR /owsec
 | 
			
		||||
RUN mkdir cmake-build
 | 
			
		||||
WORKDIR /owsec/cmake-build
 | 
			
		||||
RUN cmake ..
 | 
			
		||||
RUN cmake .. \
 | 
			
		||||
          -Dcrypto_LIBRARY=/usr/lib/libcrypto.so \
 | 
			
		||||
          -DBUILD_SHARED_LIBS=ON
 | 
			
		||||
RUN cmake --build . --config Release -j8
 | 
			
		||||
 | 
			
		||||
FROM alpine
 | 
			
		||||
FROM alpine:3.15
 | 
			
		||||
 | 
			
		||||
ENV OWSEC_USER=owsec \
 | 
			
		||||
    OWSEC_ROOT=/owsec-data \
 | 
			
		||||
@@ -48,16 +109,28 @@ RUN addgroup -S "$OWSEC_USER" && \
 | 
			
		||||
RUN mkdir /openwifi
 | 
			
		||||
RUN mkdir -p "$OWSEC_ROOT" "$OWSEC_CONFIG" && \
 | 
			
		||||
    chown "$OWSEC_USER": "$OWSEC_ROOT" "$OWSEC_CONFIG"
 | 
			
		||||
RUN apk add --update --no-cache librdkafka mariadb-connector-c libpq unixodbc su-exec gettext ca-certificates
 | 
			
		||||
COPY --from=builder /owsec/cmake-build/owsec /openwifi/owsec
 | 
			
		||||
COPY --from=builder /cppkafka/cmake-build/src/lib/* /lib/
 | 
			
		||||
COPY --from=builder /poco/cmake-build/lib/* /lib/
 | 
			
		||||
 | 
			
		||||
COPY owsec.properties.tmpl ${OWSEC_CONFIG}/
 | 
			
		||||
RUN apk add --update --no-cache librdkafka su-exec gettext ca-certificates bash jq curl \
 | 
			
		||||
    mariadb-connector-c libpq unixodbc postgresql-client
 | 
			
		||||
 | 
			
		||||
COPY readiness_check /readiness_check
 | 
			
		||||
COPY test_scripts/curl/cli /cli
 | 
			
		||||
 | 
			
		||||
COPY owsec.properties.tmpl /
 | 
			
		||||
COPY wwwassets /dist/wwwassets
 | 
			
		||||
COPY templates /dist/templates
 | 
			
		||||
COPY docker-entrypoint.sh /
 | 
			
		||||
COPY wait-for-postgres.sh /
 | 
			
		||||
RUN wget https://raw.githubusercontent.com/Telecominfraproject/wlan-cloud-ucentral-deploy/main/docker-compose/certs/restapi-ca.pem \
 | 
			
		||||
    -O /usr/local/share/ca-certificates/restapi-ca-selfsigned.pem
 | 
			
		||||
 | 
			
		||||
COPY --from=owsec-build /owsec/cmake-build/owsec /openwifi/owsec
 | 
			
		||||
COPY --from=cppkafka-build /cppkafka/cmake-build/src/lib/* /usr/local/lib
 | 
			
		||||
COPY --from=poco-build /poco/cmake-build/lib/* /usr/local/lib
 | 
			
		||||
COPY --from=aws-sdk-cpp-build /aws-sdk-cpp/cmake-build/aws-cpp-sdk-core/libaws-cpp-sdk-core.so /usr/local/lib
 | 
			
		||||
COPY --from=aws-sdk-cpp-build /aws-sdk-cpp/cmake-build/aws-cpp-sdk-s3/libaws-cpp-sdk-s3.so /usr/local/lib
 | 
			
		||||
COPY --from=aws-sdk-cpp-build /aws-sdk-cpp/cmake-build/aws-cpp-sdk-sns/libaws-cpp-sdk-sns.so /usr/local/lib
 | 
			
		||||
 | 
			
		||||
EXPOSE 16001 17001 16101
 | 
			
		||||
 | 
			
		||||
ENTRYPOINT ["/docker-entrypoint.sh"]
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										114
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										114
									
								
								README.md
									
									
									
									
									
								
							@@ -13,12 +13,12 @@ into your own systems. If all you need it to access the uCentralGW for example (
 | 
			
		||||
- choose one to manage (pick an endpoint that matches what you are trying to do by looking at its `type`. For the gateway, type = ucentrtalgw)
 | 
			
		||||
- make your calls (use the PublicEndPoint of the corresponding entry to make your calls, do not forget to add `/api/v1` as the root os the call)
 | 
			
		||||
 | 
			
		||||
The CLI for the [uCentralGW](https://github.com/telecominfraproject/wlan-cloud-ucentralgw/blob/main/test_scripts/curl/cli) has a very good example of this. Loog for the `setgateway` 
 | 
			
		||||
function.
 | 
			
		||||
The CLI for the [uCentralGW](https://github.com/telecominfraproject/wlan-cloud-ucentralgw/blob/main/test_scripts/curl/cli) has a very good example of this. 
 | 
			
		||||
Look for the `setgateway` function.
 | 
			
		||||
 | 
			
		||||
## Firewall Considerations
 | 
			
		||||
The entire uCentral systems uses several MicroServices. In order for the whole system to work, you should provide the following port
 | 
			
		||||
access
 | 
			
		||||
access:
 | 
			
		||||
 | 
			
		||||
- Security
 | 
			
		||||
  - Properties file: owsec.properties
 | 
			
		||||
@@ -28,14 +28,21 @@ access
 | 
			
		||||
    - ALB: 16101
 | 
			
		||||
 | 
			
		||||
- Gateway:
 | 
			
		||||
  - Properties file: ucentralgw.properties
 | 
			
		||||
  - Properties file: owgw.properties
 | 
			
		||||
  - Ports
 | 
			
		||||
    - Public: 16002
 | 
			
		||||
    - Private: 17002
 | 
			
		||||
    - ALB: 16102
 | 
			
		||||
 | 
			
		||||
- Firmware:
 | 
			
		||||
  - Properties file: ucentralfms.properties
 | 
			
		||||
  - Properties file: owfms.properties
 | 
			
		||||
  - Ports
 | 
			
		||||
    - Public: 16004
 | 
			
		||||
    - Private: 17004
 | 
			
		||||
    - ALB: 16104
 | 
			
		||||
 | 
			
		||||
- Provisioning:
 | 
			
		||||
  - Properties file: owprov.properties
 | 
			
		||||
  - Ports
 | 
			
		||||
    - Public: 16004
 | 
			
		||||
    - Private: 17004
 | 
			
		||||
@@ -79,7 +86,6 @@ Is this safe to show the hash in a text file? Let me put it this way, if you can
 | 
			
		||||
would have control over the entire internet. It's incredibly safe. If you love math, you can find a lot of videos explaining
 | 
			
		||||
how hashes work and why they are safe.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### `authentication.validation.expression`
 | 
			
		||||
This is a regular expression (regex) to verify the incoming password. You can find many examples on the internet on how to create these expressions. I suggest 
 | 
			
		||||
that using Google is your friend. Someone has figured out what you want to do already. Click [here](https://stackoverflow.com/questions/19605150/regex-for-password-must-contain-at-least-eight-characters-at-least-one-number-a)
 | 
			
		||||
@@ -92,11 +98,45 @@ to get a sample. The default is
 | 
			
		||||
### `authentication.oldpasswords`
 | 
			
		||||
The number of older passwords to keep. Default is 5.
 | 
			
		||||
 | 
			
		||||
### Changing default password
 | 
			
		||||
 | 
			
		||||
On the first startup of the service new user will be created with the default credentials from properties `authentication.default.username` and `authentication.default.password`, but **you will have to change the password** before making any real requests.
 | 
			
		||||
 | 
			
		||||
You can this using [owgw-ui](https://github.com/Telecominfraproject/wlan-cloud-ucentralgw-ui/) on first login or using the following script:
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
export OWSEC=openwifi.wlan.local:16001 # endpoint to your owsec RESTAPI endpoint
 | 
			
		||||
#export FLAGS="-k" # uncomment and add curl flags that you would like to pass for the request (for example '-k' may be used to pass errors with self-signed certificates)
 | 
			
		||||
export OWSEC_DEFAULT_USERNAME=root@system.com # default username that you've set in property 'authentication.default.username'
 | 
			
		||||
export OWSEC_DEFAULT_PASSWORD=weLoveWifi # default password __in cleartext__ from property 'authentication.default.password'
 | 
			
		||||
export OWSEC_NEW_PASSWORD=NewPass123% # new password that must be set for the user (must comply with 'authentication.validation.expression')
 | 
			
		||||
test_scripts/curl/cli testlogin $OWSEC_DEFAULT_USERNAME $OWSEC_DEFAULT_PASSWORD $OWSEC_NEW_PASSWORD
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
CLI is also included in Docker image if you want to run it this way:
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
export OWSEC=openwifi.wlan.local:16001
 | 
			
		||||
#export FLAGS="-k"
 | 
			
		||||
export OWSEC_DEFAULT_USERNAME=root@system.com
 | 
			
		||||
export OWSEC_DEFAULT_PASSWORD=weLoveWifi
 | 
			
		||||
export OWSEC_NEW_PASSWORD=NewPass123%
 | 
			
		||||
docker run --rm -ti \
 | 
			
		||||
  --network=host \
 | 
			
		||||
  --env OWSEC \
 | 
			
		||||
  --env FLAGS \
 | 
			
		||||
  --env OWSEC_DEFAULT_USERNAME \
 | 
			
		||||
  --env OWSEC_DEFAULT_PASSWORD \
 | 
			
		||||
  --env OWSEC_NEW_PASSWORD \
 | 
			
		||||
  tip-tip-wlan-cloud-ucentral.jfrog.io/owsec:main \
 | 
			
		||||
  /cli testlogin $OWSEC_DEFAULT_USERNAME $OWSEC_DEFAULT_PASSWORD $OWSEC_NEW_PASSWORD
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### Kafka integration
 | 
			
		||||
This security service uses Kafka to coordinate security with other services that are part of the system. You must have a Kafka service running
 | 
			
		||||
in order to use this. You can find several examples of Kafka services available with Docker. Here are the values you need to configure.
 | 
			
		||||
 | 
			
		||||
```asm
 | 
			
		||||
```
 | 
			
		||||
openwifi.kafka.group.id = security
 | 
			
		||||
openwifi.kafka.client.id = security1
 | 
			
		||||
openwifi.kafka.enable = true
 | 
			
		||||
@@ -126,7 +166,7 @@ Here are the parameters for the public interface. The important files are:
 | 
			
		||||
- `restapi-key.pem` : the key associated with this certificate
 | 
			
		||||
- `openwifi.restapi.host.0.key.password` : if you key is password protected, you may supply that password here.
 | 
			
		||||
 | 
			
		||||
```asm
 | 
			
		||||
```
 | 
			
		||||
openwifi.restapi.host.0.backlog = 100
 | 
			
		||||
openwifi.restapi.host.0.security = relaxed
 | 
			
		||||
openwifi.restapi.host.0.rootca = $OWSEC_ROOT/certs/restapi-ca.pem
 | 
			
		||||
@@ -141,7 +181,7 @@ openwifi.restapi.host.0.key.password = mypassword
 | 
			
		||||
The private interface is used for service-to-service communication. You can use self-signed certificates here or letsencrypt. The file names are similar 
 | 
			
		||||
to the filenames used in the previous section.
 | 
			
		||||
 | 
			
		||||
```asm
 | 
			
		||||
```
 | 
			
		||||
openwifi.internal.restapi.host.0.backlog = 100
 | 
			
		||||
openwifi.internal.restapi.host.0.security = relaxed
 | 
			
		||||
openwifi.internal.restapi.host.0.rootca = $OWSEC_ROOT/certs/restapi-ca.pem
 | 
			
		||||
@@ -156,14 +196,15 @@ openwifi.internal.restapi.host.0.key.password = mypassword
 | 
			
		||||
Here are other important values you must set.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
```asm
 | 
			
		||||
```
 | 
			
		||||
openwifi.system.data = $OWSEC_ROOT/data
 | 
			
		||||
openwifi.system.uri.private = https://localhost:17001
 | 
			
		||||
openwifi.system.uri.public = https://openwifi.dpaas.arilia.com:16001
 | 
			
		||||
openwifi.system.uri.ui = https://ucentral-ui.arilia.com
 | 
			
		||||
openwifi.system.commandchannel = /tmp/app.ucentralsec
 | 
			
		||||
openwifi.service.key = $OWSEC_ROOT/certs/restapi-key.pem
 | 
			
		||||
openwifi.service.key.password = mypassword
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### `openwifi.system.data`
 | 
			
		||||
The location of some important data files including the user name database.
 | 
			
		||||
 | 
			
		||||
@@ -173,3 +214,54 @@ This is the FQDN used internally between services.
 | 
			
		||||
#### `openwifi.system.uri.public`
 | 
			
		||||
This is the FQDN used externally serving the OpenAPI interface.
 | 
			
		||||
 | 
			
		||||
### Sending SMS for Multifactor Aithentication
 | 
			
		||||
`owsec` hs the ability to send SMS messages to users during login or to send notifications. In order to do so,
 | 
			
		||||
an SMS provider must be configured. At present time, 2 providers are supported: Tilio and AWS SNS
 | 
			
		||||
 | 
			
		||||
#### AWS SMS
 | 
			
		||||
For SNS you must create an IAM ID that has sns:sendmessage rights.  
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
smssender.enabled = true
 | 
			
		||||
smssender.provider = aws
 | 
			
		||||
smssender.aws.secretkey = ***************************************
 | 
			
		||||
smssender.aws.accesskey = ***************************************
 | 
			
		||||
smssender.aws.region = **************
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### Twilio
 | 
			
		||||
For Twilio, you must provide the following
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
smssender.enabled = true
 | 
			
		||||
smssender.provider = twilio
 | 
			
		||||
smssender.twilio.sid = ***********************
 | 
			
		||||
smssender.twilio.token = **********************
 | 
			
		||||
smssender.twilio.phonenumber = +18888888888
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### `owsec` Messaging Configuration
 | 
			
		||||
`owsec` nay require to send e-mails. In order to do so, you must configure an email sender. We have run tests 
 | 
			
		||||
with GMail and AWS SES. For each, you must obtain the proper credentials and insert them in this configuration as well
 | 
			
		||||
as the proper mail host.
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
mailer.enabled = true
 | 
			
		||||
mailer.hostname = smtp.gmail.com
 | 
			
		||||
mailer.username = ************************
 | 
			
		||||
mailer.password = ************************
 | 
			
		||||
mailer.sender = OpenWIFI
 | 
			
		||||
mailer.loginmethod = login
 | 
			
		||||
mailer.port = 587
 | 
			
		||||
mailer.templates = $OWSEC_ROOT/templates
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
#### Google Authenticator
 | 
			
		||||
In order to use the Google Time-based One-Time Password (TOTP), the user must download the Google Authenticator 
 | 
			
		||||
on any other app that support the TOTP protocol. You should include the following in your configuration
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
totp.issuer = OrgName
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
It is very important that you not use spaces in your OrgName.
 | 
			
		||||
@@ -11,7 +11,7 @@ if [[ "$TEMPLATE_CONFIG" = 'true' && ! -f "$OWSEC_CONFIG"/owsec.properties ]]; t
 | 
			
		||||
  RESTAPI_HOST_CERT=${RESTAPI_HOST_CERT:-"\$OWSEC_ROOT/certs/restapi-cert.pem"} \
 | 
			
		||||
  RESTAPI_HOST_KEY=${RESTAPI_HOST_KEY:-"\$OWSEC_ROOT/certs/restapi-key.pem"} \
 | 
			
		||||
  RESTAPI_HOST_KEY_PASSWORD=${RESTAPI_HOST_KEY_PASSWORD:-"mypassword"} \
 | 
			
		||||
  RESTAPI_WWWASSETS=${RESTAPI_WWWASSETS:-"\$OWSEC_ROOT/wwwassets"} \
 | 
			
		||||
  RESTAPI_WWWASSETS=${RESTAPI_WWWASSETS:-"\$OWSEC_ROOT/persist/wwwassets"} \
 | 
			
		||||
  INTERNAL_RESTAPI_HOST_ROOTCA=${INTERNAL_RESTAPI_HOST_ROOTCA:-"\$OWSEC_ROOT/certs/restapi-ca.pem"} \
 | 
			
		||||
  INTERNAL_RESTAPI_HOST_PORT=${INTERNAL_RESTAPI_HOST_PORT:-"17001"} \
 | 
			
		||||
  INTERNAL_RESTAPI_HOST_CERT=${INTERNAL_RESTAPI_HOST_CERT:-"\$OWSEC_ROOT/certs/restapi-cert.pem"} \
 | 
			
		||||
@@ -25,13 +25,29 @@ if [[ "$TEMPLATE_CONFIG" = 'true' && ! -f "$OWSEC_CONFIG"/owsec.properties ]]; t
 | 
			
		||||
  SYSTEM_URI_UI=${SYSTEM_URI_UI:-"http://localhost"} \
 | 
			
		||||
  SERVICE_KEY=${SERVICE_KEY:-"\$OWSEC_ROOT/certs/restapi-key.pem"} \
 | 
			
		||||
  SERVICE_KEY_PASSWORD=${SERVICE_KEY_PASSWORD:-"mypassword"} \
 | 
			
		||||
  MAILER_HOSTNAME=${MAILER_HOSTNAME:-"smtp.gmail.com"} \
 | 
			
		||||
  MAILER_USERNAME=${MAILER_USERNAME:-"************************"} \
 | 
			
		||||
  MAILER_PASSWORD=${MAILER_PASSWORD:-"************************"} \
 | 
			
		||||
  SMSSENDER_ENABLED=${SMSSENDER_ENABLED:-"false"} \
 | 
			
		||||
  SMSSENDER_PROVIDER=${SMSSENDER_PROVIDER:-""} \
 | 
			
		||||
  SMSSENDER_AWS_SECRETKEY=${SMSSENDER_AWS_SECRETKEY:-""} \
 | 
			
		||||
  SMSSENDER_AWS_ACCESSKEY=${SMSSENDER_AWS_ACCESSKEY:-""} \
 | 
			
		||||
  SMSSENDER_AWS_REGION=${SMSSENDER_AWS_REGION:-""} \
 | 
			
		||||
  SMSSENDER_TWILIO_SID=${SMSSENDER_TWILIO_SID:-""} \
 | 
			
		||||
  SMSSENDER_TWILIO_TOKEN=${SMSSENDER_TWILIO_TOKEN:-""} \
 | 
			
		||||
  SMSSENDER_TWILIO_PHONENUMBER=${SMSSENDER_TWILIO_PHONENUMBER:-""} \
 | 
			
		||||
  MAILER_ENABLED=${MAILER_ENABLED:-"false"} \
 | 
			
		||||
  MAILER_HOSTNAME=${MAILER_HOSTNAME:-"localhost"} \
 | 
			
		||||
  MAILER_USERNAME=${MAILER_USERNAME:-""} \
 | 
			
		||||
  MAILER_PASSWORD=${MAILER_PASSWORD:-""} \
 | 
			
		||||
  MAILER_SENDER=${MAILER_SENDER:-"OpenWIFI"} \
 | 
			
		||||
  MAILER_PORT=${MAILER_PORT:-"587"} \
 | 
			
		||||
  MAILER_TEMPLATES=${MAILER_TEMPLATES:-"\$OWSEC_ROOT/persist/templates"} \
 | 
			
		||||
  KAFKA_ENABLE=${KAFKA_ENABLE:-"true"} \
 | 
			
		||||
  KAFKA_BROKERLIST=${KAFKA_BROKERLIST:-"localhost:9092"} \
 | 
			
		||||
  KAFKA_SSL_CA_LOCATION=${KAFKA_SSL_CA_LOCATION:-""} \
 | 
			
		||||
  KAFKA_SSL_CERTIFICATE_LOCATION=${KAFKA_SSL_CERTIFICATE_LOCATION:-""} \
 | 
			
		||||
  KAFKA_SSL_KEY_LOCATION=${KAFKA_SSL_KEY_LOCATION:-""} \
 | 
			
		||||
  KAFKA_SSL_KEY_PASSWORD=${KAFKA_SSL_KEY_PASSWORD:-""} \
 | 
			
		||||
  DOCUMENT_POLICY_ACCESS=${DOCUMENT_POLICY_ACCESS:-"\$OWSEC_ROOT/persist/wwwassets/access_policy.html"} \
 | 
			
		||||
  DOCUMENT_POLICY_PASSWORD=${DOCUMENT_POLICY_PASSWORD:-"\$OWSEC_ROOT/persist/wwwassets/password_policy.html"} \
 | 
			
		||||
  STORAGE_TYPE=${STORAGE_TYPE:-"sqlite"} \
 | 
			
		||||
  STORAGE_TYPE_POSTGRESQL_HOST=${STORAGE_TYPE_POSTGRESQL_HOST:-"localhost"} \
 | 
			
		||||
  STORAGE_TYPE_POSTGRESQL_USERNAME=${STORAGE_TYPE_POSTGRESQL_USERNAME:-"owsec"} \
 | 
			
		||||
@@ -43,7 +59,25 @@ if [[ "$TEMPLATE_CONFIG" = 'true' && ! -f "$OWSEC_CONFIG"/owsec.properties ]]; t
 | 
			
		||||
  STORAGE_TYPE_MYSQL_PASSWORD=${STORAGE_TYPE_MYSQL_PASSWORD:-"owsec"} \
 | 
			
		||||
  STORAGE_TYPE_MYSQL_DATABASE=${STORAGE_TYPE_MYSQL_DATABASE:-"owsec"} \
 | 
			
		||||
  STORAGE_TYPE_MYSQL_PORT=${STORAGE_TYPE_MYSQL_PORT:-"3306"} \
 | 
			
		||||
  envsubst < $OWSEC_CONFIG/owsec.properties.tmpl > $OWSEC_CONFIG/owsec.properties
 | 
			
		||||
  envsubst < /owsec.properties.tmpl > $OWSEC_CONFIG/owsec.properties
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
# Check if wwwassets directory exists
 | 
			
		||||
export RESTAPI_WWWASSETS=$(grep 'openwifi.restapi.wwwassets' $OWSEC_CONFIG/owsec.properties | awk -F '=' '{print $2}' | xargs | envsubst)
 | 
			
		||||
if [[ ! -d "$(dirname $RESTAPI_WWWASSETS)" ]]; then
 | 
			
		||||
  mkdir -p $(dirname $RESTAPI_WWWASSETS)
 | 
			
		||||
fi
 | 
			
		||||
if [[ ! -d "$RESTAPI_WWWASSETS" ]]; then
 | 
			
		||||
  cp -r /dist/wwwassets $RESTAPI_WWWASSETS
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
# Check if templates directory exists
 | 
			
		||||
export MAILER_TEMPLATES=$(grep 'mailer.templates' $OWSEC_CONFIG/owsec.properties | awk -F '=' '{print $2}' | xargs | envsubst)
 | 
			
		||||
if [[ ! -d "$(dirname $MAILER_TEMPLATES)" ]]; then
 | 
			
		||||
  mkdir -p $(dirname $MAILER_TEMPLATES)
 | 
			
		||||
fi
 | 
			
		||||
if [[ ! -d "$MAILER_TEMPLATES" ]]; then
 | 
			
		||||
  cp -r /dist/templates $MAILER_TEMPLATES
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
if [ "$1" = '/openwifi/owsec' -a "$(id -u)" = '0' ]; then
 | 
			
		||||
 
 | 
			
		||||
@@ -5,14 +5,14 @@ name: owsec
 | 
			
		||||
version: 0.1.0
 | 
			
		||||
dependencies:
 | 
			
		||||
- name: postgresql
 | 
			
		||||
  repository: https://charts.bitnami.com/bitnami
 | 
			
		||||
  repository: https://tip.jfrog.io/artifactory/tip-wlan-cloud-ucentral-helm/
 | 
			
		||||
  version: 10.9.2
 | 
			
		||||
  condition: postgresql.enabled
 | 
			
		||||
- name: mysql
 | 
			
		||||
  repository: https://charts.bitnami.com/bitnami
 | 
			
		||||
  repository: https://tip.jfrog.io/artifactory/tip-wlan-cloud-ucentral-helm/
 | 
			
		||||
  version: 8.8.3
 | 
			
		||||
  condition: mysql.enabled
 | 
			
		||||
- name: mariadb
 | 
			
		||||
  repository: https://charts.bitnami.com/bitnami
 | 
			
		||||
  repository: https://tip.jfrog.io/artifactory/tip-wlan-cloud-ucentral-helm/
 | 
			
		||||
  version: 9.4.2
 | 
			
		||||
  condition: mariadb.enabled
 | 
			
		||||
 
 | 
			
		||||
@@ -20,7 +20,7 @@ Currently this chart is not assembled in charts archives, so [helm-git](https://
 | 
			
		||||
To install the chart with the release name `my-release`:
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
$ helm install --name my-release git+https://github.com/Telecominfraproject/wlan-cloud-ucentralsec@helm?ref=main
 | 
			
		||||
$ helm install --name my-release git+https://github.com/Telecominfraproject/wlan-cloud-ucentralsec@helm/owsec-0.1.0.tgz?ref=main
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
The command deploys the Security on the Kubernetes cluster in the default configuration. The [configuration](#configuration) section lists the parameters that can be configured during installation.
 | 
			
		||||
 
 | 
			
		||||
@@ -30,3 +30,13 @@ Create chart name and version as used by the chart label.
 | 
			
		||||
{{- define "owsec.chart" -}}
 | 
			
		||||
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}}
 | 
			
		||||
{{- end -}}
 | 
			
		||||
 | 
			
		||||
{{- define "owsec.ingress.apiVersion" -}}
 | 
			
		||||
  {{- if .Capabilities.APIVersions.Has "networking.k8s.io/v1" -}}
 | 
			
		||||
      {{- print "networking.k8s.io/v1" -}}
 | 
			
		||||
  {{- else if .Capabilities.APIVersions.Has "networking.k8s.io/v1beta1" -}}
 | 
			
		||||
    {{- print "networking.k8s.io/v1beta1" -}}
 | 
			
		||||
  {{- else -}}
 | 
			
		||||
    {{- print "extensions/v1beta1" -}}
 | 
			
		||||
  {{- end -}}
 | 
			
		||||
{{- end -}}
 | 
			
		||||
 
 | 
			
		||||
@@ -13,6 +13,7 @@ spec:
 | 
			
		||||
  replicas: {{ .Values.replicaCount }}
 | 
			
		||||
  strategy:
 | 
			
		||||
    type: {{ .Values.strategyType }}
 | 
			
		||||
  revisionHistoryLimit: {{ .Values.revisionHistoryLimit }}
 | 
			
		||||
  selector:
 | 
			
		||||
    matchLabels:
 | 
			
		||||
      app.kubernetes.io/name: {{ include "owsec.name" . }}
 | 
			
		||||
@@ -24,6 +25,9 @@ spec:
 | 
			
		||||
    metadata:
 | 
			
		||||
      annotations:
 | 
			
		||||
        checksum/config: {{ include "owsec.config" . | sha256sum }}
 | 
			
		||||
        {{- with .Values.podAnnotations }}
 | 
			
		||||
        {{- toYaml . | nindent 8 }}
 | 
			
		||||
        {{- end }}
 | 
			
		||||
      labels:
 | 
			
		||||
        app.kubernetes.io/name: {{ include "owsec.name" . }}
 | 
			
		||||
        app.kubernetes.io/instance: {{ .Release.Name }}
 | 
			
		||||
@@ -32,6 +36,16 @@ spec:
 | 
			
		||||
        {{- end }}
 | 
			
		||||
    spec:
 | 
			
		||||
 | 
			
		||||
      initContainers:
 | 
			
		||||
        - name: wait-kafka
 | 
			
		||||
          image: "{{ .Values.images.dockerize.repository }}:{{ .Values.images.dockerize.tag }}"
 | 
			
		||||
          imagePullPolicy: {{ .Values.images.dockerize.pullPolicy }}
 | 
			
		||||
          args:
 | 
			
		||||
            - -wait
 | 
			
		||||
            - tcp://{{ index .Values.configProperties "openwifi.kafka.brokerlist" }}
 | 
			
		||||
            - -timeout
 | 
			
		||||
            - 600s
 | 
			
		||||
 | 
			
		||||
      containers:
 | 
			
		||||
 | 
			
		||||
        - name: owsec
 | 
			
		||||
 
 | 
			
		||||
@@ -2,7 +2,7 @@
 | 
			
		||||
{{- range $ingress, $ingressValue := .Values.ingresses }}
 | 
			
		||||
{{- if $ingressValue.enabled }}
 | 
			
		||||
---
 | 
			
		||||
apiVersion: extensions/v1beta1
 | 
			
		||||
apiVersion: {{ include "owsec.ingress.apiVersion" $root }}
 | 
			
		||||
kind: Ingress
 | 
			
		||||
metadata:
 | 
			
		||||
  name: {{ include "owsec.fullname" $root }}-{{ $ingress }}
 | 
			
		||||
@@ -36,9 +36,23 @@ spec:
 | 
			
		||||
      paths:
 | 
			
		||||
      {{- range $ingressValue.paths }}
 | 
			
		||||
        - path: {{ .path }}
 | 
			
		||||
          {{- if $root.Capabilities.APIVersions.Has "networking.k8s.io/v1" }}
 | 
			
		||||
          pathType: {{ .pathType | default "ImplementationSpecific" }}
 | 
			
		||||
          {{- end }}
 | 
			
		||||
          backend:
 | 
			
		||||
            {{- if $root.Capabilities.APIVersions.Has "networking.k8s.io/v1" }}
 | 
			
		||||
            service:
 | 
			
		||||
              name: {{ include "owsec.fullname" $root }}-{{ .serviceName }}
 | 
			
		||||
              port:
 | 
			
		||||
              {{- if kindIs "string" .servicePort }}
 | 
			
		||||
                name: {{ .servicePort }}
 | 
			
		||||
              {{- else }}
 | 
			
		||||
                number: {{ .servicePort }}
 | 
			
		||||
              {{- end }}
 | 
			
		||||
            {{- else }}
 | 
			
		||||
            serviceName: {{ include "owsec.fullname" $root }}-{{ .serviceName }}
 | 
			
		||||
            servicePort: {{ .servicePort }}
 | 
			
		||||
            {{- end }}
 | 
			
		||||
      {{- end }}
 | 
			
		||||
  {{- end }}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,7 @@
 | 
			
		||||
# System
 | 
			
		||||
replicaCount: 1
 | 
			
		||||
strategyType: Recreate
 | 
			
		||||
revisionHistoryLimit: 2
 | 
			
		||||
 | 
			
		||||
nameOverride: ""
 | 
			
		||||
fullnameOverride: ""
 | 
			
		||||
@@ -8,16 +9,20 @@ fullnameOverride: ""
 | 
			
		||||
images:
 | 
			
		||||
  owsec:
 | 
			
		||||
    repository: tip-tip-wlan-cloud-ucentral.jfrog.io/owsec
 | 
			
		||||
    tag: main
 | 
			
		||||
    tag: v2.6.0-RC2
 | 
			
		||||
    pullPolicy: Always
 | 
			
		||||
#    regcred:
 | 
			
		||||
#      registry: tip-tip-wlan-cloud-ucentral.jfrog.io
 | 
			
		||||
#      username: username
 | 
			
		||||
#      password: password
 | 
			
		||||
  dockerize:
 | 
			
		||||
    repository: tip-tip-wlan-cloud-ucentral.jfrog.io/dockerize
 | 
			
		||||
    tag: 0.16.0
 | 
			
		||||
    pullPolicy: IfNotPresent
 | 
			
		||||
 | 
			
		||||
services:
 | 
			
		||||
  owsec:
 | 
			
		||||
    type: LoadBalancer
 | 
			
		||||
    type: ClusterIP
 | 
			
		||||
    ports:
 | 
			
		||||
      restapi:
 | 
			
		||||
        servicePort: 16001
 | 
			
		||||
@@ -35,9 +40,9 @@ checks:
 | 
			
		||||
        path: /
 | 
			
		||||
        port: 16101
 | 
			
		||||
    readiness:
 | 
			
		||||
      httpGet:
 | 
			
		||||
        path: /
 | 
			
		||||
        port: 16101
 | 
			
		||||
      exec:
 | 
			
		||||
        command:
 | 
			
		||||
          - /readiness_check
 | 
			
		||||
 | 
			
		||||
ingresses:
 | 
			
		||||
  restapi:
 | 
			
		||||
@@ -49,6 +54,7 @@ ingresses:
 | 
			
		||||
    - restapi.chart-example.local
 | 
			
		||||
    paths:
 | 
			
		||||
    - path: /
 | 
			
		||||
      pathType: ImplementationSpecific
 | 
			
		||||
      serviceName: owsec
 | 
			
		||||
      servicePort: restapi
 | 
			
		||||
 | 
			
		||||
@@ -94,6 +100,8 @@ tolerations: []
 | 
			
		||||
 | 
			
		||||
affinity: {}
 | 
			
		||||
 | 
			
		||||
podAnnotations: {}
 | 
			
		||||
 | 
			
		||||
persistence:
 | 
			
		||||
  enabled: true
 | 
			
		||||
  # storageClassName: "-"
 | 
			
		||||
@@ -106,8 +114,14 @@ persistence:
 | 
			
		||||
public_env_variables:
 | 
			
		||||
  OWSEC_ROOT: /owsec-data
 | 
			
		||||
  OWSEC_CONFIG: /owsec-data
 | 
			
		||||
  # Environment variables required for the readiness checks using script
 | 
			
		||||
  FLAGS: "-s --connect-timeout 3"
 | 
			
		||||
  # NOTE in order for readiness check to use system info you need to set READINESS_METHOD to "systeminfo" and set OWSEC to the OWSEC's REST API endpoint
 | 
			
		||||
  #READINESS_METHOD: systeminfo
 | 
			
		||||
 | 
			
		||||
secret_env_variables: {}
 | 
			
		||||
secret_env_variables:
 | 
			
		||||
  OWSEC_USERNAME: tip@ucentral.com
 | 
			
		||||
  OWSEC_PASSWORD: openwifi
 | 
			
		||||
 | 
			
		||||
configProperties:
 | 
			
		||||
  # -> Public part
 | 
			
		||||
@@ -119,7 +133,7 @@ configProperties:
 | 
			
		||||
  openwifi.restapi.host.0.port: 16001
 | 
			
		||||
  openwifi.restapi.host.0.cert: $OWSEC_ROOT/certs/restapi-cert.pem
 | 
			
		||||
  openwifi.restapi.host.0.key: $OWSEC_ROOT/certs/restapi-key.pem
 | 
			
		||||
  openwifi.restapi.wwwassets: $OWSEC_ROOT/wwwassets
 | 
			
		||||
  openwifi.restapi.wwwassets: $OWSEC_ROOT/persist/wwwassets
 | 
			
		||||
  openwifi.internal.restapi.host.0.backlog: 100
 | 
			
		||||
  openwifi.internal.restapi.host.0.security: relaxed
 | 
			
		||||
  openwifi.internal.restapi.host.0.rootca: $OWSEC_ROOT/certs/restapi-ca.pem
 | 
			
		||||
@@ -132,11 +146,17 @@ configProperties:
 | 
			
		||||
  authentication.default.access: master
 | 
			
		||||
  authentication.service.type: internal
 | 
			
		||||
  # Mailer
 | 
			
		||||
  mailer.enabled: "false"
 | 
			
		||||
  mailer.hostname: smtp.gmail.com
 | 
			
		||||
  mailer.sender: OpenWIFI
 | 
			
		||||
  mailer.loginmethod: login
 | 
			
		||||
  mailer.port: 587
 | 
			
		||||
  mailer.templates: $OWSEC_ROOT/templates
 | 
			
		||||
  mailer.templates: $OWSEC_ROOT/persist/templates
 | 
			
		||||
  # SMS
 | 
			
		||||
  smssender.enabled: "false"
 | 
			
		||||
  smssender.provider: "aws"
 | 
			
		||||
  #smssender.aws.region: ""
 | 
			
		||||
  #smssender.twilio.phonenumber: ""
 | 
			
		||||
  # ALB
 | 
			
		||||
  alb.enable: "true"
 | 
			
		||||
  alb.port: 16101
 | 
			
		||||
@@ -147,6 +167,10 @@ configProperties:
 | 
			
		||||
  openwifi.kafka.brokerlist: localhost:9092
 | 
			
		||||
  openwifi.kafka.auto.commit: false
 | 
			
		||||
  openwifi.kafka.queue.buffering.max.ms: 50
 | 
			
		||||
  openwifi.kafka.ssl.ca.location: ""
 | 
			
		||||
  openwifi.kafka.ssl.certificate.location: ""
 | 
			
		||||
  openwifi.kafka.ssl.key.location: ""
 | 
			
		||||
  openwifi.kafka.ssl.key.password: ""
 | 
			
		||||
  # Storage
 | 
			
		||||
  storage.type: sqlite # (sqlite|postgresql|mysql|odbc)
 | 
			
		||||
  ## SQLite
 | 
			
		||||
@@ -176,22 +200,9 @@ configProperties:
 | 
			
		||||
  openwifi.system.uri.ui: https://localhost
 | 
			
		||||
  openwifi.system.commandchannel: /tmp/app_owsec
 | 
			
		||||
  # Logging
 | 
			
		||||
  logging.formatters.f1.class: PatternFormatter
 | 
			
		||||
  logging.formatters.f1.pattern: "%Y-%m-%d %H:%M:%S %s: [%p] %t"
 | 
			
		||||
  logging.formatters.f1.times: UTC
 | 
			
		||||
  logging.channels.c1.class: ConsoleChannel
 | 
			
		||||
  logging.channels.c1.formatter: f1
 | 
			
		||||
  logging.channels.c2.class: FileChannel
 | 
			
		||||
  logging.channels.c2.path: /tmp/log_owsec
 | 
			
		||||
  logging.channels.c2.formatter.class: PatternFormatter
 | 
			
		||||
  logging.channels.c2.formatter.pattern: "%Y-%m-%d %H:%M:%S %s: [%p] %t"
 | 
			
		||||
  logging.channels.c2.rotation: "20 M"
 | 
			
		||||
  logging.channels.c2.archive: timestamp
 | 
			
		||||
  logging.channels.c2.purgeCount: 20
 | 
			
		||||
  logging.channels.c3.class: ConsoleChannel
 | 
			
		||||
  logging.channels.c3.pattern: "%s: [%p] %t"
 | 
			
		||||
  logging.loggers.root.channel: c1
 | 
			
		||||
  logging.loggers.root.level: debug
 | 
			
		||||
  logging.type: console
 | 
			
		||||
  logging.path: $OWSEC_ROOT/logs
 | 
			
		||||
  logging.level: debug
 | 
			
		||||
 | 
			
		||||
  # -> Secret part
 | 
			
		||||
  # REST API
 | 
			
		||||
@@ -203,6 +214,12 @@ configProperties:
 | 
			
		||||
  # Mailer
 | 
			
		||||
  mailer.username: no-reply@arilia.com
 | 
			
		||||
  mailer.password: "**************************"
 | 
			
		||||
  # SMS
 | 
			
		||||
  #smssender.aws.secretkey: ""
 | 
			
		||||
  #smssender.aws.accesskey: ""
 | 
			
		||||
  #smssender.twilio.sid: ""
 | 
			
		||||
  #smssender.twilio.token: ""
 | 
			
		||||
  #
 | 
			
		||||
  # Storage
 | 
			
		||||
  ## PostgreSQL
 | 
			
		||||
  storage.type.postgresql.username: stephb
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,8 @@
 | 
			
		||||
openapi: 3.0.1
 | 
			
		||||
info:
 | 
			
		||||
  title: uCentral Security API
 | 
			
		||||
  description: A process to manage security logins
 | 
			
		||||
  version: 2.0.0
 | 
			
		||||
  description: A process to manage security logins.
 | 
			
		||||
  version: 2.5.0
 | 
			
		||||
  license:
 | 
			
		||||
    name: BSD3
 | 
			
		||||
    url: https://github.com/Telecominfraproject/wlan-cloud-ucentralgw/blob/master/LICENSE
 | 
			
		||||
@@ -51,6 +51,22 @@ components:
 | 
			
		||||
            properties:
 | 
			
		||||
              ErrorCode:
 | 
			
		||||
                type: integer
 | 
			
		||||
                enum:
 | 
			
		||||
                  - 0     # Success
 | 
			
		||||
                  - 1     # PASSWORD_CHANGE_REQUIRED,
 | 
			
		||||
                  - 2     # INVALID_CREDENTIALS,
 | 
			
		||||
                  - 3     # PASSWORD_ALREADY_USED,
 | 
			
		||||
                  - 4     # USERNAME_PENDING_VERIFICATION,
 | 
			
		||||
                  - 5     # PASSWORD_INVALID,
 | 
			
		||||
                  - 6     # INTERNAL_ERROR,
 | 
			
		||||
                  - 7     # ACCESS_DENIED,
 | 
			
		||||
                  - 8     # INVALID_TOKEN
 | 
			
		||||
                  - 9     # EXPIRED_TOKEN
 | 
			
		||||
                  - 10    # RATE_LIMIT_EXCEEDED
 | 
			
		||||
                  - 11    # BAD_MFA_TRANSACTION
 | 
			
		||||
                  - 12    # MFA_FAILURE
 | 
			
		||||
                  - 13    # SECURITY_SERVICE_UNREACHABLE
 | 
			
		||||
                  - 14    # CANNOT REFRESH TOKEN
 | 
			
		||||
              ErrorDetails:
 | 
			
		||||
                type: string
 | 
			
		||||
              ErrorDescription:
 | 
			
		||||
@@ -69,8 +85,20 @@ components:
 | 
			
		||||
              Code:
 | 
			
		||||
                type: integer
 | 
			
		||||
 | 
			
		||||
  schemas:
 | 
			
		||||
    BadRequest:
 | 
			
		||||
      description: The requested operation failed.
 | 
			
		||||
      content:
 | 
			
		||||
        application/json:
 | 
			
		||||
          schema:
 | 
			
		||||
            properties:
 | 
			
		||||
              ErrorCode:
 | 
			
		||||
                type: integer
 | 
			
		||||
              ErrorDetails:
 | 
			
		||||
                type: string
 | 
			
		||||
              ErrorDescription:
 | 
			
		||||
                type: integer
 | 
			
		||||
 | 
			
		||||
  schemas:
 | 
			
		||||
    WebTokenRequest:
 | 
			
		||||
      description: User Id and password.
 | 
			
		||||
      type: object
 | 
			
		||||
@@ -93,6 +121,15 @@ components:
 | 
			
		||||
        userId: support@example.com
 | 
			
		||||
        password: support
 | 
			
		||||
 | 
			
		||||
    WebTokenRefreshRequest:
 | 
			
		||||
      type: object
 | 
			
		||||
      properties:
 | 
			
		||||
        userId:
 | 
			
		||||
          type: string
 | 
			
		||||
          default: support@example.com
 | 
			
		||||
        refresh_token:
 | 
			
		||||
          type: string
 | 
			
		||||
 | 
			
		||||
    WebTokenResult:
 | 
			
		||||
      description: Login and Refresh Tokens to be used in subsequent API calls.
 | 
			
		||||
      type: object
 | 
			
		||||
@@ -196,6 +233,40 @@ components:
 | 
			
		||||
          items:
 | 
			
		||||
            $ref: '#/components/schemas/SystemEndpoint'
 | 
			
		||||
 | 
			
		||||
    MobilePhoneNumber:
 | 
			
		||||
      type: object
 | 
			
		||||
      properties:
 | 
			
		||||
        number:
 | 
			
		||||
          type: string
 | 
			
		||||
        verified:
 | 
			
		||||
          type: boolean
 | 
			
		||||
        primary:
 | 
			
		||||
          type: boolean
 | 
			
		||||
 | 
			
		||||
    MfaAuthInfo:
 | 
			
		||||
      type: object
 | 
			
		||||
      properties:
 | 
			
		||||
        enabled:
 | 
			
		||||
          type: boolean
 | 
			
		||||
        method:
 | 
			
		||||
          type: string
 | 
			
		||||
          enum:
 | 
			
		||||
            - sms
 | 
			
		||||
            - email
 | 
			
		||||
            - authenticator
 | 
			
		||||
 | 
			
		||||
    UserLoginLoginExtensions:
 | 
			
		||||
      type: object
 | 
			
		||||
      properties:
 | 
			
		||||
        mobiles:
 | 
			
		||||
          type: array
 | 
			
		||||
          items:
 | 
			
		||||
            $ref: '#/components/schemas/MobilePhoneNumber'
 | 
			
		||||
        authenticatorSecret:
 | 
			
		||||
          type: string
 | 
			
		||||
        mfa:
 | 
			
		||||
          $ref: '#/components/schemas/MfaAuthInfo'
 | 
			
		||||
 | 
			
		||||
    UserInfo:
 | 
			
		||||
      type: object
 | 
			
		||||
      properties:
 | 
			
		||||
@@ -267,10 +338,12 @@ components:
 | 
			
		||||
          enum:
 | 
			
		||||
            - root
 | 
			
		||||
            - admin
 | 
			
		||||
            - sub
 | 
			
		||||
            - subscriber
 | 
			
		||||
            - csr
 | 
			
		||||
            - system
 | 
			
		||||
            - special
 | 
			
		||||
            - installer
 | 
			
		||||
            - noc
 | 
			
		||||
            - accounting
 | 
			
		||||
        oauthType:
 | 
			
		||||
          type: string
 | 
			
		||||
          enum:
 | 
			
		||||
@@ -287,8 +360,14 @@ components:
 | 
			
		||||
        securityPolicyChange:
 | 
			
		||||
          type: integer
 | 
			
		||||
          format: int64
 | 
			
		||||
        modified:
 | 
			
		||||
          type: integer
 | 
			
		||||
          format: int64
 | 
			
		||||
        userTypeProprietaryInfo:
 | 
			
		||||
          $ref: '#/components/schemas/UserLoginLoginExtensions'
 | 
			
		||||
        signupUUID:
 | 
			
		||||
          type: string
 | 
			
		||||
          format: uuid
 | 
			
		||||
 | 
			
		||||
    UserList:
 | 
			
		||||
      type: object
 | 
			
		||||
@@ -314,6 +393,57 @@ components:
 | 
			
		||||
        text:
 | 
			
		||||
          type: string
 | 
			
		||||
 | 
			
		||||
    SMSInfo:
 | 
			
		||||
      type: object
 | 
			
		||||
      properties:
 | 
			
		||||
        from:
 | 
			
		||||
          type: string
 | 
			
		||||
        to:
 | 
			
		||||
          type: string
 | 
			
		||||
        text:
 | 
			
		||||
          type: string
 | 
			
		||||
 | 
			
		||||
    MFAChallengeRequest:
 | 
			
		||||
      type: object
 | 
			
		||||
      properties:
 | 
			
		||||
        uuid:
 | 
			
		||||
          type: string
 | 
			
		||||
          format: uuid
 | 
			
		||||
        question:
 | 
			
		||||
          type: string
 | 
			
		||||
        created:
 | 
			
		||||
          type: integer
 | 
			
		||||
          format: integer64
 | 
			
		||||
        method:
 | 
			
		||||
          type: string
 | 
			
		||||
 | 
			
		||||
    MFAChallengeResponse:
 | 
			
		||||
      type: object
 | 
			
		||||
      properties:
 | 
			
		||||
        uuid:
 | 
			
		||||
          type: string
 | 
			
		||||
          format: uuid
 | 
			
		||||
        answer:
 | 
			
		||||
          type: string
 | 
			
		||||
 | 
			
		||||
    SubMfaConfig:
 | 
			
		||||
      type: object
 | 
			
		||||
      properties:
 | 
			
		||||
        id:
 | 
			
		||||
          type: string
 | 
			
		||||
          format: uuid
 | 
			
		||||
        type:
 | 
			
		||||
          type: string
 | 
			
		||||
          enum:
 | 
			
		||||
            - disabled
 | 
			
		||||
            - sms
 | 
			
		||||
            - email
 | 
			
		||||
        email:
 | 
			
		||||
          type: string
 | 
			
		||||
          format: email
 | 
			
		||||
        sms:
 | 
			
		||||
          type: string
 | 
			
		||||
 | 
			
		||||
    #########################################################################################
 | 
			
		||||
    ##
 | 
			
		||||
    ## These are endpoints that all services in the uCentral stack must provide
 | 
			
		||||
@@ -558,6 +688,22 @@ components:
 | 
			
		||||
          items:
 | 
			
		||||
            $ref: '#/components/schemas/TagValuePair'
 | 
			
		||||
 | 
			
		||||
    Preferences:
 | 
			
		||||
      type: object
 | 
			
		||||
      properties:
 | 
			
		||||
        modified:
 | 
			
		||||
          type: integer
 | 
			
		||||
          format: int64
 | 
			
		||||
        data:
 | 
			
		||||
          type: array
 | 
			
		||||
          items:
 | 
			
		||||
            type: object
 | 
			
		||||
            properties:
 | 
			
		||||
              tag:
 | 
			
		||||
                type: string
 | 
			
		||||
              value:
 | 
			
		||||
                type: string
 | 
			
		||||
 | 
			
		||||
    #########################################################################################
 | 
			
		||||
    ##
 | 
			
		||||
    ## End of uCentral system wide values
 | 
			
		||||
@@ -590,20 +736,106 @@ paths:
 | 
			
		||||
          schema:
 | 
			
		||||
            type: boolean
 | 
			
		||||
          required: false
 | 
			
		||||
        - in: query
 | 
			
		||||
          name: resendMFACode
 | 
			
		||||
          schema:
 | 
			
		||||
            type: boolean
 | 
			
		||||
          required: false
 | 
			
		||||
        - in: query
 | 
			
		||||
          name: completeMFAChallenge
 | 
			
		||||
          schema:
 | 
			
		||||
            type: boolean
 | 
			
		||||
          required: false
 | 
			
		||||
        - in: query
 | 
			
		||||
          name: grant_type
 | 
			
		||||
          schema:
 | 
			
		||||
            type: string
 | 
			
		||||
            example: refresh_token
 | 
			
		||||
          required: false
 | 
			
		||||
      requestBody:
 | 
			
		||||
        description: User id and password
 | 
			
		||||
        required: true
 | 
			
		||||
        content:
 | 
			
		||||
          application/json:
 | 
			
		||||
            schema:
 | 
			
		||||
              $ref: '#/components/schemas/WebTokenRequest'
 | 
			
		||||
              oneOf:
 | 
			
		||||
                - $ref: '#/components/schemas/WebTokenRequest'
 | 
			
		||||
                - $ref: '#/components/schemas/MFAChallengeResponse'
 | 
			
		||||
                - $ref: '#/components/schemas/WebTokenRefreshRequest'
 | 
			
		||||
      responses:
 | 
			
		||||
        200:
 | 
			
		||||
          description: successful operation
 | 
			
		||||
          content:
 | 
			
		||||
            application/json:
 | 
			
		||||
              schema:
 | 
			
		||||
                $ref: '#/components/schemas/WebTokenResult'
 | 
			
		||||
                oneOf:
 | 
			
		||||
                  - $ref: '#/components/schemas/WebTokenResult'
 | 
			
		||||
                  - $ref: '#/components/schemas/MFAChallengeRequest'
 | 
			
		||||
        403:
 | 
			
		||||
          $ref: '#/components/responses/Unauthorized'
 | 
			
		||||
        404:
 | 
			
		||||
          $ref: '#/components/responses/NotFound'
 | 
			
		||||
 | 
			
		||||
  /suboauth2:
 | 
			
		||||
    post:
 | 
			
		||||
      tags:
 | 
			
		||||
        - Authentication
 | 
			
		||||
      summary: Get access token - to be used as Bearer token header for all other API requests.
 | 
			
		||||
      operationId: getSubAccessToken
 | 
			
		||||
      parameters:
 | 
			
		||||
        - in: query
 | 
			
		||||
          name: newPassword
 | 
			
		||||
          description: used when a user is trying to change her password. This will be the new password.
 | 
			
		||||
          schema:
 | 
			
		||||
            type: string
 | 
			
		||||
          required: false
 | 
			
		||||
        - in: query
 | 
			
		||||
          name: forgotPassword
 | 
			
		||||
          description: A user forgot her password. She needs to present her e-mail address in the userId and set this to true
 | 
			
		||||
          schema:
 | 
			
		||||
            type: boolean
 | 
			
		||||
          required: false
 | 
			
		||||
        - in: query
 | 
			
		||||
          name: requirements
 | 
			
		||||
          description: A user forgot her password. She needs to present her e-mail address in the userId and set this to true
 | 
			
		||||
          schema:
 | 
			
		||||
            type: boolean
 | 
			
		||||
          required: false
 | 
			
		||||
        - in: query
 | 
			
		||||
          name: resendMFACode
 | 
			
		||||
          schema:
 | 
			
		||||
            type: boolean
 | 
			
		||||
          required: false
 | 
			
		||||
        - in: query
 | 
			
		||||
          name: completeMFAChallenge
 | 
			
		||||
          schema:
 | 
			
		||||
            type: boolean
 | 
			
		||||
          required: false
 | 
			
		||||
        - in: query
 | 
			
		||||
          name: grant_type
 | 
			
		||||
          schema:
 | 
			
		||||
            type: string
 | 
			
		||||
            example: refresh_token
 | 
			
		||||
          required: false
 | 
			
		||||
      requestBody:
 | 
			
		||||
        description: User id and password
 | 
			
		||||
        required: true
 | 
			
		||||
        content:
 | 
			
		||||
          application/json:
 | 
			
		||||
            schema:
 | 
			
		||||
              oneOf:
 | 
			
		||||
                - $ref: '#/components/schemas/WebTokenRequest'
 | 
			
		||||
                - $ref: '#/components/schemas/MFAChallengeResponse'
 | 
			
		||||
                - $ref: '#/components/schemas/WebTokenRefreshRequest'
 | 
			
		||||
      responses:
 | 
			
		||||
        200:
 | 
			
		||||
          description: successful operation
 | 
			
		||||
          content:
 | 
			
		||||
            application/json:
 | 
			
		||||
              schema:
 | 
			
		||||
                oneOf:
 | 
			
		||||
                  - $ref: '#/components/schemas/WebTokenResult'
 | 
			
		||||
                  - $ref: '#/components/schemas/MFAChallengeRequest'
 | 
			
		||||
        403:
 | 
			
		||||
          $ref: '#/components/responses/Unauthorized'
 | 
			
		||||
        404:
 | 
			
		||||
@@ -634,11 +866,36 @@ paths:
 | 
			
		||||
        404:
 | 
			
		||||
          $ref: '#/components/responses/NotFound'
 | 
			
		||||
 | 
			
		||||
  /suboauth2/{token}:
 | 
			
		||||
    delete:
 | 
			
		||||
      tags:
 | 
			
		||||
        - Authentication
 | 
			
		||||
      summary: Revoke a token.
 | 
			
		||||
      operationId: removeSubAccessToken
 | 
			
		||||
      parameters:
 | 
			
		||||
        - in: path
 | 
			
		||||
          name: token
 | 
			
		||||
          schema:
 | 
			
		||||
            type:
 | 
			
		||||
              string
 | 
			
		||||
          required: true
 | 
			
		||||
      responses:
 | 
			
		||||
        204:
 | 
			
		||||
          description: successful operation
 | 
			
		||||
          content:
 | 
			
		||||
            application/json:
 | 
			
		||||
              schema:
 | 
			
		||||
                $ref: '#/components/responses/Success'
 | 
			
		||||
        403:
 | 
			
		||||
          $ref: '#/components/responses/Unauthorized'
 | 
			
		||||
        404:
 | 
			
		||||
          $ref: '#/components/responses/NotFound'
 | 
			
		||||
 | 
			
		||||
  /systemEndpoints:
 | 
			
		||||
    get:
 | 
			
		||||
      tags:
 | 
			
		||||
        - Authentication
 | 
			
		||||
      summary: retrieve the system layout
 | 
			
		||||
      summary: Retrieve the system layout.
 | 
			
		||||
      operationId: getSystemInfo
 | 
			
		||||
      responses:
 | 
			
		||||
        200:
 | 
			
		||||
@@ -690,6 +947,72 @@ paths:
 | 
			
		||||
            type: string
 | 
			
		||||
            example: id1,id2,id3,id4,id5
 | 
			
		||||
          required: false
 | 
			
		||||
        - in: query
 | 
			
		||||
          description: Name matching
 | 
			
		||||
          name: nameSearch
 | 
			
		||||
          schema:
 | 
			
		||||
            type: string
 | 
			
		||||
        - in: query
 | 
			
		||||
          description: Name matching
 | 
			
		||||
          name: emailSearch
 | 
			
		||||
          schema:
 | 
			
		||||
            type: string
 | 
			
		||||
      responses:
 | 
			
		||||
        200:
 | 
			
		||||
          $ref: '#/components/schemas/UserList'
 | 
			
		||||
        403:
 | 
			
		||||
          $ref: '#/components/responses/Unauthorized'
 | 
			
		||||
        404:
 | 
			
		||||
          $ref: '#/components/responses/NotFound'
 | 
			
		||||
 | 
			
		||||
  /subusers:
 | 
			
		||||
    get:
 | 
			
		||||
      tags:
 | 
			
		||||
        - Subscribers
 | 
			
		||||
      summary: Retrieve a list of existing users as well as some information about them.
 | 
			
		||||
      operationId: getSubUsers
 | 
			
		||||
      parameters:
 | 
			
		||||
        - in: query
 | 
			
		||||
          name: offset
 | 
			
		||||
          schema:
 | 
			
		||||
            type: integer
 | 
			
		||||
            format: int64
 | 
			
		||||
          required: false
 | 
			
		||||
        - in: query
 | 
			
		||||
          name: limit
 | 
			
		||||
          schema:
 | 
			
		||||
            type: integer
 | 
			
		||||
            format: int64
 | 
			
		||||
          required: false
 | 
			
		||||
        - in: query
 | 
			
		||||
          description: Selecting this option means the newest record will be returned. Use limit to select how many.
 | 
			
		||||
          name: filter
 | 
			
		||||
          schema:
 | 
			
		||||
            type: string
 | 
			
		||||
          required: false
 | 
			
		||||
        - in: query
 | 
			
		||||
          description: Return only the ids.
 | 
			
		||||
          name: idOnly
 | 
			
		||||
          schema:
 | 
			
		||||
            type: boolean
 | 
			
		||||
          required: false
 | 
			
		||||
        - in: query
 | 
			
		||||
          description: Return only the ids.
 | 
			
		||||
          name: select
 | 
			
		||||
          schema:
 | 
			
		||||
            type: string
 | 
			
		||||
            example: id1,id2,id3,id4,id5
 | 
			
		||||
          required: false
 | 
			
		||||
        - in: query
 | 
			
		||||
          description: Name matching
 | 
			
		||||
          name: nameSearch
 | 
			
		||||
          schema:
 | 
			
		||||
            type: string
 | 
			
		||||
        - in: query
 | 
			
		||||
          description: Name matching
 | 
			
		||||
          name: emailSearch
 | 
			
		||||
          schema:
 | 
			
		||||
            type: string
 | 
			
		||||
      responses:
 | 
			
		||||
        200:
 | 
			
		||||
          $ref: '#/components/schemas/UserList'
 | 
			
		||||
@@ -703,7 +1026,7 @@ paths:
 | 
			
		||||
      tags:
 | 
			
		||||
        - User Management
 | 
			
		||||
      operationId: getUser
 | 
			
		||||
      summary: Retrieve the information for a single user
 | 
			
		||||
      summary: Retrieve the information for a single user.
 | 
			
		||||
      parameters:
 | 
			
		||||
        - in: path
 | 
			
		||||
          name: id
 | 
			
		||||
@@ -723,7 +1046,7 @@ paths:
 | 
			
		||||
      tags:
 | 
			
		||||
        - User Management
 | 
			
		||||
      operationId: deleteUser
 | 
			
		||||
      summary: Delete s single user
 | 
			
		||||
      summary: Delete a single user.
 | 
			
		||||
      parameters:
 | 
			
		||||
        - in: path
 | 
			
		||||
          name: id
 | 
			
		||||
@@ -743,7 +1066,7 @@ paths:
 | 
			
		||||
      tags:
 | 
			
		||||
        - User Management
 | 
			
		||||
      operationId: createUser
 | 
			
		||||
      summary: Create a single user
 | 
			
		||||
      summary: Create a single user.
 | 
			
		||||
      parameters:
 | 
			
		||||
        - in: path
 | 
			
		||||
          name: id
 | 
			
		||||
@@ -775,7 +1098,7 @@ paths:
 | 
			
		||||
      tags:
 | 
			
		||||
        - User Management
 | 
			
		||||
      operationId: updateUser
 | 
			
		||||
      summary: Modifying a single user
 | 
			
		||||
      summary: Modify a single user.
 | 
			
		||||
      parameters:
 | 
			
		||||
        - in: path
 | 
			
		||||
          name: id
 | 
			
		||||
@@ -788,6 +1111,134 @@ paths:
 | 
			
		||||
          schema:
 | 
			
		||||
            type: boolean
 | 
			
		||||
          required: false
 | 
			
		||||
        - in: query
 | 
			
		||||
          name: forgotPassword
 | 
			
		||||
          schema:
 | 
			
		||||
            type: boolean
 | 
			
		||||
            default: false
 | 
			
		||||
          required: false
 | 
			
		||||
        - in: query
 | 
			
		||||
          name: resetMFA
 | 
			
		||||
          schema:
 | 
			
		||||
            type: boolean
 | 
			
		||||
            default: false
 | 
			
		||||
          required: false
 | 
			
		||||
      requestBody:
 | 
			
		||||
        description: User details (some fields are ignored during update)
 | 
			
		||||
        content:
 | 
			
		||||
          application/json:
 | 
			
		||||
            schema:
 | 
			
		||||
              $ref: '#/components/schemas/UserInfo'
 | 
			
		||||
      responses:
 | 
			
		||||
        200:
 | 
			
		||||
          $ref: '#/components/schemas/UserInfo'
 | 
			
		||||
        403:
 | 
			
		||||
          $ref: '#/components/responses/Unauthorized'
 | 
			
		||||
        404:
 | 
			
		||||
          $ref: '#/components/responses/NotFound'
 | 
			
		||||
 | 
			
		||||
  /subuser/{id}:
 | 
			
		||||
    get:
 | 
			
		||||
      tags:
 | 
			
		||||
        - Subscribers
 | 
			
		||||
      operationId: getSubUser
 | 
			
		||||
      summary: Retrieve the information for a single user.
 | 
			
		||||
      parameters:
 | 
			
		||||
        - in: path
 | 
			
		||||
          name: id
 | 
			
		||||
          schema:
 | 
			
		||||
            type: string
 | 
			
		||||
            format: uuid
 | 
			
		||||
          required: true
 | 
			
		||||
      responses:
 | 
			
		||||
        200:
 | 
			
		||||
          $ref: '#/components/schemas/UserInfo'
 | 
			
		||||
        403:
 | 
			
		||||
          $ref: '#/components/responses/Unauthorized'
 | 
			
		||||
        404:
 | 
			
		||||
          $ref: '#/components/responses/NotFound'
 | 
			
		||||
 | 
			
		||||
    delete:
 | 
			
		||||
      tags:
 | 
			
		||||
        - Subscribers
 | 
			
		||||
      operationId: deleteSubUser
 | 
			
		||||
      summary: Delete a single user.
 | 
			
		||||
      parameters:
 | 
			
		||||
        - in: path
 | 
			
		||||
          name: id
 | 
			
		||||
          schema:
 | 
			
		||||
            type: integer
 | 
			
		||||
            format: int64
 | 
			
		||||
          required: true
 | 
			
		||||
      responses:
 | 
			
		||||
        204:
 | 
			
		||||
          $ref: '#/components/responses/Success'
 | 
			
		||||
        403:
 | 
			
		||||
          $ref: '#/components/responses/Unauthorized'
 | 
			
		||||
        404:
 | 
			
		||||
          $ref: '#/components/responses/NotFound'
 | 
			
		||||
 | 
			
		||||
    post:
 | 
			
		||||
      tags:
 | 
			
		||||
        - Subscribers
 | 
			
		||||
      operationId: createSubUser
 | 
			
		||||
      summary: Create a single user.
 | 
			
		||||
      parameters:
 | 
			
		||||
        - in: path
 | 
			
		||||
          name: id
 | 
			
		||||
          #must be set to 0 for user creation
 | 
			
		||||
          schema:
 | 
			
		||||
            type: integer
 | 
			
		||||
            format: int64
 | 
			
		||||
          required: true
 | 
			
		||||
        - in: query
 | 
			
		||||
          name: email_verification
 | 
			
		||||
          schema:
 | 
			
		||||
            type: boolean
 | 
			
		||||
          required: false
 | 
			
		||||
      requestBody:
 | 
			
		||||
        description: User details (some fields are ignored during creation)
 | 
			
		||||
        content:
 | 
			
		||||
          application/json:
 | 
			
		||||
            schema:
 | 
			
		||||
              $ref: '#/components/schemas/UserInfo'
 | 
			
		||||
      responses:
 | 
			
		||||
        200:
 | 
			
		||||
          $ref: '#/components/schemas/UserInfo'
 | 
			
		||||
        403:
 | 
			
		||||
          $ref: '#/components/responses/Unauthorized'
 | 
			
		||||
        404:
 | 
			
		||||
          $ref: '#/components/responses/NotFound'
 | 
			
		||||
 | 
			
		||||
    put:
 | 
			
		||||
      tags:
 | 
			
		||||
        - Subscribers
 | 
			
		||||
      operationId: updateSubUser
 | 
			
		||||
      summary: Modify a single user.
 | 
			
		||||
      parameters:
 | 
			
		||||
        - in: path
 | 
			
		||||
          name: id
 | 
			
		||||
          schema:
 | 
			
		||||
            type: integer
 | 
			
		||||
            format: int64
 | 
			
		||||
          required: true
 | 
			
		||||
        - in: query
 | 
			
		||||
          name: email_verification
 | 
			
		||||
          schema:
 | 
			
		||||
            type: boolean
 | 
			
		||||
          required: false
 | 
			
		||||
        - in: query
 | 
			
		||||
          name: forgotPassword
 | 
			
		||||
          schema:
 | 
			
		||||
            type: boolean
 | 
			
		||||
            default: false
 | 
			
		||||
          required: false
 | 
			
		||||
        - in: query
 | 
			
		||||
          name: resetMFA
 | 
			
		||||
          schema:
 | 
			
		||||
            type: boolean
 | 
			
		||||
            default: false
 | 
			
		||||
          required: false
 | 
			
		||||
      requestBody:
 | 
			
		||||
        description: User details (some fields are ignored during update)
 | 
			
		||||
        content:
 | 
			
		||||
@@ -807,7 +1258,7 @@ paths:
 | 
			
		||||
      tags:
 | 
			
		||||
        - Avatar
 | 
			
		||||
      operationId: getAvatar
 | 
			
		||||
      summary: Retrieve teh avatar associated with a user ID
 | 
			
		||||
      summary: Retrieve the avatar associated with a user ID.
 | 
			
		||||
      parameters:
 | 
			
		||||
        - in: path
 | 
			
		||||
          name: id
 | 
			
		||||
@@ -841,7 +1292,7 @@ paths:
 | 
			
		||||
      tags:
 | 
			
		||||
        - Avatar
 | 
			
		||||
      operationId: deleteAvatar
 | 
			
		||||
      summary: Remove an Avatar associated with a user ID
 | 
			
		||||
      summary: Remove an avatar associated with a user ID.
 | 
			
		||||
      parameters:
 | 
			
		||||
        - in: path
 | 
			
		||||
          name: id
 | 
			
		||||
@@ -861,7 +1312,7 @@ paths:
 | 
			
		||||
      tags:
 | 
			
		||||
        - Avatar
 | 
			
		||||
      operationId: createAvatar
 | 
			
		||||
      summary: Create an Avatar associated with a user ID
 | 
			
		||||
      summary: Create an avatar associated with a user ID.
 | 
			
		||||
      parameters:
 | 
			
		||||
        - in: path
 | 
			
		||||
          name: id
 | 
			
		||||
@@ -897,8 +1348,8 @@ paths:
 | 
			
		||||
  /email:
 | 
			
		||||
    post:
 | 
			
		||||
      tags:
 | 
			
		||||
        - EMail
 | 
			
		||||
      summary: Send test email with the system
 | 
			
		||||
        - Email
 | 
			
		||||
      summary: Send test email with the system.
 | 
			
		||||
      operationId: Send a test email
 | 
			
		||||
      requestBody:
 | 
			
		||||
        description: The requested message
 | 
			
		||||
@@ -925,6 +1376,266 @@ paths:
 | 
			
		||||
                    items:
 | 
			
		||||
                      type: string
 | 
			
		||||
 | 
			
		||||
  /sms:
 | 
			
		||||
    post:
 | 
			
		||||
      tags:
 | 
			
		||||
        - Email
 | 
			
		||||
      summary: Send test email with the system.
 | 
			
		||||
      operationId: Send a test SMS
 | 
			
		||||
      parameters:
 | 
			
		||||
        - in: query
 | 
			
		||||
          name: validateNumber
 | 
			
		||||
          schema:
 | 
			
		||||
            type: boolean
 | 
			
		||||
          required: false
 | 
			
		||||
        - in: query
 | 
			
		||||
          name: completeValidation
 | 
			
		||||
          schema:
 | 
			
		||||
            type: boolean
 | 
			
		||||
          required: false
 | 
			
		||||
        - in: query
 | 
			
		||||
          name: validationCode
 | 
			
		||||
          schema:
 | 
			
		||||
            type: string
 | 
			
		||||
          required: false
 | 
			
		||||
      requestBody:
 | 
			
		||||
        description: The requested message
 | 
			
		||||
        content:
 | 
			
		||||
          application/json:
 | 
			
		||||
            schema:
 | 
			
		||||
              $ref: '#/components/schemas/SMSInfo'
 | 
			
		||||
      responses:
 | 
			
		||||
        200:
 | 
			
		||||
          $ref: '#/components/responses/Success'
 | 
			
		||||
        403:
 | 
			
		||||
          $ref: '#/components/responses/Unauthorized'
 | 
			
		||||
        404:
 | 
			
		||||
          $ref: '#/components/responses/NotFound'
 | 
			
		||||
        500:
 | 
			
		||||
          description: Error description
 | 
			
		||||
          content:
 | 
			
		||||
            application/json:
 | 
			
		||||
              schema:
 | 
			
		||||
                type: object
 | 
			
		||||
                properties:
 | 
			
		||||
                  errors:
 | 
			
		||||
                    type: array
 | 
			
		||||
                    items:
 | 
			
		||||
                      type: string
 | 
			
		||||
 | 
			
		||||
  /userPreferences:
 | 
			
		||||
    get:
 | 
			
		||||
      tags:
 | 
			
		||||
        - Preferences
 | 
			
		||||
      operationId: getUserPreferences
 | 
			
		||||
      summary: Get the list of recorded preferences for a user
 | 
			
		||||
      responses:
 | 
			
		||||
        200:
 | 
			
		||||
          $ref: '#/components/schemas/Preferences'
 | 
			
		||||
        400:
 | 
			
		||||
          $ref: '#/components/responses/BadRequest'
 | 
			
		||||
    post:
 | 
			
		||||
      tags:
 | 
			
		||||
        - Preferences
 | 
			
		||||
      operationId: setUserPreferences
 | 
			
		||||
      summary: Set the list of recorded preferences for a user
 | 
			
		||||
      requestBody:
 | 
			
		||||
        description: Setting the list of preferences
 | 
			
		||||
        content:
 | 
			
		||||
          application/json:
 | 
			
		||||
            schema:
 | 
			
		||||
              $ref: '#/components/schemas/Preferences'
 | 
			
		||||
      responses:
 | 
			
		||||
        200:
 | 
			
		||||
          $ref: '#/components/schemas/Preferences'
 | 
			
		||||
        400:
 | 
			
		||||
          $ref: '#/components/responses/BadRequest'
 | 
			
		||||
 | 
			
		||||
  /submfa:
 | 
			
		||||
    get:
 | 
			
		||||
      tags:
 | 
			
		||||
        - MFA
 | 
			
		||||
      summary: Retrieve the cyrrent setting for MFA
 | 
			
		||||
      operationId: getMFS
 | 
			
		||||
      responses:
 | 
			
		||||
        200:
 | 
			
		||||
          $ref: '#/components/schemas/SubMfaConfig'
 | 
			
		||||
 | 
			
		||||
    put:
 | 
			
		||||
      tags:
 | 
			
		||||
        - MFA
 | 
			
		||||
      summary: Retrieve the cyrrent setting for MFA
 | 
			
		||||
      operationId: modifyMFS
 | 
			
		||||
      parameters:
 | 
			
		||||
        - in: query
 | 
			
		||||
          name: startValidation
 | 
			
		||||
          schema:
 | 
			
		||||
            type: boolean
 | 
			
		||||
          required: false
 | 
			
		||||
        - in: query
 | 
			
		||||
          name: completeValidation
 | 
			
		||||
          schema:
 | 
			
		||||
            type: boolean
 | 
			
		||||
          required: false
 | 
			
		||||
        - in: query
 | 
			
		||||
          name: challengeCode
 | 
			
		||||
          schema:
 | 
			
		||||
            type: string
 | 
			
		||||
          required: false
 | 
			
		||||
      requestBody:
 | 
			
		||||
        content:
 | 
			
		||||
          application/json:
 | 
			
		||||
            schema:
 | 
			
		||||
              $ref: '#/components/schemas/SubMfaConfig'
 | 
			
		||||
      responses:
 | 
			
		||||
        200:
 | 
			
		||||
          $ref: '#/components/schemas/SubMfaConfig'
 | 
			
		||||
        400:
 | 
			
		||||
          $ref: '#/components/responses/BadRequest'
 | 
			
		||||
 | 
			
		||||
  /totp:
 | 
			
		||||
    get:
 | 
			
		||||
      tags:
 | 
			
		||||
        - Security
 | 
			
		||||
      summary: Retrieve the Authenticator QR Code
 | 
			
		||||
      operationId: getTotpQrCode
 | 
			
		||||
      parameters:
 | 
			
		||||
        - in: query
 | 
			
		||||
          name: reset
 | 
			
		||||
          schema:
 | 
			
		||||
            type: boolean
 | 
			
		||||
            default: false
 | 
			
		||||
          required: false
 | 
			
		||||
      responses:
 | 
			
		||||
        200:
 | 
			
		||||
          description: QRCode
 | 
			
		||||
          content:
 | 
			
		||||
            image/svg+xml:
 | 
			
		||||
              schema:
 | 
			
		||||
                type: string
 | 
			
		||||
                format: binary
 | 
			
		||||
        400:
 | 
			
		||||
          $ref: '#/components/responses/BadRequest'
 | 
			
		||||
        403:
 | 
			
		||||
          $ref: '#/components/responses/Unauthorized'
 | 
			
		||||
 | 
			
		||||
    put:
 | 
			
		||||
      tags:
 | 
			
		||||
        - Security
 | 
			
		||||
      summary: Send the first security code to validate your setup
 | 
			
		||||
      operationId: sendToptTestCode
 | 
			
		||||
      parameters:
 | 
			
		||||
        - in: query
 | 
			
		||||
          name: value
 | 
			
		||||
          schema:
 | 
			
		||||
            type: integer
 | 
			
		||||
            format: int64
 | 
			
		||||
          required: true
 | 
			
		||||
        - in: query
 | 
			
		||||
          name: index
 | 
			
		||||
          schema:
 | 
			
		||||
            type: integer
 | 
			
		||||
            format: int64
 | 
			
		||||
            example: 1,2,3
 | 
			
		||||
          required: true
 | 
			
		||||
      responses:
 | 
			
		||||
        200:
 | 
			
		||||
          description: Succesful posting of response.
 | 
			
		||||
          content:
 | 
			
		||||
            application/json:
 | 
			
		||||
              schema:
 | 
			
		||||
                type: object
 | 
			
		||||
                properties:
 | 
			
		||||
                  nextIndex:
 | 
			
		||||
                    type: integer
 | 
			
		||||
                  moreCodes:
 | 
			
		||||
                    type: boolean
 | 
			
		||||
        400:
 | 
			
		||||
          $ref: '#/components/responses/BadRequest'
 | 
			
		||||
        403:
 | 
			
		||||
          $ref: '#/components/responses/Unauthorized'
 | 
			
		||||
 | 
			
		||||
  /signup:
 | 
			
		||||
    post:
 | 
			
		||||
      tags:
 | 
			
		||||
        - Subscriber Registration
 | 
			
		||||
      summary: This call allows a new subscriber to register themselves and their devices.
 | 
			
		||||
      operationId: postSignup
 | 
			
		||||
      parameters:
 | 
			
		||||
        - in: query
 | 
			
		||||
          name: email
 | 
			
		||||
          schema:
 | 
			
		||||
            type: string
 | 
			
		||||
            format: email
 | 
			
		||||
          required: true
 | 
			
		||||
        - in: query
 | 
			
		||||
          name: signupUUID
 | 
			
		||||
          schema:
 | 
			
		||||
            type: string
 | 
			
		||||
            format: uuid
 | 
			
		||||
          required: true
 | 
			
		||||
      responses:
 | 
			
		||||
        200:
 | 
			
		||||
          $ref: '#/components/schemas/UserInfo'
 | 
			
		||||
        400:
 | 
			
		||||
          $ref: '#/components/responses/BadRequest'
 | 
			
		||||
        403:
 | 
			
		||||
          $ref: '#/components/responses/Unauthorized'
 | 
			
		||||
        404:
 | 
			
		||||
          $ref: '#/components/responses/NotFound'
 | 
			
		||||
 | 
			
		||||
    put:
 | 
			
		||||
      tags:
 | 
			
		||||
        - Subscriber Registration
 | 
			
		||||
      summary: modify the signup command in play
 | 
			
		||||
      operationId: modifySignup
 | 
			
		||||
      parameters:
 | 
			
		||||
        - in: query
 | 
			
		||||
          name: signupUUID
 | 
			
		||||
          schema:
 | 
			
		||||
            type: string
 | 
			
		||||
            format: uuid
 | 
			
		||||
          required: true
 | 
			
		||||
        - in: query
 | 
			
		||||
          name: operation
 | 
			
		||||
          schema:
 | 
			
		||||
            type: string
 | 
			
		||||
            enum:
 | 
			
		||||
              - cancel
 | 
			
		||||
              - success
 | 
			
		||||
              - inprogress
 | 
			
		||||
              - failed
 | 
			
		||||
              - poll
 | 
			
		||||
              - emailVerified
 | 
			
		||||
          required: true
 | 
			
		||||
      requestBody:
 | 
			
		||||
        content:
 | 
			
		||||
          application/json:
 | 
			
		||||
            schema:
 | 
			
		||||
              type: object
 | 
			
		||||
              properties:
 | 
			
		||||
                reason:
 | 
			
		||||
                  type: string
 | 
			
		||||
                time:
 | 
			
		||||
                  type: integer
 | 
			
		||||
                  format: int64
 | 
			
		||||
                errorCode:
 | 
			
		||||
                  type: integer
 | 
			
		||||
                  format: int32
 | 
			
		||||
        required: false
 | 
			
		||||
 | 
			
		||||
      responses:
 | 
			
		||||
        200:
 | 
			
		||||
          $ref: '#/components/responses/Success'
 | 
			
		||||
        400:
 | 
			
		||||
          $ref: '#/components/responses/BadRequest'
 | 
			
		||||
        403:
 | 
			
		||||
          $ref: '#/components/responses/Unauthorized'
 | 
			
		||||
        404:
 | 
			
		||||
          $ref: '#/components/responses/NotFound'
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  #########################################################################################
 | 
			
		||||
  ##
 | 
			
		||||
  ## These are endpoints that all services in the uCentral stack must provide
 | 
			
		||||
@@ -934,7 +1645,7 @@ paths:
 | 
			
		||||
    get:
 | 
			
		||||
      tags:
 | 
			
		||||
        - Security
 | 
			
		||||
      summary: Retrieve the list of security profiles for a specific service type
 | 
			
		||||
      summary: Retrieve the list of security profiles for a specific service type.
 | 
			
		||||
      operationId: getSecurituProfiles
 | 
			
		||||
      parameters:
 | 
			
		||||
        - in: query
 | 
			
		||||
@@ -1000,11 +1711,32 @@ paths:
 | 
			
		||||
        404:
 | 
			
		||||
          $ref: '#/components/responses/NotFound'
 | 
			
		||||
 | 
			
		||||
  /validateSubToken:
 | 
			
		||||
    get:
 | 
			
		||||
      tags:
 | 
			
		||||
        - Security
 | 
			
		||||
        - Subscribers
 | 
			
		||||
      summary: Allows any microservice to validate a token and get security policy for a specific user.
 | 
			
		||||
      operationId: validateSubToken
 | 
			
		||||
      parameters:
 | 
			
		||||
        - in: query
 | 
			
		||||
          name: token
 | 
			
		||||
          schema:
 | 
			
		||||
            type: string
 | 
			
		||||
          required: true
 | 
			
		||||
      responses:
 | 
			
		||||
        200:
 | 
			
		||||
          $ref: '#/components/schemas/TokenValidationResult'
 | 
			
		||||
        403:
 | 
			
		||||
          $ref: '#/components/responses/Unauthorized'
 | 
			
		||||
        404:
 | 
			
		||||
          $ref: '#/components/responses/NotFound'
 | 
			
		||||
 | 
			
		||||
  /system:
 | 
			
		||||
    post:
 | 
			
		||||
      tags:
 | 
			
		||||
        - System Commands
 | 
			
		||||
      summary: Perform some systeme wide commands
 | 
			
		||||
      summary: Perform some system wide commands.
 | 
			
		||||
      operationId: systemCommand
 | 
			
		||||
      requestBody:
 | 
			
		||||
        description: Command details
 | 
			
		||||
@@ -1019,7 +1751,7 @@ paths:
 | 
			
		||||
                - $ref: '#/components/schemas/SystemCommandGetSubsystemNames'
 | 
			
		||||
      responses:
 | 
			
		||||
        200:
 | 
			
		||||
          description: Successfull command execution
 | 
			
		||||
          description: Successful command execution
 | 
			
		||||
          content:
 | 
			
		||||
            application/json:
 | 
			
		||||
              schema:
 | 
			
		||||
@@ -1048,7 +1780,7 @@ paths:
 | 
			
		||||
 | 
			
		||||
      responses:
 | 
			
		||||
        200:
 | 
			
		||||
          description: Successfull command execution
 | 
			
		||||
          description: Successful command execution
 | 
			
		||||
          content:
 | 
			
		||||
            application/json:
 | 
			
		||||
              schema:
 | 
			
		||||
@@ -1064,4 +1796,4 @@ paths:
 | 
			
		||||
##
 | 
			
		||||
## These are endpoints that all services in the uCentral stack must provide
 | 
			
		||||
##
 | 
			
		||||
#########################################################################################
 | 
			
		||||
#########################################################################################
 | 
			
		||||
@@ -40,9 +40,21 @@ openwifi.system.commandchannel = /tmp/app.ucentralsec
 | 
			
		||||
openwifi.service.key = $OWSEC_ROOT/certs/restapi-key.pem
 | 
			
		||||
openwifi.service.key.password = mypassword
 | 
			
		||||
 | 
			
		||||
smssender.enabled = false
 | 
			
		||||
smssender.provider = aws
 | 
			
		||||
smssender.aws.secretkey = ***************************************
 | 
			
		||||
smssender.aws.accesskey = ***************************************
 | 
			
		||||
smssender.aws.region = **************
 | 
			
		||||
 | 
			
		||||
#smssender.provider = twilio
 | 
			
		||||
#smssender.twilio.sid = ***********************
 | 
			
		||||
#smssender.twilio.token = **********************
 | 
			
		||||
#smssender.twilio.phonenumber = +18888888888
 | 
			
		||||
 | 
			
		||||
#
 | 
			
		||||
# Security Microservice Specific Section
 | 
			
		||||
#
 | 
			
		||||
mailer.enabled = false
 | 
			
		||||
mailer.hostname = smtp.gmail.com
 | 
			
		||||
mailer.username = ************************
 | 
			
		||||
mailer.password = ************************
 | 
			
		||||
@@ -70,10 +82,16 @@ openwifi.kafka.enable = true
 | 
			
		||||
openwifi.kafka.brokerlist = a1.arilia.com:9092
 | 
			
		||||
openwifi.kafka.auto.commit = false
 | 
			
		||||
openwifi.kafka.queue.buffering.max.ms = 50
 | 
			
		||||
openwifi.kafka.ssl.ca.location =
 | 
			
		||||
openwifi.kafka.ssl.certificate.location =
 | 
			
		||||
openwifi.kafka.ssl.key.location =
 | 
			
		||||
openwifi.kafka.ssl.key.password =
 | 
			
		||||
 | 
			
		||||
openwifi.document.policy.access = /wwwassets/access_policy.html
 | 
			
		||||
openwifi.document.policy.password = /wwwassets/password_policy.html
 | 
			
		||||
openwifi.avatar.maxsize = 2000000
 | 
			
		||||
 | 
			
		||||
totp.issuer = OpenWiFi
 | 
			
		||||
#
 | 
			
		||||
# This section select which form of persistence you need
 | 
			
		||||
# Only one selected at a time. If you select multiple, this service will die if a horrible
 | 
			
		||||
@@ -106,44 +124,12 @@ storage.type.mysql.database = ucentral
 | 
			
		||||
storage.type.mysql.port = 3306
 | 
			
		||||
storage.type.mysql.connectiontimeout = 60
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
########################################################################
 | 
			
		||||
########################################################################
 | 
			
		||||
#
 | 
			
		||||
# Logging: please leave as is for now.
 | 
			
		||||
#
 | 
			
		||||
########################################################################
 | 
			
		||||
logging.formatters.f1.class = PatternFormatter
 | 
			
		||||
logging.formatters.f1.pattern = %s: [%p] %t
 | 
			
		||||
logging.formatters.f1.times = UTC
 | 
			
		||||
logging.channels.c1.class = ConsoleChannel
 | 
			
		||||
logging.channels.c1.formatter = f1
 | 
			
		||||
 | 
			
		||||
# This is where the logs will be written. This path MUST exist
 | 
			
		||||
logging.channels.c2.class = FileChannel
 | 
			
		||||
logging.channels.c2.path = $OWSEC_ROOT/logs/log
 | 
			
		||||
logging.channels.c2.formatter.class = PatternFormatter
 | 
			
		||||
logging.channels.c2.formatter.pattern = %Y-%m-%d %H:%M:%S %s: [%p] %t
 | 
			
		||||
logging.channels.c2.rotation = 20 M
 | 
			
		||||
logging.channels.c2.archive = timestamp
 | 
			
		||||
logging.channels.c2.purgeCount = 20
 | 
			
		||||
logging.channels.c3.class = ConsoleChannel
 | 
			
		||||
logging.channels.c3.pattern = %s: [%p] %t
 | 
			
		||||
 | 
			
		||||
# External Channel
 | 
			
		||||
logging.loggers.root.channel = c2
 | 
			
		||||
logging.loggers.root.level = debug
 | 
			
		||||
 | 
			
		||||
# Inline Channel with PatternFormatter
 | 
			
		||||
# logging.loggers.l1.name = logger1
 | 
			
		||||
# logging.loggers.l1.channel.class = ConsoleChannel
 | 
			
		||||
# logging.loggers.l1.channel.pattern = %s: [%p] %t
 | 
			
		||||
# logging.loggers.l1.level = information
 | 
			
		||||
# SplitterChannel
 | 
			
		||||
# logging.channels.splitter.class = SplitterChannel
 | 
			
		||||
# logging.channels.splitter.channels = l1,l2
 | 
			
		||||
# logging.loggers.l2.name = logger2
 | 
			
		||||
# logging.loggers.l2.channel = splitter
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
logging.type = file
 | 
			
		||||
logging.path = $OWSEC_ROOT/logs
 | 
			
		||||
logging.level = debug
 | 
			
		||||
@@ -40,16 +40,28 @@ openwifi.system.commandchannel = /tmp/app.ucentralsec
 | 
			
		||||
openwifi.service.key = ${SERVICE_KEY}
 | 
			
		||||
openwifi.service.key.password = ${SERVICE_KEY_PASSWORD}
 | 
			
		||||
 | 
			
		||||
smssender.enabled = ${SMSSENDER_ENABLED}
 | 
			
		||||
smssender.provider = ${SMSSENDER_PROVIDER}
 | 
			
		||||
 | 
			
		||||
smssender.aws.secretkey = ${SMSSENDER_AWS_SECRETKEY}
 | 
			
		||||
smssender.aws.accesskey = ${SMSSENDER_AWS_ACCESSKEY}
 | 
			
		||||
smssender.aws.region = ${SMSSENDER_AWS_REGION}
 | 
			
		||||
 | 
			
		||||
smssender.twilio.sid = ${SMSSENDER_TWILIO_SID}
 | 
			
		||||
smssender.twilio.token = ${SMSSENDER_TWILIO_TOKEN}
 | 
			
		||||
smssender.twilio.phonenumber = ${SMSSENDER_TWILIO_PHONENUMBER}
 | 
			
		||||
 | 
			
		||||
#
 | 
			
		||||
# Security Microservice Specific Section
 | 
			
		||||
#
 | 
			
		||||
mailer.enabled = ${MAILER_ENABLED}
 | 
			
		||||
mailer.hostname = ${MAILER_HOSTNAME}
 | 
			
		||||
mailer.username = ${MAILER_USERNAME}
 | 
			
		||||
mailer.password = ${MAILER_PASSWORD}
 | 
			
		||||
mailer.sender = ${MAILER_SENDER}
 | 
			
		||||
mailer.loginmethod = login
 | 
			
		||||
mailer.port = ${MAILER_PORT}
 | 
			
		||||
mailer.templates = $UCENTRALSEC_ROOT/templates
 | 
			
		||||
mailer.templates = ${MAILER_TEMPLATES}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#############################
 | 
			
		||||
@@ -70,9 +82,13 @@ openwifi.kafka.enable = ${KAFKA_ENABLE}
 | 
			
		||||
openwifi.kafka.brokerlist = ${KAFKA_BROKERLIST}
 | 
			
		||||
openwifi.kafka.auto.commit = false
 | 
			
		||||
openwifi.kafka.queue.buffering.max.ms = 50
 | 
			
		||||
openwifi.kafka.ssl.ca.location = ${KAFKA_SSL_CA_LOCATION}
 | 
			
		||||
openwifi.kafka.ssl.certificate.location = ${KAFKA_SSL_CERTIFICATE_LOCATION}
 | 
			
		||||
openwifi.kafka.ssl.key.location = ${KAFKA_SSL_KEY_LOCATION}
 | 
			
		||||
openwifi.kafka.ssl.key.password = ${KAFKA_SSL_KEY_PASSWORD}
 | 
			
		||||
 | 
			
		||||
openwifi.document.policy.access = /wwwassets/access_policy.html
 | 
			
		||||
openwifi.document.policy.password = /wwwassets/password_policy.html
 | 
			
		||||
openwifi.document.policy.access = ${DOCUMENT_POLICY_ACCESS}
 | 
			
		||||
openwifi.document.policy.password = ${DOCUMENT_POLICY_PASSWORD}
 | 
			
		||||
openwifi.avatar.maxsize = 2000000
 | 
			
		||||
#
 | 
			
		||||
# This section select which form of persistence you need
 | 
			
		||||
@@ -110,37 +126,6 @@ storage.type.mysql.connectiontimeout = 60
 | 
			
		||||
# Logging: please leave as is for now.
 | 
			
		||||
#
 | 
			
		||||
########################################################################
 | 
			
		||||
logging.formatters.f1.class = PatternFormatter
 | 
			
		||||
logging.formatters.f1.pattern = %Y-%m-%d %H:%M:%S %s: [%p] %t
 | 
			
		||||
logging.formatters.f1.times = UTC
 | 
			
		||||
logging.channels.c1.class = ConsoleChannel
 | 
			
		||||
logging.channels.c1.formatter = f1
 | 
			
		||||
 | 
			
		||||
# This is where the logs will be written. This path MUST exist
 | 
			
		||||
logging.channels.c2.class = FileChannel
 | 
			
		||||
logging.channels.c2.path = $UCENTRALSEC_ROOT/logs/log
 | 
			
		||||
logging.channels.c2.formatter.class = PatternFormatter
 | 
			
		||||
logging.channels.c2.formatter.pattern = %Y-%m-%d %H:%M:%S %s: [%p] %t
 | 
			
		||||
logging.channels.c2.rotation = 20 M
 | 
			
		||||
logging.channels.c2.archive = timestamp
 | 
			
		||||
logging.channels.c2.purgeCount = 20
 | 
			
		||||
logging.channels.c3.class = ConsoleChannel
 | 
			
		||||
logging.channels.c3.pattern = %s: [%p] %t
 | 
			
		||||
 | 
			
		||||
# External Channel
 | 
			
		||||
logging.loggers.root.channel = c1
 | 
			
		||||
logging.loggers.root.level = debug
 | 
			
		||||
 | 
			
		||||
# Inline Channel with PatternFormatter
 | 
			
		||||
# logging.loggers.l1.name = logger1
 | 
			
		||||
# logging.loggers.l1.channel.class = ConsoleChannel
 | 
			
		||||
# logging.loggers.l1.channel.pattern = %s: [%p] %t
 | 
			
		||||
# logging.loggers.l1.level = information
 | 
			
		||||
# SplitterChannel
 | 
			
		||||
# logging.channels.splitter.class = SplitterChannel
 | 
			
		||||
# logging.channels.splitter.channels = l1,l2
 | 
			
		||||
# logging.loggers.l2.name = logger2
 | 
			
		||||
# logging.loggers.l2.channel = splitter
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
logging.type = console
 | 
			
		||||
logging.path = $OWSEC_ROOT/logs
 | 
			
		||||
logging.level = debug
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										59
									
								
								readiness_check
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										59
									
								
								readiness_check
									
									
									
									
									
										Executable file
									
								
							@@ -0,0 +1,59 @@
 | 
			
		||||
#!/bin/bash
 | 
			
		||||
set -e
 | 
			
		||||
 | 
			
		||||
if [[ "$(which jq)" == "" ]]
 | 
			
		||||
then
 | 
			
		||||
  echo "You need the package jq installed to use this script."
 | 
			
		||||
  exit 1
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
if [[ "$(which curl)" == "" ]]
 | 
			
		||||
then
 | 
			
		||||
  echo "You need the package curl installed to use this script."
 | 
			
		||||
  exit 1
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
if [[ "${READINESS_METHOD}" == "systeminfo" ]]
 | 
			
		||||
then
 | 
			
		||||
  if [[ "${OWSEC_USERNAME}" == "" ]]
 | 
			
		||||
  then
 | 
			
		||||
    echo "You must set the variable OWSEC_USERNAME in order to use this script. Something like"
 | 
			
		||||
    echo "OWSEC_USERNAME=tip@ucentral.com"
 | 
			
		||||
    exit 1
 | 
			
		||||
  fi
 | 
			
		||||
 | 
			
		||||
  if [[ "${OWSEC_PASSWORD}" == "" ]]
 | 
			
		||||
  then
 | 
			
		||||
    echo "You must set the variable OWSEC_PASSWORD in order to use this script. Something like"
 | 
			
		||||
    echo "OWSEC_PASSWORD=openwifi"
 | 
			
		||||
    exit 1
 | 
			
		||||
  fi
 | 
			
		||||
 | 
			
		||||
  export RESTAPI_PORT=$(grep 'openwifi.restapi.host.0.port' $OWSEC_CONFIG/owsec.properties | awk -F '=' '{print $2}' | xargs | envsubst)
 | 
			
		||||
  # Get OAuth token from OWSEC and cache it or use cached one
 | 
			
		||||
  payload="{ \"userId\" : \"$OWSEC_USERNAME\" , \"password\" : \"$OWSEC_PASSWORD\" }"
 | 
			
		||||
  if [[ -f "/tmp/token" ]]
 | 
			
		||||
  then
 | 
			
		||||
    token=$(cat /tmp/token)
 | 
			
		||||
  else
 | 
			
		||||
    token=$(curl ${FLAGS} -k -X POST -H "Content-Type: application/json" -d "$payload" "https://localhost:$RESTAPI_PORT/api/v1/oauth2" | jq -r '.access_token')
 | 
			
		||||
  fi
 | 
			
		||||
  if [[ "${token}" == "" ]]
 | 
			
		||||
  then
 | 
			
		||||
    echo "Could not login. Please verify the host and username/password."
 | 
			
		||||
    exit 13
 | 
			
		||||
  fi
 | 
			
		||||
  echo -n $token > /tmp/token
 | 
			
		||||
 | 
			
		||||
  # Make systeminfo request to the local owsec instance
 | 
			
		||||
  curl ${FLAGS} -k -X GET "https://localhost:$RESTAPI_PORT/api/v1/system?command=info" \
 | 
			
		||||
    -H "accept: application/json" \
 | 
			
		||||
    -H "Authorization: Bearer ${token}" > /tmp/result.json
 | 
			
		||||
  exit_code=$?
 | 
			
		||||
  jq < /tmp/result.json
 | 
			
		||||
  exit $exit_code
 | 
			
		||||
else
 | 
			
		||||
  export ALB_PORT=$(grep 'alb.port' $OWSEC_CONFIG/owsec.properties | awk -F '=' '{print $2}' | xargs | envsubst)
 | 
			
		||||
  curl localhost:$ALB_PORT
 | 
			
		||||
fi
 | 
			
		||||
							
								
								
									
										90
									
								
								src/ACLProcessor.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										90
									
								
								src/ACLProcessor.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,90 @@
 | 
			
		||||
//
 | 
			
		||||
// Created by stephane bourque on 2021-11-12.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#ifndef OWSEC_ACLPROCESSOR_H
 | 
			
		||||
#define OWSEC_ACLPROCESSOR_H
 | 
			
		||||
 | 
			
		||||
#include "RESTObjects/RESTAPI_SecurityObjects.h"
 | 
			
		||||
 | 
			
		||||
namespace OpenWifi {
 | 
			
		||||
 | 
			
		||||
    class ACLProcessor {
 | 
			
		||||
    public:
 | 
			
		||||
        enum ACL_OPS {
 | 
			
		||||
            READ,
 | 
			
		||||
            MODIFY,
 | 
			
		||||
            DELETE,
 | 
			
		||||
            CREATE
 | 
			
		||||
        };
 | 
			
		||||
/*
 | 
			
		||||
 *  0) You can only delete yourself if you are a subscriber
 | 
			
		||||
    1) You cannot delete yourself
 | 
			
		||||
    2) If you are root, you can do anything.
 | 
			
		||||
    3) You can do anything to yourself
 | 
			
		||||
    4) Nobody can touch a root, unless they are a root, unless it is to get information on a ROOT
 | 
			
		||||
    5) Creation rules:
 | 
			
		||||
        ROOT -> create anything
 | 
			
		||||
        PARTNER -> (multi-tenant owner) admin,subs,csr,installer,noc,accounting - matches to an entity in provisioning
 | 
			
		||||
        ADMIN -> admin-subs-csr-installer-noc-accounting
 | 
			
		||||
        ACCOUNTING -> subs-installer-csr
 | 
			
		||||
 | 
			
		||||
 */
 | 
			
		||||
        static inline bool Can( const SecurityObjects::UserInfo & User, const SecurityObjects::UserInfo & Target, ACL_OPS Op) {
 | 
			
		||||
 | 
			
		||||
            // rule 0
 | 
			
		||||
            if(User.id == Target.id && User.userRole == SecurityObjects::SUBSCRIBER && Op == DELETE)
 | 
			
		||||
                return true;
 | 
			
		||||
 | 
			
		||||
            //  rule 1
 | 
			
		||||
            if(User.id == Target.id && Op==DELETE)
 | 
			
		||||
                return false;
 | 
			
		||||
 | 
			
		||||
            //  rule 2
 | 
			
		||||
            if(User.userRole==SecurityObjects::ROOT)
 | 
			
		||||
                return true;
 | 
			
		||||
 | 
			
		||||
            //  rule 3
 | 
			
		||||
            if(User.id == Target.id)
 | 
			
		||||
                return true;
 | 
			
		||||
 | 
			
		||||
            //  rule 4
 | 
			
		||||
            if(Target.userRole==SecurityObjects::ROOT && Op!=READ)
 | 
			
		||||
                return false;
 | 
			
		||||
 | 
			
		||||
            if(Op==CREATE) {
 | 
			
		||||
                if(User.userRole==SecurityObjects::ROOT)
 | 
			
		||||
                    return true;
 | 
			
		||||
                if(User.userRole==SecurityObjects::PARTNER && (Target.userRole==SecurityObjects::ADMIN ||
 | 
			
		||||
                    Target.userRole==SecurityObjects::SUBSCRIBER ||
 | 
			
		||||
                    Target.userRole==SecurityObjects::CSR ||
 | 
			
		||||
                    Target.userRole==SecurityObjects::INSTALLER ||
 | 
			
		||||
                    Target.userRole==SecurityObjects::NOC ||
 | 
			
		||||
                    Target.userRole==SecurityObjects::ACCOUNTING))
 | 
			
		||||
                    return true;
 | 
			
		||||
                if(User.userRole==SecurityObjects::ADMIN &&
 | 
			
		||||
                    (Target.userRole==SecurityObjects::ADMIN ||
 | 
			
		||||
                    Target.userRole==SecurityObjects::SUBSCRIBER ||
 | 
			
		||||
                    Target.userRole==SecurityObjects::CSR ||
 | 
			
		||||
                    Target.userRole==SecurityObjects::INSTALLER ||
 | 
			
		||||
                    Target.userRole==SecurityObjects::NOC ||
 | 
			
		||||
                    Target.userRole==SecurityObjects::ACCOUNTING))
 | 
			
		||||
                    return true;
 | 
			
		||||
                if(User.userRole==SecurityObjects::ACCOUNTING &&
 | 
			
		||||
                    (Target.userRole==SecurityObjects::SUBSCRIBER ||
 | 
			
		||||
                    Target.userRole==SecurityObjects::INSTALLER ||
 | 
			
		||||
                    Target.userRole==SecurityObjects::CSR))
 | 
			
		||||
                    return true;
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
    private:
 | 
			
		||||
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#endif //OWSEC_ACLPROCESSOR_H
 | 
			
		||||
@@ -1,118 +0,0 @@
 | 
			
		||||
//
 | 
			
		||||
//	License type: BSD 3-Clause License
 | 
			
		||||
//	License copy: https://github.com/Telecominfraproject/wlan-cloud-ucentralgw/blob/master/LICENSE
 | 
			
		||||
//
 | 
			
		||||
//	Created by Stephane Bourque on 2021-03-04.
 | 
			
		||||
//	Arilia Wireless Inc.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#ifndef UCENTRALGW_ALBHEALTHCHECKSERVER_H
 | 
			
		||||
#define UCENTRALGW_ALBHEALTHCHECKSERVER_H
 | 
			
		||||
 | 
			
		||||
#include <memory>
 | 
			
		||||
#include <iostream>
 | 
			
		||||
#include <fstream>
 | 
			
		||||
#include <sstream>
 | 
			
		||||
 | 
			
		||||
#include "Poco/Thread.h"
 | 
			
		||||
#include "Poco/Net/HTTPServer.h"
 | 
			
		||||
#include "Poco/Net/HTTPServerRequest.h"
 | 
			
		||||
#include "Poco/Net/HTTPServerResponse.h"
 | 
			
		||||
#include "Poco/Net/HTTPRequestHandler.h"
 | 
			
		||||
#include "Poco/Logger.h"
 | 
			
		||||
 | 
			
		||||
#include "Daemon.h"
 | 
			
		||||
#include "SubSystemServer.h"
 | 
			
		||||
 | 
			
		||||
namespace OpenWifi {
 | 
			
		||||
 | 
			
		||||
	class ALBRequestHandler: public Poco::Net::HTTPRequestHandler
 | 
			
		||||
			/// Return a HTML document with the current date and time.
 | 
			
		||||
		{
 | 
			
		||||
		  public:
 | 
			
		||||
			explicit ALBRequestHandler(Poco::Logger & L)
 | 
			
		||||
				: Logger_(L)
 | 
			
		||||
			{
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			void handleRequest(Poco::Net::HTTPServerRequest& Request, Poco::Net::HTTPServerResponse& Response) override
 | 
			
		||||
			{
 | 
			
		||||
				Logger_.information(Poco::format("ALB-REQUEST(%s): New ALB request.",Request.clientAddress().toString()));
 | 
			
		||||
				Response.setChunkedTransferEncoding(true);
 | 
			
		||||
				Response.setContentType("text/html");
 | 
			
		||||
				Response.setDate(Poco::Timestamp());
 | 
			
		||||
				Response.setStatus(Poco::Net::HTTPResponse::HTTP_OK);
 | 
			
		||||
				Response.setKeepAlive(true);
 | 
			
		||||
				Response.set("Connection","keep-alive");
 | 
			
		||||
				Response.setVersion(Poco::Net::HTTPMessage::HTTP_1_1);
 | 
			
		||||
				std::ostream &Answer = Response.send();
 | 
			
		||||
				Answer << "uCentralGW Alive and kicking!" ;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
	  private:
 | 
			
		||||
		Poco::Logger 	& Logger_;
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	class ALBRequestHandlerFactory: public Poco::Net::HTTPRequestHandlerFactory
 | 
			
		||||
		{
 | 
			
		||||
		  public:
 | 
			
		||||
			explicit ALBRequestHandlerFactory(Poco::Logger & L):
 | 
			
		||||
				Logger_(L)
 | 
			
		||||
			{
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			ALBRequestHandler* createRequestHandler(const Poco::Net::HTTPServerRequest& request) override
 | 
			
		||||
			{
 | 
			
		||||
				if (request.getURI() == "/")
 | 
			
		||||
					return new ALBRequestHandler(Logger_);
 | 
			
		||||
				else
 | 
			
		||||
					return nullptr;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
		  private:
 | 
			
		||||
			Poco::Logger	&Logger_;
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
    class ALBHealthCheckServer : public SubSystemServer {
 | 
			
		||||
        public:
 | 
			
		||||
            ALBHealthCheckServer() noexcept:
 | 
			
		||||
                    SubSystemServer("ALBHealthCheckServer", "ALB-SVR", "alb")
 | 
			
		||||
            {
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            static ALBHealthCheckServer *instance() {
 | 
			
		||||
                if (instance_ == nullptr) {
 | 
			
		||||
                    instance_ = new ALBHealthCheckServer;
 | 
			
		||||
                }
 | 
			
		||||
                return instance_;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            int Start() override {
 | 
			
		||||
                if(Daemon()->ConfigGetBool("alb.enable",false)) {
 | 
			
		||||
                    Port_ = (int)Daemon()->ConfigGetInt("alb.port",15015);
 | 
			
		||||
                    Socket_ = std::make_unique<Poco::Net::ServerSocket>(Port_);
 | 
			
		||||
                    auto Params = new Poco::Net::HTTPServerParams;
 | 
			
		||||
                    Server_ = std::make_unique<Poco::Net::HTTPServer>(new ALBRequestHandlerFactory(Logger_), *Socket_, Params);
 | 
			
		||||
                    Server_->start();
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                return 0;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            void Stop() override {
 | 
			
		||||
                if(Server_)
 | 
			
		||||
                    Server_->stop();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
          private:
 | 
			
		||||
            static ALBHealthCheckServer *instance_;
 | 
			
		||||
            std::unique_ptr<Poco::Net::HTTPServer>   	Server_;
 | 
			
		||||
            std::unique_ptr<Poco::Net::ServerSocket> 	Socket_;
 | 
			
		||||
            int                                     	Port_ = 0;
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
    inline ALBHealthCheckServer * ALBHealthCheckServer() { return ALBHealthCheckServer::instance(); }
 | 
			
		||||
    inline class ALBHealthCheckServer * ALBHealthCheckServer::instance_ = nullptr;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#endif // UCENTRALGW_ALBHEALTHCHECKSERVER_H
 | 
			
		||||
							
								
								
									
										106
									
								
								src/ActionLinkManager.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										106
									
								
								src/ActionLinkManager.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,106 @@
 | 
			
		||||
//
 | 
			
		||||
// Created by stephane bourque on 2021-11-08.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#include "ActionLinkManager.h"
 | 
			
		||||
#include "StorageService.h"
 | 
			
		||||
#include "RESTObjects/RESTAPI_SecurityObjects.h"
 | 
			
		||||
 | 
			
		||||
namespace OpenWifi {
 | 
			
		||||
 | 
			
		||||
    int ActionLinkManager::Start() {
 | 
			
		||||
        if(!Running_)
 | 
			
		||||
            Thr_.start(*this);
 | 
			
		||||
        return 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void ActionLinkManager::Stop() {
 | 
			
		||||
        if(Running_) {
 | 
			
		||||
            Running_ = false;
 | 
			
		||||
            Thr_.wakeUp();
 | 
			
		||||
            Thr_.join();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void ActionLinkManager::run() {
 | 
			
		||||
        Running_ = true ;
 | 
			
		||||
 | 
			
		||||
        while(Running_) {
 | 
			
		||||
            Poco::Thread::trySleep(2000);
 | 
			
		||||
            if(!Running_)
 | 
			
		||||
                break;
 | 
			
		||||
            std::vector<SecurityObjects::ActionLink>    Links;
 | 
			
		||||
            {
 | 
			
		||||
                std::lock_guard G(Mutex_);
 | 
			
		||||
                StorageService()->ActionLinksDB().GetActions(Links);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if(Links.empty())
 | 
			
		||||
                continue;
 | 
			
		||||
 | 
			
		||||
            for(auto &i:Links) {
 | 
			
		||||
                if(!Running_)
 | 
			
		||||
                    break;
 | 
			
		||||
 | 
			
		||||
                SecurityObjects::UserInfo UInfo;
 | 
			
		||||
                if((i.action==OpenWifi::SecurityObjects::LinkActions::FORGOT_PASSWORD ||
 | 
			
		||||
                    i.action==OpenWifi::SecurityObjects::LinkActions::VERIFY_EMAIL) && !StorageService()->UserDB().GetUserById(i.userId,UInfo)) {
 | 
			
		||||
                    StorageService()->ActionLinksDB().CancelAction(i.id);
 | 
			
		||||
                    continue;
 | 
			
		||||
                } else if(( i.action==OpenWifi::SecurityObjects::LinkActions::SUB_FORGOT_PASSWORD ||
 | 
			
		||||
                            i.action==OpenWifi::SecurityObjects::LinkActions::SUB_VERIFY_EMAIL ||
 | 
			
		||||
                            i.action==OpenWifi::SecurityObjects::LinkActions::SUB_SIGNUP ) && !StorageService()->SubDB().GetUserById(i.userId,UInfo)) {
 | 
			
		||||
                    StorageService()->ActionLinksDB().CancelAction(i.id);
 | 
			
		||||
                    continue;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                switch(i.action) {
 | 
			
		||||
                    case OpenWifi::SecurityObjects::LinkActions::FORGOT_PASSWORD: {
 | 
			
		||||
                            if(AuthService::SendEmailToUser(i.id, UInfo.email, AuthService::FORGOT_PASSWORD)) {
 | 
			
		||||
                                Logger().information(fmt::format("Send password reset link to {}",UInfo.email));
 | 
			
		||||
                            }
 | 
			
		||||
                            StorageService()->ActionLinksDB().SentAction(i.id);
 | 
			
		||||
                        }
 | 
			
		||||
                        break;
 | 
			
		||||
 | 
			
		||||
                    case OpenWifi::SecurityObjects::LinkActions::VERIFY_EMAIL: {
 | 
			
		||||
                            if(AuthService::SendEmailToUser(i.id, UInfo.email, AuthService::EMAIL_VERIFICATION)) {
 | 
			
		||||
                                Logger().information(fmt::format("Send email verification link to {}",UInfo.email));
 | 
			
		||||
                            }
 | 
			
		||||
                            StorageService()->ActionLinksDB().SentAction(i.id);
 | 
			
		||||
                        }
 | 
			
		||||
                        break;
 | 
			
		||||
 | 
			
		||||
                    case OpenWifi::SecurityObjects::LinkActions::SUB_FORGOT_PASSWORD: {
 | 
			
		||||
                            if(AuthService::SendEmailToSubUser(i.id, UInfo.email, AuthService::FORGOT_PASSWORD)) {
 | 
			
		||||
                                Logger().information(fmt::format("Send subscriber password reset link to {}",UInfo.email));
 | 
			
		||||
                            }
 | 
			
		||||
                            StorageService()->ActionLinksDB().SentAction(i.id);
 | 
			
		||||
                        }
 | 
			
		||||
                        break;
 | 
			
		||||
 | 
			
		||||
                    case OpenWifi::SecurityObjects::LinkActions::SUB_VERIFY_EMAIL: {
 | 
			
		||||
                            if(AuthService::SendEmailToSubUser(i.id, UInfo.email, AuthService::EMAIL_VERIFICATION)) {
 | 
			
		||||
                                Logger().information(fmt::format("Send subscriber email verification link to {}",UInfo.email));
 | 
			
		||||
                            }
 | 
			
		||||
                            StorageService()->ActionLinksDB().SentAction(i.id);
 | 
			
		||||
                        }
 | 
			
		||||
                        break;
 | 
			
		||||
 | 
			
		||||
                    case OpenWifi::SecurityObjects::LinkActions::SUB_SIGNUP: {
 | 
			
		||||
                        if(AuthService::SendEmailToSubUser(i.id, UInfo.email, AuthService::SIGNUP_VERIFICATION)) {
 | 
			
		||||
                            Logger().information(fmt::format("Send new subscriber email verification link to {}",UInfo.email));
 | 
			
		||||
                        }
 | 
			
		||||
                        StorageService()->ActionLinksDB().SentAction(i.id);
 | 
			
		||||
                        }
 | 
			
		||||
                        break;
 | 
			
		||||
 | 
			
		||||
                    default: {
 | 
			
		||||
                        StorageService()->ActionLinksDB().SentAction(i.id);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										44
									
								
								src/ActionLinkManager.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								src/ActionLinkManager.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,44 @@
 | 
			
		||||
//
 | 
			
		||||
// Created by stephane bourque on 2021-11-08.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#ifndef OWSEC_ACTIONLINKMANAGER_H
 | 
			
		||||
#define OWSEC_ACTIONLINKMANAGER_H
 | 
			
		||||
 | 
			
		||||
#include "framework/MicroService.h"
 | 
			
		||||
 | 
			
		||||
namespace OpenWifi {
 | 
			
		||||
 | 
			
		||||
    class ActionLinkManager : public SubSystemServer, Poco::Runnable {
 | 
			
		||||
    public:
 | 
			
		||||
 | 
			
		||||
/*        enum Actions {
 | 
			
		||||
            FORGOT_PASSWORD,
 | 
			
		||||
            VERIFY_EMAIL,
 | 
			
		||||
            SUB_FORGOT_PASSWORD,
 | 
			
		||||
            SUB_VERIFY_EMAIL,
 | 
			
		||||
            SUB_SIGNUP
 | 
			
		||||
        };
 | 
			
		||||
*/
 | 
			
		||||
        static ActionLinkManager * instance() {
 | 
			
		||||
            static auto instance_ = new ActionLinkManager;
 | 
			
		||||
            return instance_;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        int Start() final;
 | 
			
		||||
        void Stop() final;
 | 
			
		||||
        void run() final;
 | 
			
		||||
 | 
			
		||||
    private:
 | 
			
		||||
        Poco::Thread        Thr_;
 | 
			
		||||
        std::atomic_bool    Running_ = false;
 | 
			
		||||
 | 
			
		||||
        ActionLinkManager() noexcept:
 | 
			
		||||
            SubSystemServer("ActionLinkManager", "ACTION-SVR", "action.server")
 | 
			
		||||
                {
 | 
			
		||||
                }
 | 
			
		||||
    };
 | 
			
		||||
    inline ActionLinkManager * ActionLinkManager() { return ActionLinkManager::instance(); }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#endif //OWSEC_ACTIONLINKMANAGER_H
 | 
			
		||||
@@ -1,93 +0,0 @@
 | 
			
		||||
//
 | 
			
		||||
//	License type: BSD 3-Clause License
 | 
			
		||||
//	License copy: https://github.com/Telecominfraproject/wlan-cloud-ucentralgw/blob/master/LICENSE
 | 
			
		||||
//
 | 
			
		||||
//	Created by Stephane Bourque on 2021-03-04.
 | 
			
		||||
//	Arilia Wireless Inc.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#include <utility>
 | 
			
		||||
 | 
			
		||||
#include "AuthClient.h"
 | 
			
		||||
#include "RESTAPI_SecurityObjects.h"
 | 
			
		||||
#include "Daemon.h"
 | 
			
		||||
#include "OpenAPIRequest.h"
 | 
			
		||||
 | 
			
		||||
namespace OpenWifi {
 | 
			
		||||
	class AuthClient * AuthClient::instance_ = nullptr;
 | 
			
		||||
 | 
			
		||||
	int AuthClient::Start() {
 | 
			
		||||
		return 0;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	void AuthClient::Stop() {
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	void AuthClient::RemovedCachedToken(const std::string &Token) {
 | 
			
		||||
		std::lock_guard	G(Mutex_);
 | 
			
		||||
		UserCache_.erase(Token);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	bool IsTokenExpired(const SecurityObjects::WebToken &T) {
 | 
			
		||||
		return ((T.expires_in_+T.created_)<std::time(nullptr));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	bool AuthClient::IsAuthorized(Poco::Net::HTTPServerRequest & Request, std::string &SessionToken, SecurityObjects::UserInfoAndPolicy & UInfo ) {
 | 
			
		||||
		std::lock_guard G(Mutex_);
 | 
			
		||||
 | 
			
		||||
		auto User = UserCache_.find(SessionToken);
 | 
			
		||||
		if(User != UserCache_.end() && !IsTokenExpired(User->second.webtoken)) {
 | 
			
		||||
			UInfo = User->second;
 | 
			
		||||
			return true;
 | 
			
		||||
		} else {
 | 
			
		||||
			Types::StringPairVec QueryData;
 | 
			
		||||
			QueryData.push_back(std::make_pair("token",SessionToken));
 | 
			
		||||
			OpenAPIRequestGet	Req(    uSERVICE_SECURITY,
 | 
			
		||||
								  	"/api/v1/validateToken",
 | 
			
		||||
									 QueryData,
 | 
			
		||||
								  5000);
 | 
			
		||||
			Poco::JSON::Object::Ptr Response;
 | 
			
		||||
			if(Req.Do(Response)==Poco::Net::HTTPResponse::HTTP_OK) {
 | 
			
		||||
				if(Response->has("tokenInfo") && Response->has("userInfo")) {
 | 
			
		||||
					SecurityObjects::UserInfoAndPolicy	P;
 | 
			
		||||
					P.from_json(Response);
 | 
			
		||||
					UserCache_[SessionToken] = P;
 | 
			
		||||
					UInfo = P;
 | 
			
		||||
				}
 | 
			
		||||
				return true;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
		}
 | 
			
		||||
		return false;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	bool AuthClient::IsTokenAuthorized(const std::string &SessionToken, SecurityObjects::UserInfoAndPolicy & UInfo) {
 | 
			
		||||
		std::lock_guard G(Mutex_);
 | 
			
		||||
 | 
			
		||||
		auto User = UserCache_.find(SessionToken);
 | 
			
		||||
		if(User != UserCache_.end() && !IsTokenExpired(User->second.webtoken)) {
 | 
			
		||||
			UInfo = User->second;
 | 
			
		||||
			return true;
 | 
			
		||||
		} else {
 | 
			
		||||
			Types::StringPairVec QueryData;
 | 
			
		||||
			QueryData.push_back(std::make_pair("token",SessionToken));
 | 
			
		||||
			OpenAPIRequestGet	Req(uSERVICE_SECURITY,
 | 
			
		||||
									 "/api/v1/validateToken",
 | 
			
		||||
									 QueryData,
 | 
			
		||||
									 5000);
 | 
			
		||||
			Poco::JSON::Object::Ptr Response;
 | 
			
		||||
			if(Req.Do(Response)==Poco::Net::HTTPResponse::HTTP_OK) {
 | 
			
		||||
				if(Response->has("tokenInfo") && Response->has("userInfo")) {
 | 
			
		||||
					SecurityObjects::UserInfoAndPolicy	P;
 | 
			
		||||
					P.from_json(Response);
 | 
			
		||||
					UserCache_[SessionToken] = P;
 | 
			
		||||
					UInfo = P;
 | 
			
		||||
				}
 | 
			
		||||
				return true;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
		}
 | 
			
		||||
		return false;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -1,45 +0,0 @@
 | 
			
		||||
//
 | 
			
		||||
// Created by stephane bourque on 2021-06-30.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#ifndef UCENTRALGW_AUTHCLIENT_H
 | 
			
		||||
#define UCENTRALGW_AUTHCLIENT_H
 | 
			
		||||
 | 
			
		||||
#include "Poco/JSON/Object.h"
 | 
			
		||||
#include "Poco/Net/HTTPServerRequest.h"
 | 
			
		||||
#include "Poco/Net/HTTPServerResponse.h"
 | 
			
		||||
#include "Poco/JWT/Signer.h"
 | 
			
		||||
#include "Poco/SHA2Engine.h"
 | 
			
		||||
#include "RESTAPI_SecurityObjects.h"
 | 
			
		||||
#include "SubSystemServer.h"
 | 
			
		||||
 | 
			
		||||
namespace OpenWifi {
 | 
			
		||||
 | 
			
		||||
class AuthClient : public SubSystemServer {
 | 
			
		||||
	  public:
 | 
			
		||||
		explicit AuthClient() noexcept:
 | 
			
		||||
			SubSystemServer("Authentication", "AUTH-CLNT", "authentication")
 | 
			
		||||
		{
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		static AuthClient *instance() {
 | 
			
		||||
			if (instance_ == nullptr) {
 | 
			
		||||
				instance_ = new AuthClient;
 | 
			
		||||
			}
 | 
			
		||||
			return instance_;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		int Start() override;
 | 
			
		||||
		void Stop() override;
 | 
			
		||||
		bool IsAuthorized(Poco::Net::HTTPServerRequest & Request, std::string &SessionToken, OpenWifi::SecurityObjects::UserInfoAndPolicy & UInfo );
 | 
			
		||||
		void RemovedCachedToken(const std::string &Token);
 | 
			
		||||
		bool IsTokenAuthorized(const std::string &Token, SecurityObjects::UserInfoAndPolicy & UInfo);
 | 
			
		||||
	  private:
 | 
			
		||||
		static AuthClient 					*instance_;
 | 
			
		||||
		OpenWifi::SecurityObjects::UserInfoCache 		UserCache_;
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	inline AuthClient * AuthClient() { return AuthClient::instance(); }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#endif // UCENTRALGW_AUTHCLIENT_H
 | 
			
		||||
@@ -11,19 +11,17 @@
 | 
			
		||||
#include "Poco/Net/OAuth20Credentials.h"
 | 
			
		||||
#include "Poco/JWT/Token.h"
 | 
			
		||||
#include "Poco/JWT/Signer.h"
 | 
			
		||||
#include "Poco/StringTokenizer.h"
 | 
			
		||||
 | 
			
		||||
#include "Daemon.h"
 | 
			
		||||
#include "RESTAPI_handler.h"
 | 
			
		||||
#include "framework/MicroService.h"
 | 
			
		||||
#include "StorageService.h"
 | 
			
		||||
#include "AuthService.h"
 | 
			
		||||
#include "Utils.h"
 | 
			
		||||
#include "KafkaManager.h"
 | 
			
		||||
#include "Kafka_topics.h"
 | 
			
		||||
#include "framework/KafkaTopics.h"
 | 
			
		||||
 | 
			
		||||
#include "SMTPMailerService.h"
 | 
			
		||||
#include "MFAServer.h"
 | 
			
		||||
 | 
			
		||||
namespace OpenWifi {
 | 
			
		||||
    class AuthService *AuthService::instance_ = nullptr;
 | 
			
		||||
 | 
			
		||||
    AuthService::ACCESS_TYPE AuthService::IntToAccessType(int C) {
 | 
			
		||||
		switch (C) {
 | 
			
		||||
@@ -35,6 +33,7 @@ namespace OpenWifi {
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
	int AuthService::AccessTypeToInt(ACCESS_TYPE T) {
 | 
			
		||||
		switch (T) {
 | 
			
		||||
		case USERNAME: return 1;
 | 
			
		||||
@@ -44,102 +43,254 @@ namespace OpenWifi {
 | 
			
		||||
		return 1;	// some compilers complain...
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
    static const std::string DefaultPassword_8_u_l_n_1{"^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[\\{\\}\\(\\)~_\\+\\|\\\\\\[\\]\\;\\:\\<\\>\\.\\,\\/\\?\\\"\\'\\`\\=#?!@$%^&*-]).{8,}$"};
 | 
			
		||||
 | 
			
		||||
    int AuthService::Start() {
 | 
			
		||||
		Signer_.setRSAKey(Daemon()->Key());
 | 
			
		||||
		Signer_.addAllAlgorithms();
 | 
			
		||||
		Logger_.notice("Starting...");
 | 
			
		||||
        Secure_ = Daemon()->ConfigGetBool("authentication.enabled",true);
 | 
			
		||||
        DefaultPassword_ = Daemon()->ConfigGetString("authentication.default.password","");
 | 
			
		||||
        DefaultUserName_ = Daemon()->ConfigGetString("authentication.default.username","");
 | 
			
		||||
        Mechanism_ = Daemon()->ConfigGetString("authentication.service.type","internal");
 | 
			
		||||
        PasswordValidation_ = PasswordValidationStr_ = Daemon()->ConfigGetString("authentication.validation.expression","^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[#?!@$%^&*-]).{8,}$");
 | 
			
		||||
        TokenAging_ = (uint64_t) Daemon()->ConfigGetInt("authentication.token.ageing", 30 * 24 * 60 * 60);
 | 
			
		||||
        HowManyOldPassword_ = Daemon()->ConfigGetInt("authentication.oldpasswords", 5);
 | 
			
		||||
		Logger().notice("Starting...");
 | 
			
		||||
        TokenAging_ = (uint64_t) MicroService::instance().ConfigGetInt("authentication.token.ageing", 30 * 24 * 60 * 60);
 | 
			
		||||
        RefreshTokenLifeSpan_ = (uint64_t) MicroService::instance().ConfigGetInt("authentication.refresh_token.lifespan", 90 * 24 * 60 * 600);
 | 
			
		||||
        HowManyOldPassword_ = MicroService::instance().ConfigGetInt("authentication.oldpasswords", 5);
 | 
			
		||||
 | 
			
		||||
        AccessPolicy_ = MicroService::instance().ConfigPath("openwifi.document.policy.access", "/wwwassets/access_policy.html");
 | 
			
		||||
        PasswordPolicy_ = MicroService::instance().ConfigPath("openwifi.document.policy.password", "/wwwassets/password_policy.html");
 | 
			
		||||
        PasswordValidation_ = PasswordValidationStr_ = MicroService::instance().ConfigGetString("authentication.validation.expression",DefaultPassword_8_u_l_n_1);
 | 
			
		||||
 | 
			
		||||
        SubPasswordValidation_ = SubPasswordValidationStr_ = MicroService::instance().ConfigGetString("subscriber.validation.expression",DefaultPassword_8_u_l_n_1);
 | 
			
		||||
        SubAccessPolicy_ = MicroService::instance().ConfigPath("subscriber.policy.access", "/wwwassets/access_policy.html");
 | 
			
		||||
        SubPasswordPolicy_ = MicroService::instance().ConfigPath("subscriber.policy.password", "/wwwassets/password_policy.html");
 | 
			
		||||
 | 
			
		||||
        return 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void AuthService::Stop() {
 | 
			
		||||
		Logger_.notice("Stopping...");
 | 
			
		||||
		Logger().notice("Stopping...");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
	bool AuthService::IsAuthorized(Poco::Net::HTTPServerRequest & Request, std::string & SessionToken, SecurityObjects::UserInfoAndPolicy & UInfo )
 | 
			
		||||
    bool AuthService::RefreshUserToken(Poco::Net::HTTPServerRequest & Request, const std::string & RefreshToken, SecurityObjects::UserInfoAndPolicy & UI) {
 | 
			
		||||
        try {
 | 
			
		||||
            std::string CallToken;
 | 
			
		||||
            Poco::Net::OAuth20Credentials Auth(Request);
 | 
			
		||||
            if (Auth.getScheme() == "Bearer") {
 | 
			
		||||
                CallToken = Auth.getBearerToken();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (CallToken.empty()) {
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            uint64_t                    RevocationDate=0;
 | 
			
		||||
            std::string                 UserId;
 | 
			
		||||
            if(StorageService()->UserTokenDB().GetToken(CallToken, UI.webtoken, UserId, RevocationDate) && UI.webtoken.refresh_token_==RefreshToken) {
 | 
			
		||||
                auto now = OpenWifi::Now();
 | 
			
		||||
 | 
			
		||||
                //  Create a new token
 | 
			
		||||
                auto NewToken = GenerateTokenHMAC( UI.webtoken.access_token_, CUSTOM);
 | 
			
		||||
                auto NewRefreshToken = RefreshToken;
 | 
			
		||||
                if(now - UI.webtoken.lastRefresh_ < RefreshTokenLifeSpan_) {
 | 
			
		||||
                    NewRefreshToken = GenerateTokenHMAC( UI.webtoken.refresh_token_, CUSTOM);
 | 
			
		||||
                    UI.webtoken.lastRefresh_ = now;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                StorageService()->UserTokenDB().RefreshToken(CallToken, NewToken, NewRefreshToken, UI.webtoken.lastRefresh_ );
 | 
			
		||||
                UI.webtoken.access_token_ = NewToken;
 | 
			
		||||
                UI.webtoken.refresh_token_ = NewRefreshToken;
 | 
			
		||||
                return true;
 | 
			
		||||
            }
 | 
			
		||||
            return false;
 | 
			
		||||
 | 
			
		||||
        } catch (...) {
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    bool AuthService::RefreshSubToken(Poco::Net::HTTPServerRequest & Request, const std::string & RefreshToken, SecurityObjects::UserInfoAndPolicy & UI) {
 | 
			
		||||
        try {
 | 
			
		||||
            std::string CallToken;
 | 
			
		||||
            Poco::Net::OAuth20Credentials Auth(Request);
 | 
			
		||||
            if (Auth.getScheme() == "Bearer") {
 | 
			
		||||
                CallToken = Auth.getBearerToken();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (CallToken.empty()) {
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            uint64_t                    RevocationDate=0;
 | 
			
		||||
            std::string                 UserId;
 | 
			
		||||
            if(StorageService()->SubTokenDB().GetToken(CallToken, UI.webtoken, UserId, RevocationDate) && UI.webtoken.refresh_token_==RefreshToken) {
 | 
			
		||||
                auto now = OpenWifi::Now();
 | 
			
		||||
 | 
			
		||||
                //  Create a new token
 | 
			
		||||
                auto NewToken = GenerateTokenHMAC( UI.webtoken.access_token_, CUSTOM);
 | 
			
		||||
                auto NewRefreshToken = RefreshToken;
 | 
			
		||||
                if(now - UI.webtoken.lastRefresh_ < RefreshTokenLifeSpan_) {
 | 
			
		||||
                    NewRefreshToken = GenerateTokenHMAC( UI.webtoken.refresh_token_, CUSTOM);
 | 
			
		||||
                    UI.webtoken.lastRefresh_ = now;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                StorageService()->SubTokenDB().RefreshToken(CallToken, NewToken, NewRefreshToken, UI.webtoken.lastRefresh_ );
 | 
			
		||||
                UI.webtoken.access_token_ = NewToken;
 | 
			
		||||
                UI.webtoken.refresh_token_ = NewRefreshToken;
 | 
			
		||||
                return true;
 | 
			
		||||
            }
 | 
			
		||||
            return false;
 | 
			
		||||
 | 
			
		||||
        } catch (...) {
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    bool AuthService::IsAuthorized(Poco::Net::HTTPServerRequest & Request, std::string & SessionToken, SecurityObjects::UserInfoAndPolicy & UInfo, bool & Expired )
 | 
			
		||||
    {
 | 
			
		||||
        if(!Secure_)
 | 
			
		||||
            return true;
 | 
			
		||||
 | 
			
		||||
        std::lock_guard		Guard(Mutex_);
 | 
			
		||||
 | 
			
		||||
		std::string CallToken;
 | 
			
		||||
 | 
			
		||||
        std::lock_guard	Guard(Mutex_);
 | 
			
		||||
        Expired = false;
 | 
			
		||||
		try {
 | 
			
		||||
			Poco::Net::OAuth20Credentials Auth(Request);
 | 
			
		||||
 | 
			
		||||
			if (Auth.getScheme() == "Bearer") {
 | 
			
		||||
				CallToken = Auth.getBearerToken();
 | 
			
		||||
			}
 | 
			
		||||
		} catch(const Poco::Exception &E) {
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if(!CallToken.empty()) {
 | 
			
		||||
		    if(Storage()->IsTokenRevoked(CallToken))
 | 
			
		||||
		        return false;
 | 
			
		||||
		    auto Client = UserCache_.find(CallToken);
 | 
			
		||||
		    if( Client == UserCache_.end() )
 | 
			
		||||
		        return ValidateToken(CallToken, CallToken, UInfo);
 | 
			
		||||
 | 
			
		||||
		    if((Client->second.webtoken.created_ + Client->second.webtoken.expires_in_) > time(nullptr)) {
 | 
			
		||||
		        SessionToken = CallToken;
 | 
			
		||||
		        UInfo = Client->second ;
 | 
			
		||||
		        return true;
 | 
			
		||||
		    std::string CallToken;
 | 
			
		||||
		    Poco::Net::OAuth20Credentials Auth(Request);
 | 
			
		||||
		    if (Auth.getScheme() == "Bearer") {
 | 
			
		||||
		        CallToken = Auth.getBearerToken();
 | 
			
		||||
		    }
 | 
			
		||||
		    UserCache_.erase(CallToken);
 | 
			
		||||
		    Storage()->RevokeToken(CallToken);
 | 
			
		||||
		    return false;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
            if(CallToken.empty()) {
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            SecurityObjects::WebToken   WT;
 | 
			
		||||
            uint64_t                    RevocationDate=0;
 | 
			
		||||
            std::string                 UserId;
 | 
			
		||||
            if(StorageService()->UserTokenDB().GetToken(CallToken, WT, UserId, RevocationDate)) {
 | 
			
		||||
                if(RevocationDate!=0)
 | 
			
		||||
                    return false;
 | 
			
		||||
                auto now=OpenWifi::Now();
 | 
			
		||||
                Expired = (WT.created_ + WT.expires_in_) < now;
 | 
			
		||||
                if(StorageService()->UserDB().GetUserById(UserId,UInfo.userinfo)) {
 | 
			
		||||
                    UInfo.webtoken = WT;
 | 
			
		||||
                    SessionToken = CallToken;
 | 
			
		||||
                    return true;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            return false;
 | 
			
		||||
		} catch(const Poco::Exception &E) {
 | 
			
		||||
		    Logger().log(E);
 | 
			
		||||
		}
 | 
			
		||||
		return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    bool AuthService::DeleteUserFromCache(const std::string &UserName) {
 | 
			
		||||
        std::lock_guard		Guard(Mutex_);
 | 
			
		||||
 | 
			
		||||
        for(auto i=UserCache_.begin();i!=UserCache_.end();) {
 | 
			
		||||
            if (i->second.userinfo.email==UserName) {
 | 
			
		||||
                Logout(i->first);
 | 
			
		||||
                i = UserCache_.erase(i);
 | 
			
		||||
            } else {
 | 
			
		||||
                ++i;
 | 
			
		||||
    bool AuthService::IsSubAuthorized(Poco::Net::HTTPServerRequest & Request, std::string & SessionToken, SecurityObjects::UserInfoAndPolicy & UInfo, bool & Expired )
 | 
			
		||||
    {
 | 
			
		||||
        std::lock_guard	Guard(Mutex_);
 | 
			
		||||
        Expired = false;
 | 
			
		||||
        try {
 | 
			
		||||
            std::string CallToken;
 | 
			
		||||
            Poco::Net::OAuth20Credentials Auth(Request);
 | 
			
		||||
            if (Auth.getScheme() == "Bearer") {
 | 
			
		||||
                CallToken = Auth.getBearerToken();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if(CallToken.empty()) {
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            SecurityObjects::WebToken   WT;
 | 
			
		||||
            uint64_t                    RevocationDate=0;
 | 
			
		||||
            std::string                 UserId;
 | 
			
		||||
            if(StorageService()->SubTokenDB().GetToken(CallToken, WT, UserId, RevocationDate)) {
 | 
			
		||||
                if(RevocationDate!=0)
 | 
			
		||||
                    return false;
 | 
			
		||||
                auto now=OpenWifi::Now();
 | 
			
		||||
                Expired = (WT.created_ + WT.expires_in_) < now;
 | 
			
		||||
                if(StorageService()->SubDB().GetUserById(UserId,UInfo.userinfo)) {
 | 
			
		||||
                    UInfo.webtoken = WT;
 | 
			
		||||
                    SessionToken = CallToken;
 | 
			
		||||
                    return true;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            return false;
 | 
			
		||||
        } catch(const Poco::Exception &E) {
 | 
			
		||||
            Logger().log(E);
 | 
			
		||||
        }
 | 
			
		||||
        return true;
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void AuthService::RevokeToken(std::string & Token) {
 | 
			
		||||
        StorageService()->UserTokenDB().RevokeToken(Token);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void AuthService::RevokeSubToken(std::string & Token) {
 | 
			
		||||
        StorageService()->SubTokenDB().RevokeToken(Token);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    bool AuthService::DeleteUserFromCache(const std::string &Id) {
 | 
			
		||||
        return StorageService()->UserTokenDB().DeleteRecordsFromCache("userName",Id);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    bool AuthService::DeleteSubUserFromCache(const std::string &Id) {
 | 
			
		||||
        return StorageService()->SubTokenDB().DeleteRecordsFromCache("userName",Id);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    bool AuthService::RequiresMFA(const SecurityObjects::UserInfoAndPolicy &UInfo) {
 | 
			
		||||
        return (UInfo.userinfo.userTypeProprietaryInfo.mfa.enabled && MFAServer::MethodEnabled(UInfo.userinfo.userTypeProprietaryInfo.mfa.method));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    bool AuthService::ValidatePassword(const std::string &Password) {
 | 
			
		||||
        return std::regex_match(Password, PasswordValidation_);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void AuthService::Logout(const std::string &token) {
 | 
			
		||||
		std::lock_guard		Guard(Mutex_);
 | 
			
		||||
 | 
			
		||||
		UserCache_.erase(token);
 | 
			
		||||
    bool AuthService::ValidateSubPassword(const std::string &Password) {
 | 
			
		||||
        return std::regex_match(Password, SubPasswordValidation_);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void AuthService::RemoveTokenSystemWide(const std::string &token) {
 | 
			
		||||
        try {
 | 
			
		||||
            Poco::JSON::Object Obj;
 | 
			
		||||
            Obj.set("event", "remove-token");
 | 
			
		||||
            Obj.set("id", Daemon()->ID());
 | 
			
		||||
            Obj.set("token", token);
 | 
			
		||||
            std::stringstream ResultText;
 | 
			
		||||
            Poco::JSON::Stringifier::stringify(Obj, ResultText);
 | 
			
		||||
            std::string Tmp{token};
 | 
			
		||||
            Storage()->RevokeToken(Tmp);
 | 
			
		||||
            KafkaManager()->PostMessage(KafkaTopics::SERVICE_EVENTS, Daemon()->PrivateEndPoint(), ResultText.str(),
 | 
			
		||||
                                        false);
 | 
			
		||||
            if(KafkaManager()->Enabled()) {
 | 
			
		||||
                Poco::JSON::Object Obj;
 | 
			
		||||
                Obj.set("event", "remove-token");
 | 
			
		||||
                Obj.set("id", MicroService::instance().ID());
 | 
			
		||||
                Obj.set("token", token);
 | 
			
		||||
                std::stringstream ResultText;
 | 
			
		||||
                Poco::JSON::Stringifier::stringify(Obj, ResultText);
 | 
			
		||||
                KafkaManager()->PostMessage(KafkaTopics::SERVICE_EVENTS, MicroService::instance().PrivateEndPoint(),
 | 
			
		||||
                                            ResultText.str(),
 | 
			
		||||
                                            false);
 | 
			
		||||
            }
 | 
			
		||||
        } catch (const Poco::Exception &E) {
 | 
			
		||||
            Logger_.log(E);
 | 
			
		||||
            Logger().log(E);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    std::string AuthService::GenerateToken(const std::string & Identity, ACCESS_TYPE Type) {
 | 
			
		||||
    void AuthService::Logout(const std::string &Token,[[maybe_unused]]  bool EraseFromCache) {
 | 
			
		||||
		std::lock_guard		Guard(Mutex_);
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            auto tToken{Token};
 | 
			
		||||
            StorageService()->UserTokenDB().DeleteRecord("token",tToken);
 | 
			
		||||
            StorageService()->LoginDB().AddLogout(Token);
 | 
			
		||||
        } catch (const Poco::Exception &E) {
 | 
			
		||||
            Logger().log(E);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void AuthService::SubLogout(const std::string &Token, [[maybe_unused]] bool EraseFromCache) {
 | 
			
		||||
        std::lock_guard		Guard(Mutex_);
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            auto tToken{Token};
 | 
			
		||||
            StorageService()->SubTokenDB().DeleteRecord("token",tToken);
 | 
			
		||||
            StorageService()->SubLoginDB().AddLogout(Token);
 | 
			
		||||
        } catch (const Poco::Exception &E) {
 | 
			
		||||
            Logger().log(E);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    [[nodiscard]] std::string AuthService::GenerateTokenHMAC(const std::string & UserName, [[maybe_unused]] ACCESS_TYPE Type) {
 | 
			
		||||
        std::string Identity(UserName + ":" + fmt::format("{}",OpenWifi::Now()) + ":" + std::to_string(rand()));
 | 
			
		||||
        HMAC_.update(Identity);
 | 
			
		||||
        return Poco::DigestEngine::digestToHex(HMAC_.digest());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    std::string AuthService::GenerateTokenJWT(const std::string & Identity, ACCESS_TYPE Type) {
 | 
			
		||||
        std::lock_guard		Guard(Mutex_);
 | 
			
		||||
 | 
			
		||||
		Poco::JWT::Token	T;
 | 
			
		||||
@@ -154,34 +305,11 @@ namespace OpenWifi {
 | 
			
		||||
		T.payload().set("identity", Identity);
 | 
			
		||||
		T.setIssuedAt(Poco::Timestamp());
 | 
			
		||||
		T.setExpiration(Poco::Timestamp() + (long long)TokenAging_);
 | 
			
		||||
		std::string JWT = Signer_.sign(T,Poco::JWT::Signer::ALGO_RS256);
 | 
			
		||||
		std::string JWT = MicroService::instance().Sign(T,Poco::JWT::Signer::ALGO_RS256);
 | 
			
		||||
 | 
			
		||||
		return JWT;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
	bool AuthService::ValidateToken(const std::string & Token, std::string & SessionToken, SecurityObjects::UserInfoAndPolicy & UInfo  ) {
 | 
			
		||||
        std::lock_guard		Guard(Mutex_);
 | 
			
		||||
		Poco::JWT::Token	DecryptedToken;
 | 
			
		||||
 | 
			
		||||
		try {
 | 
			
		||||
            auto E = UserCache_.find(SessionToken);
 | 
			
		||||
            if(E == UserCache_.end()) {
 | 
			
		||||
                if(Storage()->GetToken(SessionToken,UInfo)) {
 | 
			
		||||
                    if(Storage()->GetUserById(UInfo.userinfo.email,UInfo.userinfo)) {
 | 
			
		||||
                        UserCache_[UInfo.webtoken.access_token_] = UInfo;
 | 
			
		||||
                        return true;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            } else {
 | 
			
		||||
                UInfo = E->second;
 | 
			
		||||
                return true;
 | 
			
		||||
            }
 | 
			
		||||
		} catch (const Poco::Exception &E ) {
 | 
			
		||||
			Logger_.log(E);
 | 
			
		||||
		}
 | 
			
		||||
		return false;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
    void AuthService::CreateToken(const std::string & UserName, SecurityObjects::UserInfoAndPolicy &UInfo)
 | 
			
		||||
    {
 | 
			
		||||
        std::lock_guard		Guard(Mutex_);
 | 
			
		||||
@@ -192,50 +320,170 @@ namespace OpenWifi {
 | 
			
		||||
        UInfo.webtoken.expires_in_ = TokenAging_ ;
 | 
			
		||||
        UInfo.webtoken.idle_timeout_ = 5 * 60;
 | 
			
		||||
        UInfo.webtoken.token_type_ = "Bearer";
 | 
			
		||||
        UInfo.webtoken.access_token_ = GenerateToken(UInfo.userinfo.Id,USERNAME);
 | 
			
		||||
        UInfo.webtoken.id_token_ = GenerateToken(UInfo.userinfo.Id,USERNAME);
 | 
			
		||||
        UInfo.webtoken.refresh_token_ = GenerateToken(UInfo.userinfo.Id,CUSTOM);
 | 
			
		||||
        UInfo.webtoken.access_token_ = GenerateTokenHMAC(UInfo.userinfo.id,USERNAME);
 | 
			
		||||
        UInfo.webtoken.id_token_ = GenerateTokenHMAC(UInfo.userinfo.id,USERNAME);
 | 
			
		||||
        UInfo.webtoken.refresh_token_ = GenerateTokenHMAC(UInfo.userinfo.id,CUSTOM);
 | 
			
		||||
        UInfo.webtoken.created_ = time(nullptr);
 | 
			
		||||
        UInfo.webtoken.username_ = UserName;
 | 
			
		||||
        UInfo.webtoken.errorCode = 0;
 | 
			
		||||
        UInfo.webtoken.userMustChangePassword = false;
 | 
			
		||||
        UserCache_[UInfo.webtoken.access_token_] = UInfo;
 | 
			
		||||
        Storage()->SetLastLogin(UInfo.userinfo.Id);
 | 
			
		||||
        Storage()->AddToken(UInfo.webtoken.username_, UInfo.webtoken.access_token_,
 | 
			
		||||
        StorageService()->UserDB().SetLastLogin(UInfo.userinfo.id);
 | 
			
		||||
        StorageService()->UserTokenDB().AddToken(UInfo.userinfo.id, UInfo.webtoken.access_token_,
 | 
			
		||||
                            UInfo.webtoken.refresh_token_, UInfo.webtoken.token_type_,
 | 
			
		||||
                                UInfo.webtoken.expires_in_, UInfo.webtoken.idle_timeout_);
 | 
			
		||||
        StorageService()->LoginDB().AddLogin(UInfo.userinfo.id, UInfo.userinfo.email,UInfo.webtoken.access_token_ );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void AuthService::CreateSubToken(const std::string & UserName, SecurityObjects::UserInfoAndPolicy &UInfo)
 | 
			
		||||
    {
 | 
			
		||||
        std::lock_guard		Guard(Mutex_);
 | 
			
		||||
 | 
			
		||||
        SecurityObjects::AclTemplate	ACL;
 | 
			
		||||
        ACL.PortalLogin_ = ACL.Read_ = ACL.ReadWrite_ = ACL.ReadWriteCreate_ = ACL.Delete_ = true;
 | 
			
		||||
        UInfo.webtoken.acl_template_ = ACL;
 | 
			
		||||
        UInfo.webtoken.expires_in_ = TokenAging_ ;
 | 
			
		||||
        UInfo.webtoken.idle_timeout_ = 5 * 60;
 | 
			
		||||
        UInfo.webtoken.token_type_ = "Bearer";
 | 
			
		||||
        UInfo.webtoken.access_token_ = GenerateTokenHMAC(UInfo.userinfo.id,USERNAME);
 | 
			
		||||
        UInfo.webtoken.id_token_ = GenerateTokenHMAC(UInfo.userinfo.id,USERNAME);
 | 
			
		||||
        UInfo.webtoken.refresh_token_ = GenerateTokenHMAC(UInfo.userinfo.id,CUSTOM);
 | 
			
		||||
        UInfo.webtoken.created_ = time(nullptr);
 | 
			
		||||
        UInfo.webtoken.username_ = UserName;
 | 
			
		||||
        UInfo.webtoken.errorCode = 0;
 | 
			
		||||
        UInfo.webtoken.userMustChangePassword = false;
 | 
			
		||||
        StorageService()->SubDB().SetLastLogin(UInfo.userinfo.id);
 | 
			
		||||
        StorageService()->SubTokenDB().AddToken(UInfo.userinfo.id, UInfo.webtoken.access_token_,
 | 
			
		||||
                                   UInfo.webtoken.refresh_token_, UInfo.webtoken.token_type_,
 | 
			
		||||
                                   UInfo.webtoken.expires_in_, UInfo.webtoken.idle_timeout_);
 | 
			
		||||
        StorageService()->SubLoginDB().AddLogin(UInfo.userinfo.id, UInfo.userinfo.email,UInfo.webtoken.access_token_ );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    bool AuthService::SetPassword(const std::string &NewPassword, SecurityObjects::UserInfo & UInfo) {
 | 
			
		||||
        auto NewPasswordHash = ComputePasswordHash(UInfo.email, NewPassword);
 | 
			
		||||
        for (auto const &i:UInfo.lastPasswords) {
 | 
			
		||||
            if (i == NewPasswordHash) {
 | 
			
		||||
                return false;
 | 
			
		||||
        std::lock_guard     G(Mutex_);
 | 
			
		||||
 | 
			
		||||
        Poco::toLowerInPlace(UInfo.email);
 | 
			
		||||
        for (const auto &i:UInfo.lastPasswords) {
 | 
			
		||||
            auto Tokens = Poco::StringTokenizer(i,"|");
 | 
			
		||||
            if(Tokens.count()==2) {
 | 
			
		||||
                const auto & Salt = Tokens[0];
 | 
			
		||||
                for(const auto &j:UInfo.lastPasswords) {
 | 
			
		||||
                    auto OldTokens = Poco::StringTokenizer(j,"|");
 | 
			
		||||
                    if(OldTokens.count()==2) {
 | 
			
		||||
                        SHA2_.update(Salt+NewPassword+UInfo.email);
 | 
			
		||||
                        if(OldTokens[1]==Utils::ToHex(SHA2_.digest()))
 | 
			
		||||
                            return false;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            } else {
 | 
			
		||||
                SHA2_.update(NewPassword+UInfo.email);
 | 
			
		||||
                if(Tokens[0]==Utils::ToHex(SHA2_.digest()))
 | 
			
		||||
                    return false;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if(UInfo.lastPasswords.size()==HowManyOldPassword_) {
 | 
			
		||||
            UInfo.lastPasswords.erase(UInfo.lastPasswords.begin());
 | 
			
		||||
        }
 | 
			
		||||
        UInfo.lastPasswords.push_back(NewPasswordHash);
 | 
			
		||||
        UInfo.currentPassword = NewPasswordHash;
 | 
			
		||||
 | 
			
		||||
        auto NewHash = ComputeNewPasswordHash(UInfo.email,NewPassword);
 | 
			
		||||
        UInfo.lastPasswords.push_back(NewHash);
 | 
			
		||||
        UInfo.currentPassword = NewHash;
 | 
			
		||||
        UInfo.changePassword = false;
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    AuthService::AUTH_ERROR AuthService::Authorize( std::string & UserName, const std::string & Password, const std::string & NewPassword, SecurityObjects::UserInfoAndPolicy & UInfo )
 | 
			
		||||
    bool AuthService::SetSubPassword(const std::string &NewPassword, SecurityObjects::UserInfo & UInfo) {
 | 
			
		||||
        std::lock_guard     G(Mutex_);
 | 
			
		||||
 | 
			
		||||
        Poco::toLowerInPlace(UInfo.email);
 | 
			
		||||
        for (const auto &i:UInfo.lastPasswords) {
 | 
			
		||||
            auto Tokens = Poco::StringTokenizer(i,"|");
 | 
			
		||||
            if(Tokens.count()==2) {
 | 
			
		||||
                const auto & Salt = Tokens[0];
 | 
			
		||||
                for(const auto &j:UInfo.lastPasswords) {
 | 
			
		||||
                    auto OldTokens = Poco::StringTokenizer(j,"|");
 | 
			
		||||
                    if(OldTokens.count()==2) {
 | 
			
		||||
                        SHA2_.update(Salt+NewPassword+UInfo.email);
 | 
			
		||||
                        if(OldTokens[1]==Utils::ToHex(SHA2_.digest()))
 | 
			
		||||
                            return false;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            } else {
 | 
			
		||||
                SHA2_.update(NewPassword+UInfo.email);
 | 
			
		||||
                if(Tokens[0]==Utils::ToHex(SHA2_.digest()))
 | 
			
		||||
                    return false;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if(UInfo.lastPasswords.size()==HowManyOldPassword_) {
 | 
			
		||||
            UInfo.lastPasswords.erase(UInfo.lastPasswords.begin());
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        auto NewHash = ComputeNewPasswordHash(UInfo.email,NewPassword);
 | 
			
		||||
        UInfo.lastPasswords.push_back(NewHash);
 | 
			
		||||
        UInfo.currentPassword = NewHash;
 | 
			
		||||
        UInfo.changePassword = false;
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    static std::string GetMeSomeSalt() {
 | 
			
		||||
        auto start = std::chrono::high_resolution_clock::now();
 | 
			
		||||
        return std::to_string(start.time_since_epoch().count());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    std::string AuthService::ComputeNewPasswordHash(const std::string &UserName, const std::string &Password) {
 | 
			
		||||
        std::string UName = Poco::trim(Poco::toLower(UserName));
 | 
			
		||||
        auto Salt = GetMeSomeSalt();
 | 
			
		||||
        SHA2_.update(Salt + Password + UName );
 | 
			
		||||
        return Salt + "|" + Utils::ToHex(SHA2_.digest());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    bool AuthService::ValidatePasswordHash(const std::string & UserName, const std::string & Password, const std::string &StoredPassword) {
 | 
			
		||||
        std::lock_guard G(Mutex_);
 | 
			
		||||
 | 
			
		||||
        std::string UName = Poco::trim(Poco::toLower(UserName));
 | 
			
		||||
        auto Tokens = Poco::StringTokenizer(StoredPassword,"|");
 | 
			
		||||
        if(Tokens.count()==1) {
 | 
			
		||||
            SHA2_.update(Password+UName);
 | 
			
		||||
            if(Tokens[0]==Utils::ToHex(SHA2_.digest()))
 | 
			
		||||
                return true;
 | 
			
		||||
        } else if (Tokens.count()==2) {
 | 
			
		||||
            SHA2_.update(Tokens[0]+Password+UName);
 | 
			
		||||
            if(Tokens[1]==Utils::ToHex(SHA2_.digest()))
 | 
			
		||||
                return true;
 | 
			
		||||
        }
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    bool AuthService::ValidateSubPasswordHash(const std::string & UserName, const std::string & Password, const std::string &StoredPassword) {
 | 
			
		||||
        std::lock_guard G(Mutex_);
 | 
			
		||||
 | 
			
		||||
        std::string UName = Poco::trim(Poco::toLower(UserName));
 | 
			
		||||
        auto Tokens = Poco::StringTokenizer(StoredPassword,"|");
 | 
			
		||||
        if(Tokens.count()==1) {
 | 
			
		||||
            SHA2_.update(Password+UName);
 | 
			
		||||
            if(Tokens[0]==Utils::ToHex(SHA2_.digest()))
 | 
			
		||||
                return true;
 | 
			
		||||
        } else if (Tokens.count()==2) {
 | 
			
		||||
            SHA2_.update(Tokens[0]+Password+UName);
 | 
			
		||||
            if(Tokens[1]==Utils::ToHex(SHA2_.digest()))
 | 
			
		||||
                return true;
 | 
			
		||||
        }
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    UNAUTHORIZED_REASON AuthService::Authorize( std::string & UserName, const std::string & Password, const std::string & NewPassword, SecurityObjects::UserInfoAndPolicy & UInfo , [[maybe_unused]] bool & Expired )
 | 
			
		||||
    {
 | 
			
		||||
        std::lock_guard		Guard(Mutex_);
 | 
			
		||||
        SecurityObjects::AclTemplate	ACL;
 | 
			
		||||
 | 
			
		||||
        Poco::toLowerInPlace(UserName);
 | 
			
		||||
        auto PasswordHash = ComputePasswordHash(UserName, Password);
 | 
			
		||||
 | 
			
		||||
        if(Storage()->GetUserByEmail(UserName,UInfo.userinfo)) {
 | 
			
		||||
        if(StorageService()->UserDB().GetUserByEmail(UserName,UInfo.userinfo)) {
 | 
			
		||||
            if(UInfo.userinfo.waitingForEmailCheck) {
 | 
			
		||||
                return USERNAME_PENDING_VERIFICATION;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if(PasswordHash != UInfo.userinfo.currentPassword) {
 | 
			
		||||
            if(!ValidatePasswordHash(UserName,Password,UInfo.userinfo.currentPassword)) {
 | 
			
		||||
                return INVALID_CREDENTIALS;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
@@ -253,62 +501,91 @@ namespace OpenWifi {
 | 
			
		||||
                    UInfo.webtoken.errorCode = 1;
 | 
			
		||||
                    return PASSWORD_ALREADY_USED;
 | 
			
		||||
                }
 | 
			
		||||
                UInfo.userinfo.lastPasswordChange = std::time(nullptr);
 | 
			
		||||
                UInfo.userinfo.lastPasswordChange = OpenWifi::Now();
 | 
			
		||||
                UInfo.userinfo.changePassword = false;
 | 
			
		||||
                Storage()->UpdateUserInfo(AUTHENTICATION_SYSTEM, UInfo.userinfo.Id,UInfo.userinfo);
 | 
			
		||||
                UInfo.userinfo.modified = OpenWifi::Now();
 | 
			
		||||
                StorageService()->UserDB().UpdateUserInfo(AUTHENTICATION_SYSTEM, UInfo.userinfo.id,UInfo.userinfo);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            //  so we have a good password, password up date has taken place if need be, now generate the token.
 | 
			
		||||
            UInfo.userinfo.lastLogin=std::time(nullptr);
 | 
			
		||||
            Storage()->SetLastLogin(UInfo.userinfo.Id);
 | 
			
		||||
            UInfo.userinfo.lastLogin=OpenWifi::Now();
 | 
			
		||||
            StorageService()->UserDB().SetLastLogin(UInfo.userinfo.id);
 | 
			
		||||
            CreateToken(UserName, UInfo );
 | 
			
		||||
 | 
			
		||||
            return SUCCESS;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if(((UserName == DefaultUserName_) && (DefaultPassword_== ComputePasswordHash(UserName,Password))) || !Secure_)
 | 
			
		||||
        {
 | 
			
		||||
            ACL.PortalLogin_ = ACL.Read_ = ACL.ReadWrite_ = ACL.ReadWriteCreate_ = ACL.Delete_ = true;
 | 
			
		||||
            UInfo.webtoken.acl_template_ = ACL;
 | 
			
		||||
            UInfo.userinfo.email = DefaultUserName_;
 | 
			
		||||
            UInfo.userinfo.currentPassword = DefaultPassword_;
 | 
			
		||||
            UInfo.userinfo.name = DefaultUserName_;
 | 
			
		||||
            CreateToken(UserName, UInfo );
 | 
			
		||||
            return SUCCESS;
 | 
			
		||||
        }
 | 
			
		||||
        return INVALID_CREDENTIALS;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    std::string AuthService::ComputePasswordHash(const std::string &UserName, const std::string &Password) {
 | 
			
		||||
        std::string UName = Poco::trim(Poco::toLower(UserName));
 | 
			
		||||
        SHA2_.update(Password + UName);
 | 
			
		||||
        return Utils::ToHex(SHA2_.digest());
 | 
			
		||||
    UNAUTHORIZED_REASON AuthService::AuthorizeSub( std::string & UserName, const std::string & Password, const std::string & NewPassword, SecurityObjects::UserInfoAndPolicy & UInfo , [[maybe_unused]] bool & Expired )
 | 
			
		||||
    {
 | 
			
		||||
        std::lock_guard		Guard(Mutex_);
 | 
			
		||||
 | 
			
		||||
        Poco::toLowerInPlace(UserName);
 | 
			
		||||
 | 
			
		||||
        if(StorageService()->SubDB().GetUserByEmail(UserName,UInfo.userinfo)) {
 | 
			
		||||
            if(UInfo.userinfo.waitingForEmailCheck) {
 | 
			
		||||
                return USERNAME_PENDING_VERIFICATION;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if(!ValidateSubPasswordHash(UserName,Password,UInfo.userinfo.currentPassword)) {
 | 
			
		||||
                return INVALID_CREDENTIALS;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if(UInfo.userinfo.changePassword && NewPassword.empty()) {
 | 
			
		||||
                UInfo.webtoken.userMustChangePassword = true ;
 | 
			
		||||
                return PASSWORD_CHANGE_REQUIRED;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if(!NewPassword.empty() && !ValidateSubPassword(NewPassword)) {
 | 
			
		||||
                return PASSWORD_INVALID;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if(UInfo.userinfo.changePassword || !NewPassword.empty()) {
 | 
			
		||||
                if(!SetSubPassword(NewPassword,UInfo.userinfo)) {
 | 
			
		||||
                    UInfo.webtoken.errorCode = 1;
 | 
			
		||||
                    return PASSWORD_ALREADY_USED;
 | 
			
		||||
                }
 | 
			
		||||
                UInfo.userinfo.lastPasswordChange = OpenWifi::Now();
 | 
			
		||||
                UInfo.userinfo.changePassword = false;
 | 
			
		||||
                UInfo.userinfo.modified = OpenWifi::Now();
 | 
			
		||||
                StorageService()->SubDB().UpdateUserInfo(AUTHENTICATION_SYSTEM, UInfo.userinfo.id,UInfo.userinfo);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            //  so we have a good password, password update has taken place if need be, now generate the token.
 | 
			
		||||
            UInfo.userinfo.lastLogin=OpenWifi::Now();
 | 
			
		||||
            StorageService()->SubDB().SetLastLogin(UInfo.userinfo.id);
 | 
			
		||||
            CreateSubToken(UserName, UInfo );
 | 
			
		||||
 | 
			
		||||
            return SUCCESS;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return INVALID_CREDENTIALS;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    bool AuthService::SendEmailToUser(std::string &Email, EMAIL_REASON Reason) {
 | 
			
		||||
    bool AuthService::SendEmailToUser(const std::string &LinkId, std::string &Email, EMAIL_REASON Reason) {
 | 
			
		||||
        SecurityObjects::UserInfo   UInfo;
 | 
			
		||||
 | 
			
		||||
        if(Storage()->GetUserByEmail(Email,UInfo)) {
 | 
			
		||||
        if(StorageService()->UserDB().GetUserByEmail(Email,UInfo)) {
 | 
			
		||||
            switch (Reason) {
 | 
			
		||||
 | 
			
		||||
                case FORGOT_PASSWORD: {
 | 
			
		||||
                        MessageAttributes Attrs;
 | 
			
		||||
 | 
			
		||||
                        Attrs[RECIPIENT_EMAIL] = UInfo.email;
 | 
			
		||||
                        Attrs[LOGO] = "logo.jpg";
 | 
			
		||||
                        Attrs[LOGO] = GetLogoAssetURI();
 | 
			
		||||
                        Attrs[SUBJECT] = "Password reset link";
 | 
			
		||||
                        Attrs[ACTION_LINK] =
 | 
			
		||||
                                Daemon()->GetPublicAPIEndPoint() + "/actionLink?action=password_reset&id=" + UInfo.Id ;
 | 
			
		||||
                        Attrs[ACTION_LINK] = MicroService::instance().GetPublicAPIEndPoint() + "/actionLink?action=password_reset&id=" + LinkId ;
 | 
			
		||||
                        SMTPMailerService()->SendMessage(UInfo.email, "password_reset.txt", Attrs);
 | 
			
		||||
                    }
 | 
			
		||||
                    break;
 | 
			
		||||
 | 
			
		||||
                case EMAIL_VERIFICATION: {
 | 
			
		||||
                        MessageAttributes Attrs;
 | 
			
		||||
 | 
			
		||||
                        Attrs[RECIPIENT_EMAIL] = UInfo.email;
 | 
			
		||||
                        Attrs[LOGO] = "logo.jpg";
 | 
			
		||||
                        Attrs[SUBJECT] = "EMail Address Verification";
 | 
			
		||||
                        Attrs[ACTION_LINK] =
 | 
			
		||||
                                Daemon()->GetPublicAPIEndPoint() + "/actionLink?action=email_verification&id=" + UInfo.Id ;
 | 
			
		||||
                        Attrs[LOGO] = GetLogoAssetURI();
 | 
			
		||||
                        Attrs[SUBJECT] = "e-mail Address Verification";
 | 
			
		||||
                        Attrs[ACTION_LINK] = MicroService::instance().GetPublicAPIEndPoint() + "/actionLink?action=email_verification&id=" + LinkId ;
 | 
			
		||||
                        SMTPMailerService()->SendMessage(UInfo.email, "email_verification.txt", Attrs);
 | 
			
		||||
                        UInfo.waitingForEmailCheck = true;
 | 
			
		||||
                    }
 | 
			
		||||
@@ -317,33 +594,126 @@ namespace OpenWifi {
 | 
			
		||||
                default:
 | 
			
		||||
                    break;
 | 
			
		||||
            }
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    bool AuthService::SendEmailToSubUser(const std::string &LinkId, std::string &Email, EMAIL_REASON Reason) {
 | 
			
		||||
        SecurityObjects::UserInfo   UInfo;
 | 
			
		||||
 | 
			
		||||
        if(StorageService()->SubDB().GetUserByEmail(Email,UInfo)) {
 | 
			
		||||
            switch (Reason) {
 | 
			
		||||
 | 
			
		||||
                case FORGOT_PASSWORD: {
 | 
			
		||||
                    MessageAttributes Attrs;
 | 
			
		||||
                    Attrs[RECIPIENT_EMAIL] = UInfo.email;
 | 
			
		||||
                    Attrs[LOGO] = GetLogoAssetURI();
 | 
			
		||||
                    Attrs[SUBJECT] = "Password reset link";
 | 
			
		||||
                    Attrs[ACTION_LINK] = MicroService::instance().GetPublicAPIEndPoint() + "/actionLink?action=password_reset&id=" + LinkId ;
 | 
			
		||||
                    SMTPMailerService()->SendMessage(UInfo.email, "password_reset.txt", Attrs);
 | 
			
		||||
                }
 | 
			
		||||
                break;
 | 
			
		||||
 | 
			
		||||
                case EMAIL_VERIFICATION: {
 | 
			
		||||
                    MessageAttributes Attrs;
 | 
			
		||||
                    Attrs[RECIPIENT_EMAIL] = UInfo.email;
 | 
			
		||||
                    Attrs[LOGO] = GetLogoAssetURI();
 | 
			
		||||
                    Attrs[SUBJECT] = "e-mail Address Verification";
 | 
			
		||||
                    Attrs[ACTION_LINK] = MicroService::instance().GetPublicAPIEndPoint() + "/actionLink?action=email_verification&id=" + LinkId ;
 | 
			
		||||
                    SMTPMailerService()->SendMessage(UInfo.email, "email_verification.txt", Attrs);
 | 
			
		||||
                    UInfo.waitingForEmailCheck = true;
 | 
			
		||||
                }
 | 
			
		||||
                break;
 | 
			
		||||
 | 
			
		||||
                case SIGNUP_VERIFICATION: {
 | 
			
		||||
                    MessageAttributes Attrs;
 | 
			
		||||
                    Attrs[RECIPIENT_EMAIL] = UInfo.email;
 | 
			
		||||
                    Attrs[LOGO] = GetLogoAssetURI();
 | 
			
		||||
                    Attrs[SUBJECT] = "Signup e-mail Address Verification";
 | 
			
		||||
                    Attrs[ACTION_LINK] = MicroService::instance().GetPublicAPIEndPoint() + "/actionLink?action=signup_verification&id=" + LinkId ;
 | 
			
		||||
                    SMTPMailerService()->SendMessage(UInfo.email, "signup_verification.txt", Attrs);
 | 
			
		||||
                    UInfo.waitingForEmailCheck = true;
 | 
			
		||||
                }
 | 
			
		||||
                break;
 | 
			
		||||
 | 
			
		||||
                default:
 | 
			
		||||
                    break;
 | 
			
		||||
            }
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    bool AuthService::VerifyEmail(SecurityObjects::UserInfo &UInfo) {
 | 
			
		||||
        MessageAttributes Attrs;
 | 
			
		||||
        SecurityObjects::ActionLink A;
 | 
			
		||||
 | 
			
		||||
        Attrs[RECIPIENT_EMAIL] = UInfo.email;
 | 
			
		||||
        Attrs[LOGO] = "logo.jpg";
 | 
			
		||||
        Attrs[SUBJECT] = "EMail Address Verification";
 | 
			
		||||
        Attrs[ACTION_LINK] =
 | 
			
		||||
                Daemon()->GetPublicAPIEndPoint() + "/actionLink?action=email_verification&id=" + UInfo.Id ;
 | 
			
		||||
        SMTPMailerService()->SendMessage(UInfo.email, "email_verification.txt", Attrs);
 | 
			
		||||
        A.action = OpenWifi::SecurityObjects::LinkActions::VERIFY_EMAIL;
 | 
			
		||||
        A.userId = UInfo.id;
 | 
			
		||||
        A.id = MicroService::CreateUUID();
 | 
			
		||||
        A.created = OpenWifi::Now();
 | 
			
		||||
        A.expires = A.created + 24*60*60;
 | 
			
		||||
        A.userAction = true;
 | 
			
		||||
        StorageService()->ActionLinksDB().CreateAction(A);
 | 
			
		||||
        UInfo.waitingForEmailCheck = true;
 | 
			
		||||
        UInfo.validated = false;
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    bool AuthService::IsValidToken(const std::string &Token, SecurityObjects::WebToken &WebToken, SecurityObjects::UserInfo &UserInfo) {
 | 
			
		||||
    bool AuthService::VerifySubEmail(SecurityObjects::UserInfo &UInfo) {
 | 
			
		||||
        SecurityObjects::ActionLink A;
 | 
			
		||||
 | 
			
		||||
        A.action = OpenWifi::SecurityObjects::LinkActions::SUB_VERIFY_EMAIL;
 | 
			
		||||
        A.userId = UInfo.id;
 | 
			
		||||
        A.id = MicroService::CreateUUID();
 | 
			
		||||
        A.created = OpenWifi::Now();
 | 
			
		||||
        A.expires = A.created + 24*60*60;
 | 
			
		||||
        A.userAction = false;
 | 
			
		||||
        StorageService()->ActionLinksDB().CreateAction(A);
 | 
			
		||||
        UInfo.waitingForEmailCheck = true;
 | 
			
		||||
        UInfo.validated = false;
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    bool AuthService::IsValidToken(const std::string &Token, SecurityObjects::WebToken &WebToken, SecurityObjects::UserInfo &UserInfo, bool & Expired) {
 | 
			
		||||
 | 
			
		||||
        std::lock_guard G(Mutex_);
 | 
			
		||||
        auto It = UserCache_.find(Token);
 | 
			
		||||
        Expired = false;
 | 
			
		||||
 | 
			
		||||
        if(It==UserCache_.end())
 | 
			
		||||
        std::string TToken{Token}, UserId;
 | 
			
		||||
        SecurityObjects::WebToken   WT;
 | 
			
		||||
        uint64_t RevocationDate=0;
 | 
			
		||||
        if(StorageService()->UserTokenDB().GetToken(TToken, WT, UserId, RevocationDate)) {
 | 
			
		||||
            if(RevocationDate!=0)
 | 
			
		||||
                return false;
 | 
			
		||||
            Expired = (WT.created_ + WT.expires_in_) < OpenWifi::Now();
 | 
			
		||||
            if(StorageService()->UserDB().GetUserById(UserId,UserInfo)) {
 | 
			
		||||
                WebToken = WT;
 | 
			
		||||
                return true;
 | 
			
		||||
            }
 | 
			
		||||
            return false;
 | 
			
		||||
        WebToken = It->second.webtoken;
 | 
			
		||||
        UserInfo = It->second.userinfo;
 | 
			
		||||
        return true;
 | 
			
		||||
        }
 | 
			
		||||
        return IsValidSubToken(Token, WebToken, UserInfo, Expired);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    bool AuthService::IsValidSubToken(const std::string &Token, SecurityObjects::WebToken &WebToken, SecurityObjects::UserInfo &UserInfo, bool & Expired) {
 | 
			
		||||
        std::lock_guard G(Mutex_);
 | 
			
		||||
        Expired = false;
 | 
			
		||||
 | 
			
		||||
        std::string TToken{Token}, UserId;
 | 
			
		||||
        SecurityObjects::WebToken   WT;
 | 
			
		||||
        uint64_t RevocationDate=0;
 | 
			
		||||
        if(StorageService()->SubTokenDB().GetToken(TToken, WT, UserId, RevocationDate)) {
 | 
			
		||||
            if(RevocationDate!=0)
 | 
			
		||||
                return false;
 | 
			
		||||
            Expired = (WT.created_ + WT.expires_in_) < OpenWifi::Now();
 | 
			
		||||
            if(StorageService()->SubDB().GetUserById(UserId,UserInfo)) {
 | 
			
		||||
                WebToken = WT;
 | 
			
		||||
                return true;
 | 
			
		||||
            }
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}  // end of namespace
 | 
			
		||||
 
 | 
			
		||||
@@ -11,15 +11,17 @@
 | 
			
		||||
 | 
			
		||||
#include <regex>
 | 
			
		||||
 | 
			
		||||
#include "SubSystemServer.h"
 | 
			
		||||
 | 
			
		||||
#include "Poco/JSON/Object.h"
 | 
			
		||||
#include "Poco/Net/HTTPServerRequest.h"
 | 
			
		||||
#include "Poco/Net/HTTPServerResponse.h"
 | 
			
		||||
#include "Poco/JWT/Signer.h"
 | 
			
		||||
#include "Poco/SHA2Engine.h"
 | 
			
		||||
#include "Poco/Crypto/DigestEngine.h"
 | 
			
		||||
#include "Poco/HMACEngine.h"
 | 
			
		||||
#include "Poco/ExpireLRUCache.h"
 | 
			
		||||
 | 
			
		||||
#include "RESTAPI_SecurityObjects.h"
 | 
			
		||||
#include "framework/MicroService.h"
 | 
			
		||||
#include "RESTObjects/RESTAPI_SecurityObjects.h"
 | 
			
		||||
 | 
			
		||||
namespace OpenWifi{
 | 
			
		||||
 | 
			
		||||
@@ -34,76 +36,134 @@ namespace OpenWifi{
 | 
			
		||||
            CUSTOM
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        enum AUTH_ERROR {
 | 
			
		||||
            SUCCESS,
 | 
			
		||||
            PASSWORD_CHANGE_REQUIRED,
 | 
			
		||||
            INVALID_CREDENTIALS,
 | 
			
		||||
            PASSWORD_ALREADY_USED,
 | 
			
		||||
            USERNAME_PENDING_VERIFICATION,
 | 
			
		||||
            PASSWORD_INVALID,
 | 
			
		||||
            INTERNAL_ERROR
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        enum EMAIL_REASON {
 | 
			
		||||
            FORGOT_PASSWORD,
 | 
			
		||||
            EMAIL_VERIFICATION
 | 
			
		||||
            EMAIL_VERIFICATION,
 | 
			
		||||
            SIGNUP_VERIFICATION
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        static ACCESS_TYPE IntToAccessType(int C);
 | 
			
		||||
        static int AccessTypeToInt(ACCESS_TYPE T);
 | 
			
		||||
 | 
			
		||||
        static AuthService *instance() {
 | 
			
		||||
            if (instance_ == nullptr) {
 | 
			
		||||
                instance_ = new AuthService;
 | 
			
		||||
            }
 | 
			
		||||
        static auto instance() {
 | 
			
		||||
            static auto instance_ = new AuthService;
 | 
			
		||||
            return instance_;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        int Start() override;
 | 
			
		||||
        void Stop() override;
 | 
			
		||||
 | 
			
		||||
        [[nodiscard]] bool IsAuthorized(Poco::Net::HTTPServerRequest & Request,std::string &SessionToken, SecurityObjects::UserInfoAndPolicy & UInfo );
 | 
			
		||||
        [[nodiscard]] AUTH_ERROR Authorize( std::string & UserName, const std::string & Password, const std::string & NewPassword, SecurityObjects::UserInfoAndPolicy & UInfo );
 | 
			
		||||
        [[nodiscard]] bool IsAuthorized(Poco::Net::HTTPServerRequest & Request,std::string &SessionToken, SecurityObjects::UserInfoAndPolicy & UInfo, bool & Expired);
 | 
			
		||||
        [[nodiscard]] UNAUTHORIZED_REASON Authorize( std::string & UserName, const std::string & Password, const std::string & NewPassword, SecurityObjects::UserInfoAndPolicy & UInfo, bool & Expired );
 | 
			
		||||
        void CreateToken(const std::string & UserName, SecurityObjects::UserInfoAndPolicy &UInfo);
 | 
			
		||||
        [[nodiscard]] bool ValidateToken(const std::string & Token, std::string & SessionToken, SecurityObjects::UserInfoAndPolicy & UserInfo  );
 | 
			
		||||
        [[nodiscard]] bool SetPassword(const std::string &Password, SecurityObjects::UserInfo & UInfo);
 | 
			
		||||
        [[nodiscard]] const std:: string & PasswordValidationExpression() const { return PasswordValidationStr_;};
 | 
			
		||||
        void Logout(const std::string &token);
 | 
			
		||||
        void Logout(const std::string &token, bool EraseFromCache=true);
 | 
			
		||||
 | 
			
		||||
        [[nodiscard]] bool IsSubAuthorized(Poco::Net::HTTPServerRequest & Request,std::string &SessionToken, SecurityObjects::UserInfoAndPolicy & UInfo, bool & Expired);
 | 
			
		||||
        [[nodiscard]] UNAUTHORIZED_REASON AuthorizeSub( std::string & UserName, const std::string & Password, const std::string & NewPassword, SecurityObjects::UserInfoAndPolicy & UInfo, bool & Expired );
 | 
			
		||||
        void CreateSubToken(const std::string & UserName, SecurityObjects::UserInfoAndPolicy &UInfo);
 | 
			
		||||
        [[nodiscard]] bool SetSubPassword(const std::string &Password, SecurityObjects::UserInfo & UInfo);
 | 
			
		||||
        [[nodiscard]] const std:: string & SubPasswordValidationExpression() const { return PasswordValidationStr_;};
 | 
			
		||||
        void SubLogout(const std::string &token, bool EraseFromCache=true);
 | 
			
		||||
 | 
			
		||||
        void RemoveTokenSystemWide(const std::string &token);
 | 
			
		||||
 | 
			
		||||
        bool ValidatePassword(const std::string &pwd);
 | 
			
		||||
        bool ValidateSubPassword(const std::string &pwd);
 | 
			
		||||
 | 
			
		||||
        [[nodiscard]] bool IsValidToken(const std::string &Token, SecurityObjects::WebToken &WebToken, SecurityObjects::UserInfo &UserInfo, bool & Expired);
 | 
			
		||||
        [[nodiscard]] bool IsValidSubToken(const std::string &Token, SecurityObjects::WebToken &WebToken, SecurityObjects::UserInfo &UserInfo, bool & Expired);
 | 
			
		||||
        [[nodiscard]] std::string GenerateTokenJWT(const std::string & UserName, ACCESS_TYPE Type);
 | 
			
		||||
        [[nodiscard]] std::string GenerateTokenHMAC(const std::string & UserName, ACCESS_TYPE Type);
 | 
			
		||||
 | 
			
		||||
        [[nodiscard]] std::string ComputeNewPasswordHash(const std::string &UserName, const std::string &Password);
 | 
			
		||||
        [[nodiscard]] bool ValidatePasswordHash(const std::string & UserName, const std::string & Password, const std::string &StoredPassword);
 | 
			
		||||
        [[nodiscard]] bool ValidateSubPasswordHash(const std::string & UserName, const std::string & Password, const std::string &StoredPassword);
 | 
			
		||||
 | 
			
		||||
        [[nodiscard]] bool IsValidToken(const std::string &Token, SecurityObjects::WebToken &WebToken, SecurityObjects::UserInfo &UserInfo);
 | 
			
		||||
        [[nodiscard]] bool IsValidAPIKEY(const Poco::Net::HTTPServerRequest &Request);
 | 
			
		||||
        [[nodiscard]] std::string GenerateToken(const std::string & UserName, ACCESS_TYPE Type);
 | 
			
		||||
        [[nodiscard]] bool ValidateToken(const std::string & Token, std::string & SessionToken, SecurityObjects::WebToken & UserInfo  );
 | 
			
		||||
        [[nodiscard]] std::string ComputePasswordHash(const std::string &UserName, const std::string &Password);
 | 
			
		||||
        [[nodiscard]] bool UpdatePassword(const std::string &Admin, const std::string &UserName, const std::string & OldPassword, const std::string &NewPassword);
 | 
			
		||||
        [[nodiscard]] std::string ResetPassword(const std::string &Admin, const std::string &UserName);
 | 
			
		||||
 | 
			
		||||
        [[nodiscard]] bool UpdateSubPassword(const std::string &Admin, const std::string &UserName, const std::string & OldPassword, const std::string &NewPassword);
 | 
			
		||||
        [[nodiscard]] std::string ResetSubPassword(const std::string &Admin, const std::string &UserName);
 | 
			
		||||
 | 
			
		||||
        [[nodiscard]] static bool VerifyEmail(SecurityObjects::UserInfo &UInfo);
 | 
			
		||||
        [[nodiscard]] static bool SendEmailToUser(std::string &Email, EMAIL_REASON Reason);
 | 
			
		||||
        [[nodiscard]] bool DeleteUserFromCache(const std::string &UserName);
 | 
			
		||||
        [[nodiscard]] static bool VerifySubEmail(SecurityObjects::UserInfo &UInfo);
 | 
			
		||||
 | 
			
		||||
        [[nodiscard]] static bool SendEmailToUser(const std::string &LinkId, std::string &Email, EMAIL_REASON Reason);
 | 
			
		||||
        [[nodiscard]] static bool SendEmailToSubUser(const std::string &LinkId, std::string &Email, EMAIL_REASON Reason);
 | 
			
		||||
        [[nodiscard]] bool RequiresMFA(const SecurityObjects::UserInfoAndPolicy &UInfo);
 | 
			
		||||
 | 
			
		||||
        bool DeleteUserFromCache(const std::string &UserName);
 | 
			
		||||
        bool DeleteSubUserFromCache(const std::string &UserName);
 | 
			
		||||
        void RevokeToken(std::string & Token);
 | 
			
		||||
        void RevokeSubToken(std::string & Token);
 | 
			
		||||
 | 
			
		||||
        [[nodiscard]] static inline const std::string GetLogoAssetURI() {
 | 
			
		||||
            return MicroService::instance().PublicEndPoint() + "/wwwassets/the_logo.png";
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [[nodiscard]] static inline const std::string GetLogoAssetFileName() {
 | 
			
		||||
            return MicroService::instance().WWWAssetsDir() + "/the_logo.png";
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        inline const std::string & GetPasswordPolicy() const { return PasswordPolicy_; }
 | 
			
		||||
        inline const std::string & GetAccessPolicy() const { return AccessPolicy_; }
 | 
			
		||||
 | 
			
		||||
        inline const std::string & GetSubPasswordPolicy() const { return SubPasswordPolicy_; }
 | 
			
		||||
        inline const std::string & GetSubAccessPolicy() const { return SubAccessPolicy_; }
 | 
			
		||||
 | 
			
		||||
        bool RefreshUserToken(Poco::Net::HTTPServerRequest & Request, const std::string & RefreshToken, SecurityObjects::UserInfoAndPolicy & UI);
 | 
			
		||||
        bool RefreshSubToken(Poco::Net::HTTPServerRequest & Request, const std::string & RefreshToken, SecurityObjects::UserInfoAndPolicy & UI);
 | 
			
		||||
 | 
			
		||||
    private:
 | 
			
		||||
		static AuthService *instance_;
 | 
			
		||||
		bool    			Secure_ = false ;
 | 
			
		||||
		std::string     	DefaultUserName_;
 | 
			
		||||
		std::string			DefaultPassword_;
 | 
			
		||||
		std::string     	Mechanism_;
 | 
			
		||||
		Poco::JWT::Signer	Signer_;
 | 
			
		||||
		Poco::SHA2Engine	SHA2_;
 | 
			
		||||
		SecurityObjects::UserInfoCache UserCache_;
 | 
			
		||||
        std::string          PasswordValidationStr_;
 | 
			
		||||
		std::regex          PasswordValidation_;
 | 
			
		||||
		uint64_t            TokenAging_ = 30 * 24 * 60 * 60;
 | 
			
		||||
 | 
			
		||||
		std::string         AccessPolicy_;
 | 
			
		||||
		std::string         PasswordPolicy_;
 | 
			
		||||
		std::string         SubAccessPolicy_;
 | 
			
		||||
		std::string         SubPasswordPolicy_;
 | 
			
		||||
		std::string         PasswordValidationStr_;
 | 
			
		||||
        std::string         SubPasswordValidationStr_;
 | 
			
		||||
        std::regex          PasswordValidation_;
 | 
			
		||||
        std::regex          SubPasswordValidation_;
 | 
			
		||||
 | 
			
		||||
        uint64_t            TokenAging_ = 15 * 24 * 60 * 60;
 | 
			
		||||
        uint64_t            HowManyOldPassword_=5;
 | 
			
		||||
        uint64_t            RefreshTokenLifeSpan_ = 90 * 24 * 60 * 60 ;
 | 
			
		||||
 | 
			
		||||
        class SHA256Engine : public Poco::Crypto::DigestEngine
 | 
			
		||||
                {
 | 
			
		||||
                public:
 | 
			
		||||
                    enum
 | 
			
		||||
                    {
 | 
			
		||||
                        BLOCK_SIZE = 64,
 | 
			
		||||
                        DIGEST_SIZE = 32
 | 
			
		||||
                    };
 | 
			
		||||
 | 
			
		||||
                    SHA256Engine()
 | 
			
		||||
                    : DigestEngine("SHA256")
 | 
			
		||||
                    {
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                };
 | 
			
		||||
 | 
			
		||||
        Poco::HMACEngine<SHA256Engine> HMAC_{"tipopenwifi"};
 | 
			
		||||
 | 
			
		||||
        AuthService() noexcept:
 | 
			
		||||
            SubSystemServer("Authentication", "AUTH-SVR", "authentication")
 | 
			
		||||
        {
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    inline AuthService * AuthService() { return AuthService::instance(); }
 | 
			
		||||
    inline auto AuthService() { return AuthService::instance(); }
 | 
			
		||||
 | 
			
		||||
    [[nodiscard]] inline bool AuthServiceIsAuthorized(Poco::Net::HTTPServerRequest & Request,std::string &SessionToken, SecurityObjects::UserInfoAndPolicy & UInfo , bool & Expired, bool Sub ) {
 | 
			
		||||
        if(Sub)
 | 
			
		||||
            return AuthService()->IsSubAuthorized(Request, SessionToken, UInfo, Expired );
 | 
			
		||||
        else
 | 
			
		||||
            return AuthService()->IsAuthorized(Request, SessionToken, UInfo, Expired );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
} // end of namespace
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -10,8 +10,6 @@
 | 
			
		||||
//	Arilia Wireless Inc.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#include <cstdlib>
 | 
			
		||||
#include <boost/algorithm/string.hpp>
 | 
			
		||||
 | 
			
		||||
#include "Poco/Util/Application.h"
 | 
			
		||||
#include "Poco/Util/Option.h"
 | 
			
		||||
@@ -19,13 +17,15 @@
 | 
			
		||||
 | 
			
		||||
#include "Daemon.h"
 | 
			
		||||
 | 
			
		||||
#include "ALBHealthCheckServer.h"
 | 
			
		||||
#include "KafkaManager.h"
 | 
			
		||||
#include <aws/core/Aws.h>
 | 
			
		||||
#include <aws/s3/model/AccessControlPolicy.h>
 | 
			
		||||
 | 
			
		||||
#include "StorageService.h"
 | 
			
		||||
#include "RESTAPI_server.h"
 | 
			
		||||
#include "SMTPMailerService.h"
 | 
			
		||||
#include "RESTAPI_InternalServer.h"
 | 
			
		||||
#include "AuthService.h"
 | 
			
		||||
#include "SMSSender.h"
 | 
			
		||||
#include "ActionLinkManager.h"
 | 
			
		||||
#include "TotpCache.h"
 | 
			
		||||
 | 
			
		||||
namespace OpenWifi {
 | 
			
		||||
    class Daemon *Daemon::instance_ = nullptr;
 | 
			
		||||
@@ -37,32 +37,43 @@ namespace OpenWifi {
 | 
			
		||||
                                   vDAEMON_CONFIG_ENV_VAR,
 | 
			
		||||
                                   vDAEMON_APP_NAME,
 | 
			
		||||
                                   vDAEMON_BUS_TIMER,
 | 
			
		||||
                                   Types::SubSystemVec{
 | 
			
		||||
                                           Storage(),
 | 
			
		||||
                                           RESTAPI_Server(),
 | 
			
		||||
                                           RESTAPI_InternalServer(),
 | 
			
		||||
                                   SubSystemVec{
 | 
			
		||||
                                           StorageService(),
 | 
			
		||||
                                           SMSSender(),
 | 
			
		||||
                                           ActionLinkManager(),
 | 
			
		||||
                                           SMTPMailerService(),
 | 
			
		||||
                                           RESTAPI_RateLimiter(),
 | 
			
		||||
                                           TotpCache(),
 | 
			
		||||
                                           AuthService()
 | 
			
		||||
                                   });
 | 
			
		||||
        }
 | 
			
		||||
        return instance_;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void Daemon::initialize(Poco::Util::Application &self) {
 | 
			
		||||
        MicroService::initialize(*this);
 | 
			
		||||
    void Daemon::PostInitialization([[maybe_unused]] Poco::Util::Application &self) {
 | 
			
		||||
        AssetDir_ = MicroService::instance().ConfigPath("openwifi.restapi.wwwassets");
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int main(int argc, char **argv) {
 | 
			
		||||
    try {
 | 
			
		||||
        auto App = OpenWifi::Daemon::instance();
 | 
			
		||||
        auto ExitCode =  App->run(argc, argv);
 | 
			
		||||
        delete App;
 | 
			
		||||
        SSL_library_init();
 | 
			
		||||
        Aws::SDKOptions AwsOptions;
 | 
			
		||||
        AwsOptions.memoryManagementOptions.memoryManager = nullptr;
 | 
			
		||||
        AwsOptions.cryptoOptions.initAndCleanupOpenSSL = false;
 | 
			
		||||
        AwsOptions.httpOptions.initAndCleanupCurl = true;
 | 
			
		||||
 | 
			
		||||
        Aws::InitAPI(AwsOptions);
 | 
			
		||||
 | 
			
		||||
        int ExitCode=0;
 | 
			
		||||
        {
 | 
			
		||||
            auto App = OpenWifi::Daemon::instance();
 | 
			
		||||
            ExitCode =  App->run(argc, argv);
 | 
			
		||||
        }
 | 
			
		||||
        ShutdownAPI(AwsOptions);
 | 
			
		||||
        return ExitCode;
 | 
			
		||||
 | 
			
		||||
    } catch (Poco::Exception &exc) {
 | 
			
		||||
        std::cerr << exc.displayText() << std::endl;
 | 
			
		||||
        std::cout << exc.displayText() << std::endl;
 | 
			
		||||
        return Poco::Util::Application::EXIT_SOFTWARE;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										25
									
								
								src/Daemon.h
									
									
									
									
									
								
							
							
						
						
									
										25
									
								
								src/Daemon.h
									
									
									
									
									
								
							@@ -20,17 +20,15 @@
 | 
			
		||||
#include "Poco/Crypto/CipherFactory.h"
 | 
			
		||||
#include "Poco/Crypto/Cipher.h"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#include "OpenWifiTypes.h"
 | 
			
		||||
#include "MicroService.h"
 | 
			
		||||
#include "framework/MicroService.h"
 | 
			
		||||
 | 
			
		||||
namespace OpenWifi {
 | 
			
		||||
 | 
			
		||||
    static const char * vDAEMON_PROPERTIES_FILENAME = "owsec.properties";
 | 
			
		||||
    static const char * vDAEMON_ROOT_ENV_VAR = "OWSEC_ROOT";
 | 
			
		||||
    static const char * vDAEMON_CONFIG_ENV_VAR = "OWSEC_CONFIG";
 | 
			
		||||
    static const char * vDAEMON_APP_NAME = uSERVICE_SECURITY.c_str();
 | 
			
		||||
    static const uint64_t vDAEMON_BUS_TIMER = 5000;
 | 
			
		||||
    [[maybe_unused]] static const char * vDAEMON_PROPERTIES_FILENAME = "owsec.properties";
 | 
			
		||||
    [[maybe_unused]] static const char * vDAEMON_ROOT_ENV_VAR = "OWSEC_ROOT";
 | 
			
		||||
    [[maybe_unused]] static const char * vDAEMON_CONFIG_ENV_VAR = "OWSEC_CONFIG";
 | 
			
		||||
    [[maybe_unused]] static const char * vDAEMON_APP_NAME = uSERVICE_SECURITY.c_str();
 | 
			
		||||
    [[maybe_unused]] static const uint64_t vDAEMON_BUS_TIMER = 5000;
 | 
			
		||||
 | 
			
		||||
    class Daemon : public MicroService {
 | 
			
		||||
    public:
 | 
			
		||||
@@ -39,16 +37,21 @@ namespace OpenWifi {
 | 
			
		||||
                        const std::string & ConfigEnv,
 | 
			
		||||
                        const std::string & AppName,
 | 
			
		||||
                        uint64_t BusTimer,
 | 
			
		||||
                        const Types::SubSystemVec & SubSystems) :
 | 
			
		||||
                        const SubSystemVec & SubSystems) :
 | 
			
		||||
                MicroService( PropFile, RootEnv, ConfigEnv, AppName, BusTimer, SubSystems) {};
 | 
			
		||||
 | 
			
		||||
        void initialize(Poco::Util::Application &self) override;
 | 
			
		||||
        void PostInitialization(Poco::Util::Application &self);
 | 
			
		||||
        static Daemon *instance();
 | 
			
		||||
        inline const std::string & AssetDir() { return AssetDir_; }
 | 
			
		||||
    private:
 | 
			
		||||
        static Daemon 				*instance_;
 | 
			
		||||
        static Daemon 		*instance_;
 | 
			
		||||
        std::string         AssetDir_;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    inline Daemon * Daemon() { return Daemon::instance(); }
 | 
			
		||||
    inline void DaemonPostInitialization(Poco::Util::Application &self) {
 | 
			
		||||
        Daemon()->PostInitialization(self);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#endif //UCENTRALSEC_DAEMON_H
 | 
			
		||||
 
 | 
			
		||||
@@ -1,221 +0,0 @@
 | 
			
		||||
//
 | 
			
		||||
//	License type: BSD 3-Clause License
 | 
			
		||||
//	License copy: https://github.com/Telecominfraproject/wlan-cloud-ucentralgw/blob/master/LICENSE
 | 
			
		||||
//
 | 
			
		||||
//	Created by Stephane Bourque on 2021-03-04.
 | 
			
		||||
//	Arilia Wireless Inc.
 | 
			
		||||
//
 | 
			
		||||
#include <thread>
 | 
			
		||||
 | 
			
		||||
#include "KafkaManager.h"
 | 
			
		||||
 | 
			
		||||
#include "Daemon.h"
 | 
			
		||||
#include "Utils.h"
 | 
			
		||||
 | 
			
		||||
namespace OpenWifi {
 | 
			
		||||
 | 
			
		||||
	class KafkaManager *KafkaManager::instance_ = nullptr;
 | 
			
		||||
 | 
			
		||||
	KafkaManager::KafkaManager() noexcept:
 | 
			
		||||
		SubSystemServer("KafkaManager", "KAFKA-SVR", "openwifi.kafka")
 | 
			
		||||
	{
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	void KafkaManager::initialize(Poco::Util::Application & self) {
 | 
			
		||||
		SubSystemServer::initialize(self);
 | 
			
		||||
		KafkaEnabled_ = Daemon()->ConfigGetBool("openwifi.kafka.enable",false);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
#ifdef SMALL_BUILD
 | 
			
		||||
 | 
			
		||||
	int KafkaManager::Start() {
 | 
			
		||||
		return 0;
 | 
			
		||||
	}
 | 
			
		||||
	void KafkaManager::Stop() {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
#else
 | 
			
		||||
 | 
			
		||||
	int KafkaManager::Start() {
 | 
			
		||||
		if(!KafkaEnabled_)
 | 
			
		||||
			return 0;
 | 
			
		||||
		ProducerThr_ = std::make_unique<std::thread>([this]() { this->ProducerThr(); });
 | 
			
		||||
		ConsumerThr_ = std::make_unique<std::thread>([this]() { this->ConsumerThr(); });
 | 
			
		||||
		return 0;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	void KafkaManager::Stop() {
 | 
			
		||||
		if(KafkaEnabled_) {
 | 
			
		||||
			ProducerRunning_ = ConsumerRunning_ = false;
 | 
			
		||||
			ProducerThr_->join();
 | 
			
		||||
			ConsumerThr_->join();
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	void KafkaManager::ProducerThr() {
 | 
			
		||||
		cppkafka::Configuration Config({
 | 
			
		||||
										   { "client.id", Daemon()->ConfigGetString("openwifi.kafka.client.id") },
 | 
			
		||||
										   { "metadata.broker.list", Daemon()->ConfigGetString("openwifi.kafka.brokerlist") }
 | 
			
		||||
									   });
 | 
			
		||||
		SystemInfoWrapper_ = 	R"lit({ "system" : { "id" : )lit" +
 | 
			
		||||
								  	std::to_string(Daemon()->ID()) +
 | 
			
		||||
									R"lit( , "host" : ")lit" + Daemon()->PrivateEndPoint() +
 | 
			
		||||
									R"lit(" } , "payload" : )lit" ;
 | 
			
		||||
		cppkafka::Producer	Producer(Config);
 | 
			
		||||
		ProducerRunning_ = true;
 | 
			
		||||
		while(ProducerRunning_) {
 | 
			
		||||
			std::this_thread::sleep_for(std::chrono::milliseconds(200));
 | 
			
		||||
			try
 | 
			
		||||
			{
 | 
			
		||||
				std::lock_guard G(ProducerMutex_);
 | 
			
		||||
				auto Num=0;
 | 
			
		||||
				while (!Queue_.empty()) {
 | 
			
		||||
					const auto M = Queue_.front();
 | 
			
		||||
					Producer.produce(
 | 
			
		||||
						cppkafka::MessageBuilder(M.Topic).key(M.Key).payload(M.PayLoad));
 | 
			
		||||
					Queue_.pop();
 | 
			
		||||
					Num++;
 | 
			
		||||
				}
 | 
			
		||||
				if(Num)
 | 
			
		||||
					Producer.flush();
 | 
			
		||||
			} catch (const cppkafka::HandleException &E ) {
 | 
			
		||||
				Logger_.warning(Poco::format("Caught a Kafka exception (producer): %s",std::string{E.what()}));
 | 
			
		||||
			} catch (const Poco::Exception &E) {
 | 
			
		||||
				Logger_.log(E);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	void KafkaManager::PartitionAssignment(const cppkafka::TopicPartitionList& partitions) {
 | 
			
		||||
		Logger_.information(Poco::format("Partition assigned: %Lu...",(uint64_t )partitions.front().get_partition()));
 | 
			
		||||
	}
 | 
			
		||||
	void KafkaManager::PartitionRevocation(const cppkafka::TopicPartitionList& partitions) {
 | 
			
		||||
		Logger_.information(Poco::format("Partition revocation: %Lu...",(uint64_t )partitions.front().get_partition()));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	void KafkaManager::ConsumerThr() {
 | 
			
		||||
		cppkafka::Configuration Config({
 | 
			
		||||
										   { "client.id", Daemon()->ConfigGetString("openwifi.kafka.client.id") },
 | 
			
		||||
										   { "metadata.broker.list", Daemon()->ConfigGetString("openwifi.kafka.brokerlist") },
 | 
			
		||||
										   { "group.id", Daemon()->ConfigGetString("openwifi.kafka.group.id") },
 | 
			
		||||
										   { "enable.auto.commit", Daemon()->ConfigGetBool("openwifi.kafka.auto.commit",false) },
 | 
			
		||||
										   { "auto.offset.reset", "latest" } ,
 | 
			
		||||
										   { "enable.partition.eof", false }
 | 
			
		||||
									   });
 | 
			
		||||
 | 
			
		||||
		cppkafka::TopicConfiguration topic_config = {
 | 
			
		||||
			{ "auto.offset.reset", "smallest" }
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		// Now configure it to be the default topic config
 | 
			
		||||
		Config.set_default_topic_configuration(topic_config);
 | 
			
		||||
 | 
			
		||||
		cppkafka::Consumer Consumer(Config);
 | 
			
		||||
		Consumer.set_assignment_callback([this](cppkafka::TopicPartitionList& partitions) {
 | 
			
		||||
			if(!partitions.empty()) {
 | 
			
		||||
				Logger_.information(Poco::format("Partition assigned: %Lu...",
 | 
			
		||||
												 (uint64_t)partitions.front().get_partition()));
 | 
			
		||||
			}
 | 
			
		||||
		});
 | 
			
		||||
		Consumer.set_revocation_callback([this](const cppkafka::TopicPartitionList& partitions) {
 | 
			
		||||
			if(!partitions.empty()) {
 | 
			
		||||
				Logger_.information(Poco::format("Partition revocation: %Lu...",
 | 
			
		||||
												 (uint64_t)partitions.front().get_partition()));
 | 
			
		||||
			}
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
        bool AutoCommit = Daemon()->ConfigGetBool("openwifi.kafka.auto.commit",false);
 | 
			
		||||
        auto BatchSize = Daemon()->ConfigGetInt("openwifi.kafka.consumer.batchsize",20);
 | 
			
		||||
 | 
			
		||||
        Types::StringVec    Topics;
 | 
			
		||||
		for(const auto &i:Notifiers_)
 | 
			
		||||
			Topics.push_back(i.first);
 | 
			
		||||
 | 
			
		||||
		Consumer.subscribe(Topics);
 | 
			
		||||
 | 
			
		||||
		ConsumerRunning_ = true;
 | 
			
		||||
		while(ConsumerRunning_) {
 | 
			
		||||
			try {
 | 
			
		||||
				std::vector<cppkafka::Message> MsgVec = Consumer.poll_batch(BatchSize, std::chrono::milliseconds(200));
 | 
			
		||||
				for(auto const &Msg:MsgVec) {
 | 
			
		||||
                    if (!Msg)
 | 
			
		||||
                        continue;
 | 
			
		||||
                    if (Msg.get_error()) {
 | 
			
		||||
                        if (!Msg.is_eof()) {
 | 
			
		||||
                            Logger_.error(Poco::format("Error: %s", Msg.get_error().to_string()));
 | 
			
		||||
                        }if(!AutoCommit)
 | 
			
		||||
                            Consumer.async_commit(Msg);
 | 
			
		||||
                        continue;
 | 
			
		||||
                    }
 | 
			
		||||
                    std::lock_guard G(ConsumerMutex_);
 | 
			
		||||
                    auto It = Notifiers_.find(Msg.get_topic());
 | 
			
		||||
                    if (It != Notifiers_.end()) {
 | 
			
		||||
                        Types::TopicNotifyFunctionList &FL = It->second;
 | 
			
		||||
                        std::string Key{Msg.get_key()};
 | 
			
		||||
                        std::string Payload{Msg.get_payload()};
 | 
			
		||||
                        for (auto &F : FL) {
 | 
			
		||||
                            std::thread T(F.first, Key, Payload);
 | 
			
		||||
                            T.detach();
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    if (!AutoCommit)
 | 
			
		||||
                        Consumer.async_commit(Msg);
 | 
			
		||||
                }
 | 
			
		||||
			} catch (const cppkafka::HandleException &E) {
 | 
			
		||||
				Logger_.warning(Poco::format("Caught a Kafka exception (consumer): %s",std::string{E.what()}));
 | 
			
		||||
			} catch (const Poco::Exception &E) {
 | 
			
		||||
				Logger_.log(E);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	std::string KafkaManager::WrapSystemId(const std::string & PayLoad) {
 | 
			
		||||
		return std::move( SystemInfoWrapper_ + PayLoad + "}");
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	void KafkaManager::PostMessage(const std::string &topic, const std::string & key, const std::string &PayLoad, bool WrapMessage ) {
 | 
			
		||||
		if(KafkaEnabled_) {
 | 
			
		||||
			std::lock_guard G(Mutex_);
 | 
			
		||||
			KMessage M{
 | 
			
		||||
				.Topic = topic,
 | 
			
		||||
				.Key = key,
 | 
			
		||||
				.PayLoad = WrapMessage ? WrapSystemId(PayLoad) : PayLoad };
 | 
			
		||||
			Queue_.push(M);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	int KafkaManager::RegisterTopicWatcher(const std::string &Topic, Types::TopicNotifyFunction &F) {
 | 
			
		||||
		if(KafkaEnabled_) {
 | 
			
		||||
			std::lock_guard G(Mutex_);
 | 
			
		||||
			auto It = Notifiers_.find(Topic);
 | 
			
		||||
			if(It == Notifiers_.end()) {
 | 
			
		||||
				Types::TopicNotifyFunctionList L;
 | 
			
		||||
				L.emplace(L.end(),std::make_pair(F,FunctionId_));
 | 
			
		||||
				Notifiers_[Topic] = std::move(L);
 | 
			
		||||
			} else {
 | 
			
		||||
				It->second.emplace(It->second.end(),std::make_pair(F,FunctionId_));
 | 
			
		||||
			}
 | 
			
		||||
			return FunctionId_++;
 | 
			
		||||
		} else {
 | 
			
		||||
			return 0;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	void KafkaManager::UnregisterTopicWatcher(const std::string &Topic, int Id) {
 | 
			
		||||
		if(KafkaEnabled_) {
 | 
			
		||||
			std::lock_guard G(Mutex_);
 | 
			
		||||
			auto It = Notifiers_.find(Topic);
 | 
			
		||||
			if(It != Notifiers_.end()) {
 | 
			
		||||
				Types::TopicNotifyFunctionList & L = It->second;
 | 
			
		||||
				for(auto it=L.begin(); it!=L.end(); it++)
 | 
			
		||||
					if(it->second == Id) {
 | 
			
		||||
						L.erase(it);
 | 
			
		||||
						break;
 | 
			
		||||
					}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
#endif
 | 
			
		||||
} // namespace
 | 
			
		||||
@@ -1,74 +0,0 @@
 | 
			
		||||
//
 | 
			
		||||
//	License type: BSD 3-Clause License
 | 
			
		||||
//	License copy: https://github.com/Telecominfraproject/wlan-cloud-ucentralgw/blob/master/LICENSE
 | 
			
		||||
//
 | 
			
		||||
//	Created by Stephane Bourque on 2021-03-04.
 | 
			
		||||
//	Arilia Wireless Inc.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#ifndef UCENTRALGW_KAFKAMANAGER_H
 | 
			
		||||
#define UCENTRALGW_KAFKAMANAGER_H
 | 
			
		||||
 | 
			
		||||
#include <queue>
 | 
			
		||||
#include <thread>
 | 
			
		||||
 | 
			
		||||
#include "SubSystemServer.h"
 | 
			
		||||
#include "OpenWifiTypes.h"
 | 
			
		||||
 | 
			
		||||
#include "cppkafka/cppkafka.h"
 | 
			
		||||
 | 
			
		||||
namespace OpenWifi {
 | 
			
		||||
 | 
			
		||||
	class KafkaManager : public SubSystemServer {
 | 
			
		||||
	  public:
 | 
			
		||||
 | 
			
		||||
		struct KMessage {
 | 
			
		||||
					std::string Topic,
 | 
			
		||||
								Key,
 | 
			
		||||
								PayLoad;
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		void initialize(Poco::Util::Application & self) override;
 | 
			
		||||
		static KafkaManager *instance() {
 | 
			
		||||
			if(instance_== nullptr)
 | 
			
		||||
				instance_ = new KafkaManager;
 | 
			
		||||
			return instance_;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		void ProducerThr();
 | 
			
		||||
		void ConsumerThr();
 | 
			
		||||
 | 
			
		||||
		int Start() override;
 | 
			
		||||
		void Stop() override;
 | 
			
		||||
 | 
			
		||||
		void PostMessage(const std::string &topic, const std::string & key, const std::string &payload, bool WrapMessage = true);
 | 
			
		||||
		[[nodiscard]] std::string WrapSystemId(const std::string & PayLoad);
 | 
			
		||||
		[[nodiscard]] bool Enabled() { return KafkaEnabled_; }
 | 
			
		||||
		int RegisterTopicWatcher(const std::string &Topic, Types::TopicNotifyFunction & F);
 | 
			
		||||
		void UnregisterTopicWatcher(const std::string &Topic, int FunctionId);
 | 
			
		||||
		void WakeUp();
 | 
			
		||||
		void PartitionAssignment(const cppkafka::TopicPartitionList& partitions);
 | 
			
		||||
		void PartitionRevocation(const cppkafka::TopicPartitionList& partitions);
 | 
			
		||||
 | 
			
		||||
	  private:
 | 
			
		||||
		static KafkaManager 			*instance_;
 | 
			
		||||
		std::mutex 						ProducerMutex_;
 | 
			
		||||
		std::mutex						ConsumerMutex_;
 | 
			
		||||
		bool 							KafkaEnabled_ = false;
 | 
			
		||||
		std::atomic_bool 				ProducerRunning_ = false;
 | 
			
		||||
		std::atomic_bool 				ConsumerRunning_ = false;
 | 
			
		||||
		std::queue<KMessage>			Queue_;
 | 
			
		||||
		std::string 					SystemInfoWrapper_;
 | 
			
		||||
		std::unique_ptr<std::thread>	ConsumerThr_;
 | 
			
		||||
		std::unique_ptr<std::thread>	ProducerThr_;
 | 
			
		||||
		int                       		FunctionId_=1;
 | 
			
		||||
		Types::NotifyTable        		Notifiers_;
 | 
			
		||||
		std::unique_ptr<cppkafka::Configuration>    Config_;
 | 
			
		||||
 | 
			
		||||
		KafkaManager() noexcept;
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	inline KafkaManager * KafkaManager() { return KafkaManager::instance(); }
 | 
			
		||||
}	// NameSpace
 | 
			
		||||
 | 
			
		||||
#endif // UCENTRALGW_KAFKAMANAGER_H
 | 
			
		||||
							
								
								
									
										119
									
								
								src/MFAServer.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										119
									
								
								src/MFAServer.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,119 @@
 | 
			
		||||
//
 | 
			
		||||
// Created by stephane bourque on 2021-10-11.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#include "MFAServer.h"
 | 
			
		||||
#include "SMSSender.h"
 | 
			
		||||
#include "SMTPMailerService.h"
 | 
			
		||||
#include "framework/MicroService.h"
 | 
			
		||||
#include "AuthService.h"
 | 
			
		||||
#include "TotpCache.h"
 | 
			
		||||
 | 
			
		||||
namespace OpenWifi {
 | 
			
		||||
 | 
			
		||||
    int MFAServer::Start() {
 | 
			
		||||
        return 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void MFAServer::Stop() {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    bool MFAServer::StartMFAChallenge(const SecurityObjects::UserInfoAndPolicy &UInfo, Poco::JSON::Object &ChallengeStart) {
 | 
			
		||||
        std::lock_guard G(Mutex_);
 | 
			
		||||
 | 
			
		||||
        CleanCache();
 | 
			
		||||
 | 
			
		||||
        if(!MethodEnabled(UInfo.userinfo.userTypeProprietaryInfo.mfa.method))
 | 
			
		||||
            return false;
 | 
			
		||||
 | 
			
		||||
        std::string Challenge = MakeChallenge();
 | 
			
		||||
        std::string uuid = MicroService::CreateUUID();
 | 
			
		||||
        uint64_t Created = OpenWifi::Now();
 | 
			
		||||
 | 
			
		||||
        ChallengeStart.set("uuid",uuid);
 | 
			
		||||
        ChallengeStart.set("created", Created);
 | 
			
		||||
        ChallengeStart.set("question", "mfa challenge");
 | 
			
		||||
        ChallengeStart.set("method", UInfo.userinfo.userTypeProprietaryInfo.mfa.method);
 | 
			
		||||
 | 
			
		||||
        Cache_[uuid] = MFACacheEntry{ .UInfo = UInfo, .Answer=Challenge, .Created=Created, .Method=UInfo.userinfo.userTypeProprietaryInfo.mfa.method };
 | 
			
		||||
        return SendChallenge(UInfo, UInfo.userinfo.userTypeProprietaryInfo.mfa.method, Challenge);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    bool MFAServer::SendChallenge(const SecurityObjects::UserInfoAndPolicy &UInfo, const std::string &Method, const std::string &Challenge) {
 | 
			
		||||
        if(Method==MFAMETHODS::SMS && SMSSender()->Enabled() && !UInfo.userinfo.userTypeProprietaryInfo.mobiles.empty()) {
 | 
			
		||||
            std::string Message = "This is your login code: " + Challenge + " Please enter this in your login screen.";
 | 
			
		||||
            return SMSSender()->Send(UInfo.userinfo.userTypeProprietaryInfo.mobiles[0].number, Message);
 | 
			
		||||
        } else if(Method==MFAMETHODS::EMAIL && SMTPMailerService()->Enabled() && !UInfo.userinfo.email.empty()) {
 | 
			
		||||
            MessageAttributes Attrs;
 | 
			
		||||
            Attrs[RECIPIENT_EMAIL] = UInfo.userinfo.email;
 | 
			
		||||
            Attrs[LOGO] = AuthService::GetLogoAssetURI();
 | 
			
		||||
            Attrs[SUBJECT] = "Login validation code";
 | 
			
		||||
            Attrs[CHALLENGE_CODE] = Challenge;
 | 
			
		||||
            return SMTPMailerService()->SendMessage(UInfo.userinfo.email, "verification_code.txt", Attrs);
 | 
			
		||||
        } else if(Method==MFAMETHODS::AUTHENTICATOR && !UInfo.userinfo.userTypeProprietaryInfo.authenticatorSecret.empty()) {
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    bool MFAServer::ResendCode(const std::string &uuid) {
 | 
			
		||||
        std::lock_guard G(Mutex_);
 | 
			
		||||
        auto Hint = Cache_.find(uuid);
 | 
			
		||||
        if(Hint==Cache_.end())
 | 
			
		||||
            return false;
 | 
			
		||||
        return SendChallenge(Hint->second.UInfo, Hint->second.Method, Hint->second.Answer);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    bool MFAServer::CompleteMFAChallenge(const Poco::JSON::Object::Ptr &ChallengeResponse, SecurityObjects::UserInfoAndPolicy &UInfo) {
 | 
			
		||||
        std::lock_guard G(Mutex_);
 | 
			
		||||
 | 
			
		||||
        if(!ChallengeResponse->has("uuid") || !ChallengeResponse->has("answer"))
 | 
			
		||||
            return false;
 | 
			
		||||
 | 
			
		||||
        auto uuid = ChallengeResponse->get("uuid").toString();
 | 
			
		||||
        auto Hint = Cache_.find(uuid);
 | 
			
		||||
        if(Hint == end(Cache_)) {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        auto answer = ChallengeResponse->get("answer").toString();
 | 
			
		||||
        std::string Expecting;
 | 
			
		||||
        if(Hint->second.Method==MFAMETHODS::AUTHENTICATOR) {
 | 
			
		||||
            if(!TotpCache()->ValidateCode(Hint->second.UInfo.userinfo.userTypeProprietaryInfo.authenticatorSecret,answer, Expecting)) {
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
        } else if(Hint->second.Answer!=answer) {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        UInfo = Hint->second.UInfo;
 | 
			
		||||
        Cache_.erase(Hint);
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    bool MFAServer::MethodEnabled(const std::string &Method) {
 | 
			
		||||
        if(Method==MFAMETHODS::SMS)
 | 
			
		||||
            return SMSSender()->Enabled();
 | 
			
		||||
 | 
			
		||||
        if(Method==MFAMETHODS::EMAIL)
 | 
			
		||||
            return SMTPMailerService()->Enabled();
 | 
			
		||||
 | 
			
		||||
        if(Method==MFAMETHODS::AUTHENTICATOR)
 | 
			
		||||
            return true;
 | 
			
		||||
 | 
			
		||||
        return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void MFAServer::CleanCache() {
 | 
			
		||||
        // it is assumed that you have locked Cache_ at this point.
 | 
			
		||||
        uint64_t Now = OpenWifi::Now();
 | 
			
		||||
        for(auto i=begin(Cache_);i!=end(Cache_);) {
 | 
			
		||||
            if((Now-i->second.Created)>300) {
 | 
			
		||||
                i = Cache_.erase(i);
 | 
			
		||||
            } else {
 | 
			
		||||
                ++i;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										64
									
								
								src/MFAServer.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								src/MFAServer.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,64 @@
 | 
			
		||||
//
 | 
			
		||||
// Created by stephane bourque on 2021-10-11.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "framework/MicroService.h"
 | 
			
		||||
#include "Poco/JSON/Object.h"
 | 
			
		||||
#include "RESTObjects/RESTAPI_SecurityObjects.h"
 | 
			
		||||
 | 
			
		||||
namespace OpenWifi {
 | 
			
		||||
 | 
			
		||||
    namespace MFAMETHODS {
 | 
			
		||||
        inline const static std::string SMS{"sms"};
 | 
			
		||||
        inline const static std::string EMAIL{"email"};
 | 
			
		||||
        inline const static std::string AUTHENTICATOR{"authenticator"};
 | 
			
		||||
        inline const static std::vector<std::string> Methods{ SMS, EMAIL, AUTHENTICATOR };
 | 
			
		||||
        inline bool Validate(const std::string &M) {
 | 
			
		||||
            return std::find(cbegin(Methods), cend(Methods),M)!=Methods.end();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    struct MFACacheEntry {
 | 
			
		||||
        SecurityObjects::UserInfoAndPolicy  UInfo;
 | 
			
		||||
        std::string                         Answer;
 | 
			
		||||
        uint64_t                            Created;
 | 
			
		||||
        std::string                         Method;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    typedef std::map<std::string,MFACacheEntry>     MFAChallengeCache;
 | 
			
		||||
 | 
			
		||||
    class MFAServer : public SubSystemServer{
 | 
			
		||||
    public:
 | 
			
		||||
        int Start() override;
 | 
			
		||||
        void Stop() override;
 | 
			
		||||
        static auto instance() {
 | 
			
		||||
            static auto instance_ = new MFAServer;
 | 
			
		||||
            return instance_;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        bool StartMFAChallenge(const SecurityObjects::UserInfoAndPolicy &UInfo, Poco::JSON::Object &Challenge);
 | 
			
		||||
        bool CompleteMFAChallenge(const Poco::JSON::Object::Ptr &ChallengeResponse, SecurityObjects::UserInfoAndPolicy &UInfo);
 | 
			
		||||
        static bool MethodEnabled(const std::string &Method);
 | 
			
		||||
        bool ResendCode(const std::string &uuid);
 | 
			
		||||
        static bool SendChallenge(const SecurityObjects::UserInfoAndPolicy &UInfo, const std::string &Method, const std::string &Challenge);
 | 
			
		||||
 | 
			
		||||
        static inline std::string MakeChallenge() {
 | 
			
		||||
            return fmt::format("{0:06}" , MicroService::instance().Random(1,999999) );
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    private:
 | 
			
		||||
        MFAChallengeCache   Cache_;
 | 
			
		||||
        MFAServer() noexcept:
 | 
			
		||||
            SubSystemServer("MFServer", "MFA-SVR", "mfa")
 | 
			
		||||
            {
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
        void CleanCache();
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    inline auto MFAServer() { return MFAServer::instance(); }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -1,532 +0,0 @@
 | 
			
		||||
//
 | 
			
		||||
//	License type: BSD 3-Clause License
 | 
			
		||||
//	License copy: https://github.com/Telecominfraproject/wlan-cloud-ucentralgw/blob/master/LICENSE
 | 
			
		||||
//
 | 
			
		||||
//	Created by Stephane Bourque on 2021-03-04.
 | 
			
		||||
//	Arilia Wireless Inc.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#include <cstdlib>
 | 
			
		||||
#include <boost/algorithm/string.hpp>
 | 
			
		||||
 | 
			
		||||
#include "Poco/Util/Application.h"
 | 
			
		||||
#include "Poco/Util/ServerApplication.h"
 | 
			
		||||
#include "Poco/Util/Option.h"
 | 
			
		||||
#include "Poco/Util/OptionSet.h"
 | 
			
		||||
#include "Poco/Util/HelpFormatter.h"
 | 
			
		||||
#include "Poco/Environment.h"
 | 
			
		||||
#include "Poco/Net/HTTPSStreamFactory.h"
 | 
			
		||||
#include "Poco/Net/HTTPStreamFactory.h"
 | 
			
		||||
#include "Poco/Net/FTPSStreamFactory.h"
 | 
			
		||||
#include "Poco/Net/FTPStreamFactory.h"
 | 
			
		||||
#include "Poco/Path.h"
 | 
			
		||||
#include "Poco/File.h"
 | 
			
		||||
#include "Poco/String.h"
 | 
			
		||||
#include "Poco/JSON/Object.h"
 | 
			
		||||
#include "Poco/JSON/Parser.h"
 | 
			
		||||
#include "Poco/JSON/Stringifier.h"
 | 
			
		||||
 | 
			
		||||
#include "ALBHealthCheckServer.h"
 | 
			
		||||
#ifndef SMALL_BUILD
 | 
			
		||||
#include "KafkaManager.h"
 | 
			
		||||
#endif
 | 
			
		||||
#include "Kafka_topics.h"
 | 
			
		||||
 | 
			
		||||
#include "MicroService.h"
 | 
			
		||||
#include "Utils.h"
 | 
			
		||||
 | 
			
		||||
#ifndef TIP_SECURITY_SERVICE
 | 
			
		||||
#include "AuthClient.h"
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
namespace OpenWifi {
 | 
			
		||||
 | 
			
		||||
	void MyErrorHandler::exception(const Poco::Exception & E) {
 | 
			
		||||
		Poco::Thread * CurrentThread = Poco::Thread::current();
 | 
			
		||||
		App_.logger().log(E);
 | 
			
		||||
		App_.logger().error(Poco::format("Exception occurred in %s",CurrentThread->getName()));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	void MyErrorHandler::exception(const std::exception & E) {
 | 
			
		||||
		Poco::Thread * CurrentThread = Poco::Thread::current();
 | 
			
		||||
		App_.logger().warning(Poco::format("std::exception on %s",CurrentThread->getName()));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	void MyErrorHandler::exception() {
 | 
			
		||||
		Poco::Thread * CurrentThread = Poco::Thread::current();
 | 
			
		||||
		App_.logger().warning(Poco::format("exception on %s",CurrentThread->getName()));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	void MicroService::Exit(int Reason) {
 | 
			
		||||
		std::exit(Reason);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	void MicroService::BusMessageReceived(const std::string &Key, const std::string & Message) {
 | 
			
		||||
		std::lock_guard G(InfraMutex_);
 | 
			
		||||
		try {
 | 
			
		||||
			Poco::JSON::Parser P;
 | 
			
		||||
			auto Object = P.parse(Message).extract<Poco::JSON::Object::Ptr>();
 | 
			
		||||
			if (Object->has(KafkaTopics::ServiceEvents::Fields::ID) &&
 | 
			
		||||
				Object->has(KafkaTopics::ServiceEvents::Fields::EVENT)) {
 | 
			
		||||
				uint64_t 	ID = Object->get(KafkaTopics::ServiceEvents::Fields::ID);
 | 
			
		||||
				auto 		Event = Object->get(KafkaTopics::ServiceEvents::Fields::EVENT).toString();
 | 
			
		||||
				if (ID != ID_) {
 | 
			
		||||
					if(	Event==KafkaTopics::ServiceEvents::EVENT_JOIN ||
 | 
			
		||||
						Event==KafkaTopics::ServiceEvents::EVENT_KEEP_ALIVE ||
 | 
			
		||||
						Event==KafkaTopics::ServiceEvents::EVENT_LEAVE ) {
 | 
			
		||||
						if(	Object->has(KafkaTopics::ServiceEvents::Fields::TYPE) &&
 | 
			
		||||
						   	Object->has(KafkaTopics::ServiceEvents::Fields::PUBLIC) &&
 | 
			
		||||
							Object->has(KafkaTopics::ServiceEvents::Fields::PRIVATE) &&
 | 
			
		||||
						   	Object->has(KafkaTopics::ServiceEvents::Fields::VRSN) &&
 | 
			
		||||
							Object->has(KafkaTopics::ServiceEvents::Fields::KEY)) {
 | 
			
		||||
 | 
			
		||||
							if (Event == KafkaTopics::ServiceEvents::EVENT_KEEP_ALIVE && Services_.find(ID) != Services_.end()) {
 | 
			
		||||
								Services_[ID].LastUpdate = std::time(nullptr);
 | 
			
		||||
							} else if (Event == KafkaTopics::ServiceEvents::EVENT_LEAVE) {
 | 
			
		||||
								Services_.erase(ID);
 | 
			
		||||
								logger().information(Poco::format("Service %s ID=%Lu leaving system.",Object->get(KafkaTopics::ServiceEvents::Fields::PRIVATE).toString(),ID));
 | 
			
		||||
							} else if (Event == KafkaTopics::ServiceEvents::EVENT_JOIN || Event == KafkaTopics::ServiceEvents::EVENT_KEEP_ALIVE) {
 | 
			
		||||
								logger().information(Poco::format("Service %s ID=%Lu joining system.",Object->get(KafkaTopics::ServiceEvents::Fields::PRIVATE).toString(),ID));
 | 
			
		||||
								Services_[ID] = MicroServiceMeta{
 | 
			
		||||
									.Id = ID,
 | 
			
		||||
									.Type = Poco::toLower(Object->get(KafkaTopics::ServiceEvents::Fields::TYPE).toString()),
 | 
			
		||||
									.PrivateEndPoint = Object->get(KafkaTopics::ServiceEvents::Fields::PRIVATE).toString(),
 | 
			
		||||
									.PublicEndPoint = Object->get(KafkaTopics::ServiceEvents::Fields::PUBLIC).toString(),
 | 
			
		||||
									.AccessKey = Object->get(KafkaTopics::ServiceEvents::Fields::KEY).toString(),
 | 
			
		||||
									.Version = Object->get(KafkaTopics::ServiceEvents::Fields::VRSN).toString(),
 | 
			
		||||
									.LastUpdate = (uint64_t)std::time(nullptr)};
 | 
			
		||||
								for (const auto &[Id, Svc] : Services_) {
 | 
			
		||||
									logger().information(Poco::format("ID: %Lu Type: %s EndPoint: %s",Id,Svc.Type,Svc.PrivateEndPoint));
 | 
			
		||||
								}
 | 
			
		||||
							}
 | 
			
		||||
						} else {
 | 
			
		||||
							logger().error(Poco::format("KAFKA-MSG: invalid event '%s', missing a field.",Event));
 | 
			
		||||
						}
 | 
			
		||||
					} else if (Event==KafkaTopics::ServiceEvents::EVENT_REMOVE_TOKEN) {
 | 
			
		||||
						if(Object->has(KafkaTopics::ServiceEvents::Fields::TOKEN)) {
 | 
			
		||||
#ifndef TIP_SECURITY_SERVICE
 | 
			
		||||
							AuthClient()->RemovedCachedToken(Object->get(KafkaTopics::ServiceEvents::Fields::TOKEN).toString());
 | 
			
		||||
#endif
 | 
			
		||||
						} else {
 | 
			
		||||
							logger().error(Poco::format("KAFKA-MSG: invalid event '%s', missing token",Event));
 | 
			
		||||
						}
 | 
			
		||||
					} else {
 | 
			
		||||
						logger().error(Poco::format("Unknown Event: %s Source: %Lu", Event, ID));
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			} else {
 | 
			
		||||
				logger().error("Bad bus message.");
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			auto i=Services_.begin();
 | 
			
		||||
			auto Now = (uint64_t )std::time(nullptr);
 | 
			
		||||
			for(;i!=Services_.end();) {
 | 
			
		||||
			    if((Now - i->second.LastUpdate)>60) {
 | 
			
		||||
			        i = Services_.erase(i);
 | 
			
		||||
			    } else
 | 
			
		||||
			        ++i;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
		} catch (const Poco::Exception &E) {
 | 
			
		||||
			logger().log(E);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	MicroServiceMetaVec MicroService::GetServices(const std::string & Type) {
 | 
			
		||||
		std::lock_guard G(InfraMutex_);
 | 
			
		||||
 | 
			
		||||
		auto T = Poco::toLower(Type);
 | 
			
		||||
		MicroServiceMetaVec	Res;
 | 
			
		||||
		for(const auto &[Id,ServiceRec]:Services_) {
 | 
			
		||||
			if(ServiceRec.Type==T)
 | 
			
		||||
				Res.push_back(ServiceRec);
 | 
			
		||||
		}
 | 
			
		||||
		return Res;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	MicroServiceMetaVec MicroService::GetServices() {
 | 
			
		||||
		std::lock_guard G(InfraMutex_);
 | 
			
		||||
 | 
			
		||||
		MicroServiceMetaVec	Res;
 | 
			
		||||
		for(const auto &[Id,ServiceRec]:Services_) {
 | 
			
		||||
			Res.push_back(ServiceRec);
 | 
			
		||||
		}
 | 
			
		||||
		return Res;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
    void MicroService::LoadConfigurationFile() {
 | 
			
		||||
	    std::string Location = Poco::Environment::get(DAEMON_CONFIG_ENV_VAR,".");
 | 
			
		||||
	    Poco::Path ConfigFile;
 | 
			
		||||
 | 
			
		||||
	    ConfigFile = ConfigFileName_.empty() ? Location + "/" + DAEMON_PROPERTIES_FILENAME : ConfigFileName_;
 | 
			
		||||
 | 
			
		||||
	    if(!ConfigFile.isFile())
 | 
			
		||||
	    {
 | 
			
		||||
	        std::cerr << DAEMON_APP_NAME << ": Configuration "
 | 
			
		||||
	        << ConfigFile.toString() << " does not seem to exist. Please set " + DAEMON_CONFIG_ENV_VAR
 | 
			
		||||
	        + " env variable the path of the " + DAEMON_PROPERTIES_FILENAME + " file." << std::endl;
 | 
			
		||||
	        std::exit(Poco::Util::Application::EXIT_CONFIG);
 | 
			
		||||
	    }
 | 
			
		||||
 | 
			
		||||
	    loadConfiguration(ConfigFile.toString());
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
    void MicroService::Reload() {
 | 
			
		||||
	    LoadConfigurationFile();
 | 
			
		||||
	    LoadMyConfig();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
    void MicroService::LoadMyConfig() {
 | 
			
		||||
	    std::string KeyFile = ConfigPath("openwifi.service.key");
 | 
			
		||||
	    std::string KeyFilePassword = ConfigPath("openwifi.service.key.password" , "" );
 | 
			
		||||
	    AppKey_ = Poco::SharedPtr<Poco::Crypto::RSAKey>(new Poco::Crypto::RSAKey("", KeyFile, KeyFilePassword));
 | 
			
		||||
	    Cipher_ = CipherFactory_.createCipher(*AppKey_);
 | 
			
		||||
	    ID_ = Utils::GetSystemId();
 | 
			
		||||
	    if(!DebugMode_)
 | 
			
		||||
	        DebugMode_ = ConfigGetBool("openwifi.system.debug",false);
 | 
			
		||||
	    MyPrivateEndPoint_ = ConfigGetString("openwifi.system.uri.private");
 | 
			
		||||
	    MyPublicEndPoint_ = ConfigGetString("openwifi.system.uri.public");
 | 
			
		||||
	    UIURI_ = ConfigGetString("openwifi.system.uri.ui");
 | 
			
		||||
	    MyHash_ = CreateHash(MyPublicEndPoint_);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	void MicroService::initialize(Poco::Util::Application &self) {
 | 
			
		||||
		// add the default services
 | 
			
		||||
		SubSystems_.push_back(KafkaManager());
 | 
			
		||||
		SubSystems_.push_back(ALBHealthCheckServer());
 | 
			
		||||
 | 
			
		||||
		Poco::Net::initializeSSL();
 | 
			
		||||
		Poco::Net::HTTPStreamFactory::registerFactory();
 | 
			
		||||
		Poco::Net::HTTPSStreamFactory::registerFactory();
 | 
			
		||||
		Poco::Net::FTPStreamFactory::registerFactory();
 | 
			
		||||
		Poco::Net::FTPSStreamFactory::registerFactory();
 | 
			
		||||
 | 
			
		||||
		LoadConfigurationFile();
 | 
			
		||||
 | 
			
		||||
        static const char * LogFilePathKey = "logging.channels.c2.path";
 | 
			
		||||
 | 
			
		||||
		if(LogDir_.empty()) {
 | 
			
		||||
			std::string OriginalLogFileValue = ConfigPath(LogFilePathKey);
 | 
			
		||||
			config().setString(LogFilePathKey, OriginalLogFileValue);
 | 
			
		||||
		} else {
 | 
			
		||||
			config().setString(LogFilePathKey, LogDir_);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		Poco::File	DataDir(ConfigPath("openwifi.system.data"));
 | 
			
		||||
		DataDir_ = DataDir.path();
 | 
			
		||||
		if(!DataDir.exists()) {
 | 
			
		||||
			try {
 | 
			
		||||
				DataDir.createDirectory();
 | 
			
		||||
			} catch (const Poco::Exception &E) {
 | 
			
		||||
				logger().log(E);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		LoadMyConfig();
 | 
			
		||||
 | 
			
		||||
		InitializeSubSystemServers();
 | 
			
		||||
		ServerApplication::initialize(self);
 | 
			
		||||
 | 
			
		||||
		Types::TopicNotifyFunction F = [this](std::string s1,std::string s2) { this->BusMessageReceived(s1,s2); };
 | 
			
		||||
		KafkaManager()->RegisterTopicWatcher(KafkaTopics::SERVICE_EVENTS, F);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	void MicroService::uninitialize() {
 | 
			
		||||
		// add your own uninitialization code here
 | 
			
		||||
		ServerApplication::uninitialize();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	void MicroService::reinitialize(Poco::Util::Application &self) {
 | 
			
		||||
		ServerApplication::reinitialize(self);
 | 
			
		||||
		// add your own reinitialization code here
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	void MicroService::defineOptions(Poco::Util::OptionSet &options) {
 | 
			
		||||
		ServerApplication::defineOptions(options);
 | 
			
		||||
 | 
			
		||||
		options.addOption(
 | 
			
		||||
			Poco::Util::Option("help", "", "display help information on command line arguments")
 | 
			
		||||
				.required(false)
 | 
			
		||||
				.repeatable(false)
 | 
			
		||||
				.callback(Poco::Util::OptionCallback<MicroService>(this, &MicroService::handleHelp)));
 | 
			
		||||
 | 
			
		||||
		options.addOption(
 | 
			
		||||
			Poco::Util::Option("file", "", "specify the configuration file")
 | 
			
		||||
				.required(false)
 | 
			
		||||
				.repeatable(false)
 | 
			
		||||
				.argument("file")
 | 
			
		||||
				.callback(Poco::Util::OptionCallback<MicroService>(this, &MicroService::handleConfig)));
 | 
			
		||||
 | 
			
		||||
		options.addOption(
 | 
			
		||||
			Poco::Util::Option("debug", "", "to run in debug, set to true")
 | 
			
		||||
				.required(false)
 | 
			
		||||
				.repeatable(false)
 | 
			
		||||
				.callback(Poco::Util::OptionCallback<MicroService>(this, &MicroService::handleDebug)));
 | 
			
		||||
 | 
			
		||||
		options.addOption(
 | 
			
		||||
			Poco::Util::Option("logs", "", "specify the log directory and file (i.e. dir/file.log)")
 | 
			
		||||
				.required(false)
 | 
			
		||||
				.repeatable(false)
 | 
			
		||||
				.argument("dir")
 | 
			
		||||
				.callback(Poco::Util::OptionCallback<MicroService>(this, &MicroService::handleLogs)));
 | 
			
		||||
 | 
			
		||||
		options.addOption(
 | 
			
		||||
			Poco::Util::Option("version", "", "get the version and quit.")
 | 
			
		||||
				.required(false)
 | 
			
		||||
				.repeatable(false)
 | 
			
		||||
				.callback(Poco::Util::OptionCallback<MicroService>(this, &MicroService::handleVersion)));
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	void MicroService::handleHelp(const std::string &name, const std::string &value) {
 | 
			
		||||
		HelpRequested_ = true;
 | 
			
		||||
		displayHelp();
 | 
			
		||||
		stopOptionsProcessing();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	void MicroService::handleVersion(const std::string &name, const std::string &value) {
 | 
			
		||||
		HelpRequested_ = true;
 | 
			
		||||
		std::cout << Version() << std::endl;
 | 
			
		||||
		stopOptionsProcessing();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	void MicroService::handleDebug(const std::string &name, const std::string &value) {
 | 
			
		||||
		if(value == "true")
 | 
			
		||||
			DebugMode_ = true ;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	void MicroService::handleLogs(const std::string &name, const std::string &value) {
 | 
			
		||||
		LogDir_ = value;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	void MicroService::handleConfig(const std::string &name, const std::string &value) {
 | 
			
		||||
		ConfigFileName_ = value;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	void MicroService::displayHelp() {
 | 
			
		||||
		Poco::Util::HelpFormatter helpFormatter(options());
 | 
			
		||||
		helpFormatter.setCommand(commandName());
 | 
			
		||||
		helpFormatter.setUsage("OPTIONS");
 | 
			
		||||
		helpFormatter.setHeader("A " + DAEMON_APP_NAME + " implementation for TIP.");
 | 
			
		||||
		helpFormatter.format(std::cout);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	void MicroService::InitializeSubSystemServers() {
 | 
			
		||||
		for(auto i:SubSystems_)
 | 
			
		||||
			addSubsystem(i);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	void MicroService::StartSubSystemServers() {
 | 
			
		||||
		for(auto i:SubSystems_) {
 | 
			
		||||
			i->Start();
 | 
			
		||||
		}
 | 
			
		||||
		BusEventManager_.Start();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	void MicroService::StopSubSystemServers() {
 | 
			
		||||
		BusEventManager_.Stop();
 | 
			
		||||
		for(auto i=SubSystems_.rbegin(); i!=SubSystems_.rend(); ++i)
 | 
			
		||||
			(*i)->Stop();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	std::string MicroService::CreateUUID() {
 | 
			
		||||
		return UUIDGenerator_.create().toString();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	bool MicroService::SetSubsystemLogLevel(const std::string &SubSystem, const std::string &Level) {
 | 
			
		||||
		try {
 | 
			
		||||
			auto P = Poco::Logger::parseLevel(Level);
 | 
			
		||||
			auto Sub = Poco::toLower(SubSystem);
 | 
			
		||||
 | 
			
		||||
			if (Sub == "all") {
 | 
			
		||||
				for (auto i : SubSystems_) {
 | 
			
		||||
					i->Logger().setLevel(P);
 | 
			
		||||
				}
 | 
			
		||||
				return true;
 | 
			
		||||
			} else {
 | 
			
		||||
				// std::cout << "Sub:" << SubSystem << " Level:" << Level << std::endl;
 | 
			
		||||
				for (auto i : SubSystems_) {
 | 
			
		||||
					if (Sub == Poco::toLower(i->Name())) {
 | 
			
		||||
						i->Logger().setLevel(P);
 | 
			
		||||
						return true;
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		} catch (const Poco::Exception & E) {
 | 
			
		||||
			std::cout << "Exception" << std::endl;
 | 
			
		||||
		}
 | 
			
		||||
		return false;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	void MicroService::Reload(const std::string &Sub) {
 | 
			
		||||
		for (auto i : SubSystems_) {
 | 
			
		||||
			if (Poco::toLower(Sub) == Poco::toLower(i->Name())) {
 | 
			
		||||
				i->reinitialize(Poco::Util::Application::instance());
 | 
			
		||||
				return;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	Types::StringVec MicroService::GetSubSystems() const {
 | 
			
		||||
		Types::StringVec Result;
 | 
			
		||||
		for(auto i:SubSystems_)
 | 
			
		||||
			Result.push_back(Poco::toLower(i->Name()));
 | 
			
		||||
		return Result;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	Types::StringPairVec MicroService::GetLogLevels() {
 | 
			
		||||
		Types::StringPairVec Result;
 | 
			
		||||
 | 
			
		||||
		for(auto &i:SubSystems_) {
 | 
			
		||||
			auto P = std::make_pair( i->Name(), Utils::LogLevelToString(i->GetLoggingLevel()));
 | 
			
		||||
			Result.push_back(P);
 | 
			
		||||
		}
 | 
			
		||||
		return Result;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	const Types::StringVec & MicroService::GetLogLevelNames() {
 | 
			
		||||
		static Types::StringVec LevelNames{"none", "fatal", "critical", "error", "warning", "notice", "information", "debug", "trace" };
 | 
			
		||||
		return LevelNames;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	uint64_t MicroService::ConfigGetInt(const std::string &Key,uint64_t Default) {
 | 
			
		||||
		return (uint64_t) config().getInt64(Key,Default);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	uint64_t MicroService::ConfigGetInt(const std::string &Key) {
 | 
			
		||||
		return config().getInt(Key);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	uint64_t MicroService::ConfigGetBool(const std::string &Key,bool Default) {
 | 
			
		||||
		return config().getBool(Key,Default);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	uint64_t MicroService::ConfigGetBool(const std::string &Key) {
 | 
			
		||||
		return config().getBool(Key);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	std::string MicroService::ConfigGetString(const std::string &Key,const std::string & Default) {
 | 
			
		||||
		return config().getString(Key, Default);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	std::string MicroService::ConfigGetString(const std::string &Key) {
 | 
			
		||||
		return config().getString(Key);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	std::string MicroService::ConfigPath(const std::string &Key,const std::string & Default) {
 | 
			
		||||
		std::string R = config().getString(Key, Default);
 | 
			
		||||
		return Poco::Path::expand(R);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	std::string MicroService::ConfigPath(const std::string &Key) {
 | 
			
		||||
		std::string R = config().getString(Key);
 | 
			
		||||
		return Poco::Path::expand(R);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	std::string MicroService::Encrypt(const std::string &S) {
 | 
			
		||||
		return Cipher_->encryptString(S, Poco::Crypto::Cipher::Cipher::ENC_BASE64);;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	std::string MicroService::Decrypt(const std::string &S) {
 | 
			
		||||
		return Cipher_->decryptString(S, Poco::Crypto::Cipher::Cipher::ENC_BASE64);;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	std::string MicroService::CreateHash(const std::string &S) {
 | 
			
		||||
		SHA2_.update(S);
 | 
			
		||||
		return Utils::ToHex(SHA2_.digest());
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	std::string MicroService::MakeSystemEventMessage( const std::string & Type ) const {
 | 
			
		||||
		Poco::JSON::Object	Obj;
 | 
			
		||||
		Obj.set(KafkaTopics::ServiceEvents::Fields::EVENT,Type);
 | 
			
		||||
		Obj.set(KafkaTopics::ServiceEvents::Fields::ID,ID_);
 | 
			
		||||
		Obj.set(KafkaTopics::ServiceEvents::Fields::TYPE,Poco::toLower(DAEMON_APP_NAME));
 | 
			
		||||
		Obj.set(KafkaTopics::ServiceEvents::Fields::PUBLIC,MyPublicEndPoint_);
 | 
			
		||||
		Obj.set(KafkaTopics::ServiceEvents::Fields::PRIVATE,MyPrivateEndPoint_);
 | 
			
		||||
		Obj.set(KafkaTopics::ServiceEvents::Fields::KEY,MyHash_);
 | 
			
		||||
		Obj.set(KafkaTopics::ServiceEvents::Fields::VRSN,Version_);
 | 
			
		||||
		std::stringstream ResultText;
 | 
			
		||||
		Poco::JSON::Stringifier::stringify(Obj, ResultText);
 | 
			
		||||
		return ResultText.str();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	void BusEventManager::run() {
 | 
			
		||||
		Running_ = true;
 | 
			
		||||
		auto Msg = Daemon()->MakeSystemEventMessage(KafkaTopics::ServiceEvents::EVENT_JOIN);
 | 
			
		||||
		KafkaManager()->PostMessage(KafkaTopics::SERVICE_EVENTS,Daemon()->PrivateEndPoint(),Msg, false);
 | 
			
		||||
		while(Running_) {
 | 
			
		||||
			Poco::Thread::trySleep((unsigned long)Daemon()->DaemonBusTimer());
 | 
			
		||||
			if(!Running_)
 | 
			
		||||
				break;
 | 
			
		||||
			Msg = Daemon()->MakeSystemEventMessage(KafkaTopics::ServiceEvents::EVENT_KEEP_ALIVE);
 | 
			
		||||
			KafkaManager()->PostMessage(KafkaTopics::SERVICE_EVENTS,Daemon()->PrivateEndPoint(),Msg, false);
 | 
			
		||||
		}
 | 
			
		||||
		Msg = Daemon()->MakeSystemEventMessage(KafkaTopics::ServiceEvents::EVENT_LEAVE);
 | 
			
		||||
		KafkaManager()->PostMessage(KafkaTopics::SERVICE_EVENTS,Daemon()->PrivateEndPoint(),Msg, false);
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	void BusEventManager::Start() {
 | 
			
		||||
		if(KafkaManager()->Enabled()) {
 | 
			
		||||
			Thread_.start(*this);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	void BusEventManager::Stop() {
 | 
			
		||||
		if(KafkaManager()->Enabled()) {
 | 
			
		||||
			Running_ = false;
 | 
			
		||||
			Thread_.wakeUp();
 | 
			
		||||
			Thread_.join();
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	[[nodiscard]] bool MicroService::IsValidAPIKEY(const Poco::Net::HTTPServerRequest &Request) {
 | 
			
		||||
		try {
 | 
			
		||||
			auto APIKEY = Request.get("X-API-KEY");
 | 
			
		||||
			return APIKEY == MyHash_;
 | 
			
		||||
		} catch (const Poco::Exception &E) {
 | 
			
		||||
			logger().log(E);
 | 
			
		||||
		}
 | 
			
		||||
		return false;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	void MicroService::SavePID() {
 | 
			
		||||
		try {
 | 
			
		||||
			std::ofstream O;
 | 
			
		||||
			O.open(Daemon()->DataDir() + "/pidfile",std::ios::binary | std::ios::trunc);
 | 
			
		||||
			O << Poco::Process::id();
 | 
			
		||||
			O.close();
 | 
			
		||||
		} catch (...)
 | 
			
		||||
		{
 | 
			
		||||
			std::cout << "Could not save system ID" << std::endl;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	int MicroService::main(const ArgVec &args) {
 | 
			
		||||
 | 
			
		||||
		MyErrorHandler	ErrorHandler(*this);
 | 
			
		||||
		Poco::ErrorHandler::set(&ErrorHandler);
 | 
			
		||||
 | 
			
		||||
		if (!HelpRequested_) {
 | 
			
		||||
			SavePID();
 | 
			
		||||
			Poco::Logger &logger = Poco::Logger::get(DAEMON_APP_NAME);
 | 
			
		||||
			logger.notice(Poco::format("Starting %s version %s.",DAEMON_APP_NAME, Version()));
 | 
			
		||||
 | 
			
		||||
			if(Poco::Net::Socket::supportsIPv6())
 | 
			
		||||
				logger.information("System supports IPv6.");
 | 
			
		||||
			else
 | 
			
		||||
				logger.information("System does NOT support IPv6.");
 | 
			
		||||
 | 
			
		||||
			if (config().getBool("application.runAsDaemon", false)) {
 | 
			
		||||
				logger.information("Starting as a daemon.");
 | 
			
		||||
			}
 | 
			
		||||
			logger.information(Poco::format("System ID set to %Lu",ID_));
 | 
			
		||||
			StartSubSystemServers();
 | 
			
		||||
			waitForTerminationRequest();
 | 
			
		||||
			StopSubSystemServers();
 | 
			
		||||
 | 
			
		||||
			logger.notice(Poco::format("Stopped %s...",DAEMON_APP_NAME));
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return Application::EXIT_OK;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -1,183 +0,0 @@
 | 
			
		||||
//
 | 
			
		||||
//	License type: BSD 3-Clause License
 | 
			
		||||
//	License copy: https://github.com/Telecominfraproject/wlan-cloud-ucentralgw/blob/master/LICENSE
 | 
			
		||||
//
 | 
			
		||||
//	Created by Stephane Bourque on 2021-03-04.
 | 
			
		||||
//	Arilia Wireless Inc.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#ifndef UCENTRALGW_MICROSERVICE_H
 | 
			
		||||
#define UCENTRALGW_MICROSERVICE_H
 | 
			
		||||
 | 
			
		||||
#include <array>
 | 
			
		||||
#include <iostream>
 | 
			
		||||
#include <cstdlib>
 | 
			
		||||
#include <vector>
 | 
			
		||||
#include <set>
 | 
			
		||||
 | 
			
		||||
#include "Poco/Util/Application.h"
 | 
			
		||||
#include "Poco/Util/ServerApplication.h"
 | 
			
		||||
#include "Poco/Util/Option.h"
 | 
			
		||||
#include "Poco/Util/OptionSet.h"
 | 
			
		||||
#include "Poco/UUIDGenerator.h"
 | 
			
		||||
#include "Poco/ErrorHandler.h"
 | 
			
		||||
#include "Poco/Crypto/RSAKey.h"
 | 
			
		||||
#include "Poco/Crypto/CipherFactory.h"
 | 
			
		||||
#include "Poco/Crypto/Cipher.h"
 | 
			
		||||
#include "Poco/SHA2Engine.h"
 | 
			
		||||
#include "Poco/Net/HTTPServerRequest.h"
 | 
			
		||||
#include "Poco/Process.h"
 | 
			
		||||
 | 
			
		||||
#include "OpenWifiTypes.h"
 | 
			
		||||
#include "SubSystemServer.h"
 | 
			
		||||
 | 
			
		||||
namespace OpenWifi {
 | 
			
		||||
 | 
			
		||||
	static const std::string uSERVICE_SECURITY{"owsec"};
 | 
			
		||||
	static const std::string uSERVICE_GATEWAY{"owgw"};
 | 
			
		||||
	static const std::string uSERVICE_FIRMWARE{ "owfms"};
 | 
			
		||||
    static const std::string uSERVICE_TOPOLOGY{ "owtopo"};
 | 
			
		||||
    static const std::string uSERVICE_PROVISIONING{ "owprov"};
 | 
			
		||||
    static const std::string uSERVICE_OWLS{ "owls"};
 | 
			
		||||
 | 
			
		||||
	class MyErrorHandler : public Poco::ErrorHandler {
 | 
			
		||||
	  public:
 | 
			
		||||
		explicit MyErrorHandler(Poco::Util::Application &App) : App_(App) {}
 | 
			
		||||
		void exception(const Poco::Exception & E) override;
 | 
			
		||||
		void exception(const std::exception & E) override;
 | 
			
		||||
		void exception() override;
 | 
			
		||||
	  private:
 | 
			
		||||
		Poco::Util::Application	&App_;
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	class BusEventManager : public Poco::Runnable {
 | 
			
		||||
	  public:
 | 
			
		||||
		void run() override;
 | 
			
		||||
		void Start();
 | 
			
		||||
		void Stop();
 | 
			
		||||
	  private:
 | 
			
		||||
		std::atomic_bool 	Running_ = false;
 | 
			
		||||
		Poco::Thread		Thread_;
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	struct MicroServiceMeta {
 | 
			
		||||
		uint64_t 		Id=0;
 | 
			
		||||
		std::string 	Type;
 | 
			
		||||
		std::string 	PrivateEndPoint;
 | 
			
		||||
		std::string 	PublicEndPoint;
 | 
			
		||||
		std::string 	AccessKey;
 | 
			
		||||
		std::string		Version;
 | 
			
		||||
		uint64_t 		LastUpdate=0;
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	typedef std::map<uint64_t, MicroServiceMeta>	MicroServiceMetaMap;
 | 
			
		||||
	typedef std::vector<MicroServiceMeta>			MicroServiceMetaVec;
 | 
			
		||||
 | 
			
		||||
	class MicroService : public Poco::Util::ServerApplication {
 | 
			
		||||
	  public:
 | 
			
		||||
		explicit MicroService( 	std::string PropFile,
 | 
			
		||||
					 	std::string RootEnv,
 | 
			
		||||
					 	std::string ConfigVar,
 | 
			
		||||
					 	std::string AppName,
 | 
			
		||||
					  	uint64_t BusTimer,
 | 
			
		||||
					  	Types::SubSystemVec Subsystems) :
 | 
			
		||||
			DAEMON_PROPERTIES_FILENAME(std::move(PropFile)),
 | 
			
		||||
			DAEMON_ROOT_ENV_VAR(std::move(RootEnv)),
 | 
			
		||||
			DAEMON_CONFIG_ENV_VAR(std::move(ConfigVar)),
 | 
			
		||||
			DAEMON_APP_NAME(std::move(AppName)),
 | 
			
		||||
			DAEMON_BUS_TIMER(BusTimer),
 | 
			
		||||
			SubSystems_(std::move(Subsystems)) {
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		int main(const ArgVec &args) override;
 | 
			
		||||
		void initialize(Application &self) override;
 | 
			
		||||
		void uninitialize() override;
 | 
			
		||||
		void reinitialize(Application &self) override;
 | 
			
		||||
		void defineOptions(Poco::Util::OptionSet &options) override;
 | 
			
		||||
		void handleHelp(const std::string &name, const std::string &value);
 | 
			
		||||
		void handleVersion(const std::string &name, const std::string &value);
 | 
			
		||||
		void handleDebug(const std::string &name, const std::string &value);
 | 
			
		||||
		void handleLogs(const std::string &name, const std::string &value);
 | 
			
		||||
		void handleConfig(const std::string &name, const std::string &value);
 | 
			
		||||
		void displayHelp();
 | 
			
		||||
 | 
			
		||||
		void InitializeSubSystemServers();
 | 
			
		||||
		void StartSubSystemServers();
 | 
			
		||||
		void StopSubSystemServers();
 | 
			
		||||
		void Exit(int Reason);
 | 
			
		||||
		bool SetSubsystemLogLevel(const std::string & SubSystem, const std::string & Level);
 | 
			
		||||
		[[nodiscard]] std::string Version() { return Version_; }
 | 
			
		||||
		[[nodiscard]] const Poco::SharedPtr<Poco::Crypto::RSAKey> & Key() { return AppKey_; }
 | 
			
		||||
		[[nodiscard]] inline const std::string & DataDir() { return DataDir_; }
 | 
			
		||||
		[[nodiscard]] std::string CreateUUID();
 | 
			
		||||
		[[nodiscard]] bool Debug() const { return DebugMode_; }
 | 
			
		||||
		[[nodiscard]] uint64_t ID() const { return ID_; }
 | 
			
		||||
		[[nodiscard]] Types::StringVec GetSubSystems() const;
 | 
			
		||||
		[[nodiscard]] Types::StringPairVec GetLogLevels() ;
 | 
			
		||||
		[[nodiscard]] static const Types::StringVec & GetLogLevelNames();
 | 
			
		||||
		[[nodiscard]] std::string ConfigGetString(const std::string &Key,const std::string & Default);
 | 
			
		||||
		[[nodiscard]] std::string ConfigGetString(const std::string &Key);
 | 
			
		||||
		[[nodiscard]] std::string ConfigPath(const std::string &Key,const std::string & Default);
 | 
			
		||||
		[[nodiscard]] std::string ConfigPath(const std::string &Key);
 | 
			
		||||
		[[nodiscard]] uint64_t ConfigGetInt(const std::string &Key,uint64_t Default);
 | 
			
		||||
		[[nodiscard]] uint64_t ConfigGetInt(const std::string &Key);
 | 
			
		||||
		[[nodiscard]] uint64_t ConfigGetBool(const std::string &Key,bool Default);
 | 
			
		||||
		[[nodiscard]] uint64_t ConfigGetBool(const std::string &Key);
 | 
			
		||||
		[[nodiscard]] std::string Encrypt(const std::string &S);
 | 
			
		||||
		[[nodiscard]] std::string Decrypt(const std::string &S);
 | 
			
		||||
		[[nodiscard]] std::string CreateHash(const std::string &S);
 | 
			
		||||
		[[nodiscard]] std::string Hash() const { return MyHash_; };
 | 
			
		||||
		[[nodiscard]] std::string ServiceType() const { return DAEMON_APP_NAME; };
 | 
			
		||||
		[[nodiscard]] std::string PrivateEndPoint() const { return MyPrivateEndPoint_; };
 | 
			
		||||
		[[nodiscard]] std::string PublicEndPoint() const { return MyPublicEndPoint_; };
 | 
			
		||||
		[[nodiscard]] std::string MakeSystemEventMessage( const std::string & Type ) const ;
 | 
			
		||||
		[[nodiscard]] const Types::SubSystemVec & GetFullSubSystems() { return SubSystems_; }
 | 
			
		||||
		inline uint64_t DaemonBusTimer() const { return DAEMON_BUS_TIMER; };
 | 
			
		||||
 | 
			
		||||
		void BusMessageReceived( const std::string & Key, const std::string & Message);
 | 
			
		||||
		[[nodiscard]] MicroServiceMetaVec GetServices(const std::string & type);
 | 
			
		||||
		[[nodiscard]] MicroServiceMetaVec GetServices();
 | 
			
		||||
		[[nodiscard]] bool IsValidAPIKEY(const Poco::Net::HTTPServerRequest &Request);
 | 
			
		||||
 | 
			
		||||
		static void SavePID();
 | 
			
		||||
		static inline uint64_t GetPID() { return Poco::Process::id(); };
 | 
			
		||||
		[[nodiscard]] inline const std::string GetPublicAPIEndPoint() { return MyPublicEndPoint_ + "/api/v1"; };
 | 
			
		||||
		[[nodiscard]] inline const std::string & GetUIURI() const { return UIURI_;};
 | 
			
		||||
 | 
			
		||||
		void Reload(const std::string &Name);   //  reload a subsystem
 | 
			
		||||
		void Reload();                          //  reload the daemon itself
 | 
			
		||||
		void LoadMyConfig();
 | 
			
		||||
 | 
			
		||||
		void LoadConfigurationFile();
 | 
			
		||||
 | 
			
		||||
	  private:
 | 
			
		||||
		bool                        HelpRequested_ = false;
 | 
			
		||||
		std::string                 LogDir_;
 | 
			
		||||
		std::string                 ConfigFileName_;
 | 
			
		||||
		Poco::UUIDGenerator         UUIDGenerator_;
 | 
			
		||||
		uint64_t                    ID_ = 1;
 | 
			
		||||
		Poco::SharedPtr<Poco::Crypto::RSAKey>	AppKey_ = nullptr;
 | 
			
		||||
		bool                        DebugMode_ = false;
 | 
			
		||||
		std::string 				DataDir_;
 | 
			
		||||
		Types::SubSystemVec			SubSystems_;
 | 
			
		||||
		Poco::Crypto::CipherFactory & CipherFactory_ = Poco::Crypto::CipherFactory::defaultFactory();
 | 
			
		||||
		Poco::Crypto::Cipher        * Cipher_ = nullptr;
 | 
			
		||||
		Poco::SHA2Engine			SHA2_;
 | 
			
		||||
		MicroServiceMetaMap			Services_;
 | 
			
		||||
		std::string 				MyHash_;
 | 
			
		||||
		std::string 				MyPrivateEndPoint_;
 | 
			
		||||
		std::string 				MyPublicEndPoint_;
 | 
			
		||||
		std::string                 UIURI_;
 | 
			
		||||
		std::string 				Version_{std::string(APP_VERSION) + "("+ BUILD_NUMBER + ")"};
 | 
			
		||||
		BusEventManager				BusEventManager_;
 | 
			
		||||
		std::mutex 					InfraMutex_;
 | 
			
		||||
 | 
			
		||||
		std::string DAEMON_PROPERTIES_FILENAME;
 | 
			
		||||
		std::string DAEMON_ROOT_ENV_VAR;
 | 
			
		||||
		std::string DAEMON_CONFIG_ENV_VAR;
 | 
			
		||||
		std::string DAEMON_APP_NAME;
 | 
			
		||||
		uint64_t 	DAEMON_BUS_TIMER;
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#endif // UCENTRALGW_MICROSERVICE_H
 | 
			
		||||
@@ -1,71 +0,0 @@
 | 
			
		||||
//
 | 
			
		||||
//	License type: BSD 3-Clause License
 | 
			
		||||
//	License copy: https://github.com/Telecominfraproject/wlan-cloud-ucentralgw/blob/master/LICENSE
 | 
			
		||||
//
 | 
			
		||||
//	Created by Stephane Bourque on 2021-03-04.
 | 
			
		||||
//	Arilia Wireless Inc.
 | 
			
		||||
//
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#include <iostream>
 | 
			
		||||
 | 
			
		||||
#include "OpenAPIRequest.h"
 | 
			
		||||
 | 
			
		||||
#include "Poco/Net/HTTPSClientSession.h"
 | 
			
		||||
#include <Poco/Net/HTTPRequest.h>
 | 
			
		||||
#include <Poco/Net/HTTPResponse.h>
 | 
			
		||||
#include <Poco/JSON/Parser.h>
 | 
			
		||||
#include <Poco/URI.h>
 | 
			
		||||
#include <Poco/Exception.h>
 | 
			
		||||
#include "Utils.h"
 | 
			
		||||
#include "Daemon.h"
 | 
			
		||||
 | 
			
		||||
namespace OpenWifi {
 | 
			
		||||
 | 
			
		||||
	OpenAPIRequestGet::OpenAPIRequestGet( 	std::string ServiceType,
 | 
			
		||||
											std::string EndPoint,
 | 
			
		||||
									 		Types::StringPairVec & QueryData,
 | 
			
		||||
											uint64_t msTimeout):
 | 
			
		||||
 		Type_(std::move(ServiceType)),
 | 
			
		||||
 		EndPoint_(std::move(EndPoint)),
 | 
			
		||||
		QueryData_(QueryData),
 | 
			
		||||
		msTimeout_(msTimeout) {
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	int OpenAPIRequestGet::Do(Poco::JSON::Object::Ptr &ResponseObject) {
 | 
			
		||||
		try {
 | 
			
		||||
		    auto Services = Daemon()->GetServices(Type_);
 | 
			
		||||
			for(auto const &Svc:Services) {
 | 
			
		||||
				Poco::URI	URI(Svc.PrivateEndPoint);
 | 
			
		||||
				Poco::Net::HTTPSClientSession Session(URI.getHost(), URI.getPort());
 | 
			
		||||
 | 
			
		||||
				URI.setPath(EndPoint_);
 | 
			
		||||
				for (const auto &qp : QueryData_)
 | 
			
		||||
					URI.addQueryParameter(qp.first, qp.second);
 | 
			
		||||
 | 
			
		||||
				std::string Path(URI.getPathAndQuery());
 | 
			
		||||
				Session.setTimeout(Poco::Timespan(msTimeout_/1000, msTimeout_ % 1000));
 | 
			
		||||
 | 
			
		||||
				Poco::Net::HTTPRequest Request(Poco::Net::HTTPRequest::HTTP_GET,
 | 
			
		||||
											   Path,
 | 
			
		||||
											   Poco::Net::HTTPMessage::HTTP_1_1);
 | 
			
		||||
				Request.add("X-API-KEY", Svc.AccessKey);
 | 
			
		||||
				Session.sendRequest(Request);
 | 
			
		||||
 | 
			
		||||
				Poco::Net::HTTPResponse Response;
 | 
			
		||||
				std::istream &is = Session.receiveResponse(Response);
 | 
			
		||||
				if(Response.getStatus()==Poco::Net::HTTPResponse::HTTP_OK) {
 | 
			
		||||
					Poco::JSON::Parser	P;
 | 
			
		||||
					ResponseObject = P.parse(is).extract<Poco::JSON::Object::Ptr>();
 | 
			
		||||
				}
 | 
			
		||||
				return Response.getStatus();
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		catch (const Poco::Exception &E)
 | 
			
		||||
		{
 | 
			
		||||
			std::cerr << E.displayText() << std::endl;
 | 
			
		||||
		}
 | 
			
		||||
		return -1;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -1,33 +0,0 @@
 | 
			
		||||
//
 | 
			
		||||
//	License type: BSD 3-Clause License
 | 
			
		||||
//	License copy: https://github.com/Telecominfraproject/wlan-cloud-ucentralgw/blob/master/LICENSE
 | 
			
		||||
//
 | 
			
		||||
//	Created by Stephane Bourque on 2021-03-04.
 | 
			
		||||
//	Arilia Wireless Inc.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#ifndef UCENTRALGW_OPENAPIREQUEST_H
 | 
			
		||||
#define UCENTRALGW_OPENAPIREQUEST_H
 | 
			
		||||
 | 
			
		||||
#include "Poco/JSON/Object.h"
 | 
			
		||||
 | 
			
		||||
#include "OpenWifiTypes.h"
 | 
			
		||||
 | 
			
		||||
namespace OpenWifi {
 | 
			
		||||
 | 
			
		||||
	class OpenAPIRequestGet {
 | 
			
		||||
	  public:
 | 
			
		||||
		explicit OpenAPIRequestGet( std::string Type,
 | 
			
		||||
								   	std::string EndPoint,
 | 
			
		||||
									Types::StringPairVec & QueryData,
 | 
			
		||||
									uint64_t msTimeout);
 | 
			
		||||
		int Do(Poco::JSON::Object::Ptr &ResponseObject);
 | 
			
		||||
	  private:
 | 
			
		||||
		std::string 			Type_;
 | 
			
		||||
		std::string 			EndPoint_;
 | 
			
		||||
		Types::StringPairVec 	QueryData_;
 | 
			
		||||
		uint64_t 				msTimeout_;
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#endif // UCENTRALGW_OPENAPIREQUEST_H
 | 
			
		||||
@@ -1,106 +0,0 @@
 | 
			
		||||
//
 | 
			
		||||
//	License type: BSD 3-Clause License
 | 
			
		||||
//	License copy: https://github.com/Telecominfraproject/wlan-cloud-ucentralgw/blob/master/LICENSE
 | 
			
		||||
//
 | 
			
		||||
//	Created by Stephane Bourque on 2021-03-04.
 | 
			
		||||
//	Arilia Wireless Inc.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#ifndef UCENTRALGW_UCENTRALTYPES_H
 | 
			
		||||
#define UCENTRALGW_UCENTRALTYPES_H
 | 
			
		||||
 | 
			
		||||
#include "SubSystemServer.h"
 | 
			
		||||
 | 
			
		||||
#include <vector>
 | 
			
		||||
#include <string>
 | 
			
		||||
#include <map>
 | 
			
		||||
#include <functional>
 | 
			
		||||
#include <list>
 | 
			
		||||
#include <utility>
 | 
			
		||||
#include <queue>
 | 
			
		||||
 | 
			
		||||
#include "Poco/StringTokenizer.h"
 | 
			
		||||
#include "Poco/JSON/Parser.h"
 | 
			
		||||
#include "Poco/JSON/Stringifier.h"
 | 
			
		||||
 | 
			
		||||
namespace OpenWifi::Types {
 | 
			
		||||
    typedef std::pair<std::string,std::string>              StringPair;
 | 
			
		||||
	typedef std::vector<StringPair>	                        StringPairVec;
 | 
			
		||||
    typedef std::queue<StringPair>	                        StringPairQueue;
 | 
			
		||||
	typedef std::vector<std::string>						StringVec;
 | 
			
		||||
	typedef std::set<std::string>                           StringSet;
 | 
			
		||||
	typedef std::vector<SubSystemServer*>					SubSystemVec;
 | 
			
		||||
	typedef std::map<std::string,std::set<std::string>>		StringMapStringSet;
 | 
			
		||||
	typedef std::function<void(std::string, std::string)>   TopicNotifyFunction;
 | 
			
		||||
	typedef std::list<std::pair<TopicNotifyFunction,int>>   TopicNotifyFunctionList;
 | 
			
		||||
	typedef std::map<std::string, TopicNotifyFunctionList>  NotifyTable;
 | 
			
		||||
    typedef std::map<std::string,uint64_t>                  CountedMap;
 | 
			
		||||
 | 
			
		||||
    typedef std::string         UUID_t;
 | 
			
		||||
    typedef std::vector<UUID_t> UUIDvec_t;
 | 
			
		||||
 | 
			
		||||
    inline void UpdateCountedMap(CountedMap &M, const std::string &S, uint64_t Increment=1) {
 | 
			
		||||
        auto it = M.find(S);
 | 
			
		||||
        if(it==M.end())
 | 
			
		||||
            M[S] = Increment;
 | 
			
		||||
        else
 | 
			
		||||
            it->second += Increment;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    inline std::string to_string( const StringVec &V) {
 | 
			
		||||
        Poco::JSON::Array   O;
 | 
			
		||||
        for(const auto &i:V) {
 | 
			
		||||
            O.add(i);
 | 
			
		||||
        }
 | 
			
		||||
        std::stringstream SS;
 | 
			
		||||
        Poco::JSON::Stringifier::stringify(O,SS);
 | 
			
		||||
        return SS.str();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    inline std::string to_string( const StringPairVec &V) {
 | 
			
		||||
        Poco::JSON::Array   O;
 | 
			
		||||
        for(const auto &i:V) {
 | 
			
		||||
            Poco::JSON::Array OO;
 | 
			
		||||
            OO.add(i.first);
 | 
			
		||||
            OO.add(i.second);
 | 
			
		||||
            O.add(OO);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        std::stringstream SS;
 | 
			
		||||
        Poco::JSON::Stringifier::stringify(O,SS);
 | 
			
		||||
        return SS.str();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    inline void from_string(const std::string &S, StringPairVec &V) {
 | 
			
		||||
        try {
 | 
			
		||||
            Poco::JSON::Parser      P;
 | 
			
		||||
            auto O = P.parse(S).extract<Poco::JSON::Array::Ptr>();
 | 
			
		||||
 | 
			
		||||
            for(const auto &i:*O) {
 | 
			
		||||
                auto Inner = i.extract<Poco::JSON::Array::Ptr>();
 | 
			
		||||
                for(const auto &j:*Inner) {
 | 
			
		||||
                    auto S1 = i[0].toString();
 | 
			
		||||
                    auto S2 = i[1].toString();
 | 
			
		||||
                    V.push_back(std::make_pair(S1,S2));
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        } catch (...) {
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    inline void from_string(const std::string &S, StringVec &V) {
 | 
			
		||||
        try {
 | 
			
		||||
            Poco::JSON::Parser      P;
 | 
			
		||||
            auto O = P.parse(S).extract<Poco::JSON::Array::Ptr>();
 | 
			
		||||
 | 
			
		||||
            for(auto const &i:*O) {
 | 
			
		||||
                V.push_back(i.toString());
 | 
			
		||||
            }
 | 
			
		||||
        } catch (...) {
 | 
			
		||||
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#endif // UCENTRALGW_UCENTRALTYPES_H
 | 
			
		||||
							
								
								
									
										265
									
								
								src/RESTAPI/RESTAPI_action_links.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										265
									
								
								src/RESTAPI/RESTAPI_action_links.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,265 @@
 | 
			
		||||
//
 | 
			
		||||
// Created by stephane bourque on 2021-06-22.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#include "Poco/JSON/Parser.h"
 | 
			
		||||
#include "Poco/Net/HTMLForm.h"
 | 
			
		||||
 | 
			
		||||
#include "RESTAPI_action_links.h"
 | 
			
		||||
#include "StorageService.h"
 | 
			
		||||
#include "framework/MicroService.h"
 | 
			
		||||
#include "Daemon.h"
 | 
			
		||||
 | 
			
		||||
namespace OpenWifi {
 | 
			
		||||
 | 
			
		||||
    void RESTAPI_action_links::DoGet() {
 | 
			
		||||
 | 
			
		||||
        auto Action = GetParameter("action","");
 | 
			
		||||
        auto Id = GetParameter("id","");
 | 
			
		||||
 | 
			
		||||
        SecurityObjects::ActionLink Link;
 | 
			
		||||
        if(!StorageService()->ActionLinksDB().GetActionLink(Id,Link))
 | 
			
		||||
            return DoReturnA404();
 | 
			
		||||
 | 
			
		||||
        if(Action=="password_reset")
 | 
			
		||||
            return RequestResetPassword(Link);
 | 
			
		||||
        else if(Action=="email_verification")
 | 
			
		||||
            return DoEmailVerification(Link);
 | 
			
		||||
        else if(Action=="signup_verification")
 | 
			
		||||
            return DoNewSubVerification(Link);
 | 
			
		||||
        else
 | 
			
		||||
            return DoReturnA404();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void RESTAPI_action_links::DoPost() {
 | 
			
		||||
        auto Action = GetParameter("action","");
 | 
			
		||||
 | 
			
		||||
        if(Action=="password_reset")
 | 
			
		||||
            return CompleteResetPassword();
 | 
			
		||||
        else if(Action=="signup_completion")
 | 
			
		||||
            return CompleteSubVerification();
 | 
			
		||||
        else
 | 
			
		||||
            return DoReturnA404();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void RESTAPI_action_links::RequestResetPassword(SecurityObjects::ActionLink &Link) {
 | 
			
		||||
        Logger_.information(fmt::format("REQUEST-PASSWORD-RESET({}): For ID={}", Request->clientAddress().toString(), Link.userId));
 | 
			
		||||
        Poco::File  FormFile{ Daemon()->AssetDir() + "/password_reset.html"};
 | 
			
		||||
        Types::StringPairVec    FormVars{ {"UUID", Link.id},
 | 
			
		||||
                                          {"PASSWORD_VALIDATION", AuthService()->PasswordValidationExpression()}};
 | 
			
		||||
        SendHTMLFileBack(FormFile,FormVars);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void RESTAPI_action_links::DoNewSubVerification(SecurityObjects::ActionLink &Link) {
 | 
			
		||||
        Logger_.information(fmt::format("REQUEST-SUB-SIGNUP({}): For ID={}", Request->clientAddress().toString(), Link.userId));
 | 
			
		||||
        Poco::File  FormFile{ Daemon()->AssetDir() + "/signup_verification.html"};
 | 
			
		||||
        Types::StringPairVec    FormVars{ {"UUID", Link.id},
 | 
			
		||||
                                          {"PASSWORD_VALIDATION", AuthService()->PasswordValidationExpression()}};
 | 
			
		||||
        SendHTMLFileBack(FormFile,FormVars);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void RESTAPI_action_links::CompleteResetPassword() {
 | 
			
		||||
        //  form has been posted...
 | 
			
		||||
        RESTAPI_PartHandler PartHandler;
 | 
			
		||||
        Poco::Net::HTMLForm Form(*Request, Request->stream(), PartHandler);
 | 
			
		||||
        if (!Form.empty()) {
 | 
			
		||||
 | 
			
		||||
            auto Password1 = Form.get("password1","bla");
 | 
			
		||||
            auto Password2 = Form.get("password2","blu");
 | 
			
		||||
            auto Id = Form.get("id","");
 | 
			
		||||
            auto now = OpenWifi::Now();
 | 
			
		||||
 | 
			
		||||
            SecurityObjects::ActionLink Link;
 | 
			
		||||
            if(!StorageService()->ActionLinksDB().GetActionLink(Id,Link))
 | 
			
		||||
                return DoReturnA404();
 | 
			
		||||
 | 
			
		||||
            if(now > Link.expires) {
 | 
			
		||||
                StorageService()->ActionLinksDB().CancelAction(Id);
 | 
			
		||||
                return DoReturnA404();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if(Password1!=Password2 || !AuthService()->ValidatePassword(Password2) || !AuthService()->ValidatePassword(Password1)) {
 | 
			
		||||
                Poco::File  FormFile{ Daemon()->AssetDir() + "/password_reset_error.html"};
 | 
			
		||||
                Types::StringPairVec    FormVars{ {"UUID", Id},
 | 
			
		||||
                                                  {"ERROR_TEXT", "For some reason, the passwords entered do not match or they do not comply with"
 | 
			
		||||
                                                                 " accepted password creation restrictions. Please consult our on-line help"
 | 
			
		||||
                                                                 " to look at the our password policy. If you would like to contact us, please mention"
 | 
			
		||||
                                                                 " id(" + Id + ")"}};
 | 
			
		||||
                return SendHTMLFileBack(FormFile,FormVars);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            SecurityObjects::UserInfo   UInfo;
 | 
			
		||||
 | 
			
		||||
            bool Found = Link.userAction ? StorageService()->UserDB().GetUserById(Link.userId,UInfo) : StorageService()->SubDB().GetUserById(Link.userId,UInfo);
 | 
			
		||||
            if(!Found) {
 | 
			
		||||
                Poco::File  FormFile{ Daemon()->AssetDir() + "/password_reset_error.html"};
 | 
			
		||||
                Types::StringPairVec    FormVars{ {"UUID", Id},
 | 
			
		||||
                                                  {"ERROR_TEXT", "This request does not contain a valid user ID. Please contact your system administrator."}};
 | 
			
		||||
                return SendHTMLFileBack(FormFile,FormVars);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if(UInfo.blackListed || UInfo.suspended) {
 | 
			
		||||
                Poco::File  FormFile{ Daemon()->AssetDir() + "/password_reset_error.html"};
 | 
			
		||||
                Types::StringPairVec    FormVars{ {"UUID", Id},
 | 
			
		||||
                                                  {"ERROR_TEXT", "Please contact our system administrators. We have identified an error in your account that must be resolved first."}};
 | 
			
		||||
                return SendHTMLFileBack(FormFile,FormVars);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            bool GoodPassword = Link.userAction ? AuthService()->SetPassword(Password1,UInfo) : AuthService()->SetSubPassword(Password1,UInfo);
 | 
			
		||||
            if(!GoodPassword) {
 | 
			
		||||
                Poco::File  FormFile{ Daemon()->AssetDir() + "/password_reset_error.html"};
 | 
			
		||||
                Types::StringPairVec    FormVars{ {"UUID", Id},
 | 
			
		||||
                                                  {"ERROR_TEXT", "You cannot reuse one of your recent passwords."}};
 | 
			
		||||
                return SendHTMLFileBack(FormFile,FormVars);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            UInfo.modified = OpenWifi::Now();
 | 
			
		||||
            if(Link.userAction)
 | 
			
		||||
                StorageService()->UserDB().UpdateUserInfo(UInfo.email,Link.userId,UInfo);
 | 
			
		||||
            else
 | 
			
		||||
                StorageService()->SubDB().UpdateUserInfo(UInfo.email,Link.userId,UInfo);
 | 
			
		||||
 | 
			
		||||
            Poco::File  FormFile{ Daemon()->AssetDir() + "/password_reset_success.html"};
 | 
			
		||||
            Types::StringPairVec    FormVars{ {"UUID", Id},
 | 
			
		||||
                                              {"USERNAME", UInfo.email},
 | 
			
		||||
                                              {"ACTION_LINK",MicroService::instance().GetUIURI()}};
 | 
			
		||||
            StorageService()->ActionLinksDB().CompleteAction(Id);
 | 
			
		||||
            SendHTMLFileBack(FormFile,FormVars);
 | 
			
		||||
        } else {
 | 
			
		||||
            DoReturnA404();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void RESTAPI_action_links::CompleteSubVerification() {
 | 
			
		||||
        RESTAPI_PartHandler PartHandler;
 | 
			
		||||
        Poco::Net::HTMLForm Form(*Request, Request->stream(), PartHandler);
 | 
			
		||||
 | 
			
		||||
        if (!Form.empty()) {
 | 
			
		||||
            auto Password1 = Form.get("password1","bla");
 | 
			
		||||
            auto Password2 = Form.get("password2","blu");
 | 
			
		||||
            auto Id = Form.get("id","");
 | 
			
		||||
            auto now = OpenWifi::Now();
 | 
			
		||||
 | 
			
		||||
            SecurityObjects::ActionLink Link;
 | 
			
		||||
            if(!StorageService()->ActionLinksDB().GetActionLink(Id,Link)) {
 | 
			
		||||
                return DoReturnA404();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if(now > Link.expires) {
 | 
			
		||||
                StorageService()->ActionLinksDB().CancelAction(Id);
 | 
			
		||||
                return DoReturnA404();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if(Password1!=Password2 || !AuthService()->ValidateSubPassword(Password1)) {
 | 
			
		||||
                Poco::File  FormFile{ Daemon()->AssetDir() + "/password_reset_error.html"};
 | 
			
		||||
                Types::StringPairVec    FormVars{ {"UUID", Id},
 | 
			
		||||
                                                  {"ERROR_TEXT", "For some reason, the passwords entered do not match or they do not comply with"
 | 
			
		||||
                                                                 " accepted password creation restrictions. Please consult our on-line help"
 | 
			
		||||
                                                                 " to look at the our password policy. If you would like to contact us, please mention"
 | 
			
		||||
                                                                 " id(" + Id + ")"}};
 | 
			
		||||
                return SendHTMLFileBack(FormFile,FormVars);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            SecurityObjects::UserInfo   UInfo;
 | 
			
		||||
            bool Found = StorageService()->SubDB().GetUserById(Link.userId,UInfo);
 | 
			
		||||
            if(!Found) {
 | 
			
		||||
                Poco::File  FormFile{ Daemon()->AssetDir() + "/signup_verification_error.html"};
 | 
			
		||||
                Types::StringPairVec    FormVars{ {"UUID", Id},
 | 
			
		||||
                                                  {"ERROR_TEXT", "This request does not contain a valid user ID. Please contact your system administrator."}};
 | 
			
		||||
                return SendHTMLFileBack(FormFile,FormVars);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if(UInfo.blackListed || UInfo.suspended) {
 | 
			
		||||
                Poco::File  FormFile{ Daemon()->AssetDir() + "/signup_verification_error.html"};
 | 
			
		||||
                Types::StringPairVec    FormVars{ {"UUID", Id},
 | 
			
		||||
                                                  {"ERROR_TEXT", "Please contact our system administrators. We have identified an error in your account that must be resolved first."}};
 | 
			
		||||
                return SendHTMLFileBack(FormFile,FormVars);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            bool GoodPassword = AuthService()->SetSubPassword(Password1,UInfo);
 | 
			
		||||
            if(!GoodPassword) {
 | 
			
		||||
                Poco::File  FormFile{ Daemon()->AssetDir() + "/signup_verification_error.html"};
 | 
			
		||||
                Types::StringPairVec    FormVars{ {"UUID", Id},
 | 
			
		||||
                                                  {"ERROR_TEXT", "You cannot reuse one of your recent passwords."}};
 | 
			
		||||
                return SendHTMLFileBack(FormFile,FormVars);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            UInfo.modified = OpenWifi::Now();
 | 
			
		||||
            UInfo.changePassword = false;
 | 
			
		||||
            UInfo.lastEmailCheck = OpenWifi::Now();
 | 
			
		||||
            UInfo.waitingForEmailCheck = false;
 | 
			
		||||
            UInfo.validated = OpenWifi::Now();
 | 
			
		||||
 | 
			
		||||
            StorageService()->SubDB().UpdateUserInfo(UInfo.email,Link.userId,UInfo);
 | 
			
		||||
 | 
			
		||||
            Poco::File  FormFile{ Daemon()->AssetDir() + "/signup_verification_success.html"};
 | 
			
		||||
            Types::StringPairVec    FormVars{ {"UUID", Id},
 | 
			
		||||
                                              {"USERNAME", UInfo.email} };
 | 
			
		||||
            StorageService()->ActionLinksDB().CompleteAction(Id);
 | 
			
		||||
 | 
			
		||||
            //  Send the update to the provisioning service
 | 
			
		||||
            Poco::JSON::Object  Body;
 | 
			
		||||
            Body.set("signupUUID", UInfo.signingUp);
 | 
			
		||||
            OpenAPIRequestPut   ProvRequest(uSERVICE_PROVISIONING,"/api/v1/signup",
 | 
			
		||||
                                            {
 | 
			
		||||
                                                {"signupUUID", UInfo.signingUp} ,
 | 
			
		||||
                                                {"operation", "emailVerified"}
 | 
			
		||||
                                            },
 | 
			
		||||
                                            Body,30000);
 | 
			
		||||
            Logger().information(fmt::format("({}): Completed subscriber e-mail verification and password.",UInfo.email));
 | 
			
		||||
            Poco::JSON::Object::Ptr Response;
 | 
			
		||||
            auto Status = ProvRequest.Do(Response);
 | 
			
		||||
            std::stringstream ooo;
 | 
			
		||||
            if(Response!= nullptr)
 | 
			
		||||
                Response->stringify(ooo);
 | 
			
		||||
            Logger().information(fmt::format("({}): Completed subscriber e-mail verification. Provisioning notified, Error={}.",
 | 
			
		||||
                                             UInfo.email, Status));
 | 
			
		||||
            SendHTMLFileBack(FormFile,FormVars);
 | 
			
		||||
            Logger().information(fmt::format("({}): Completed subscriber e-mail verification. FORM notified.",UInfo.email));
 | 
			
		||||
        } else {
 | 
			
		||||
            DoReturnA404();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void RESTAPI_action_links::DoEmailVerification(SecurityObjects::ActionLink &Link) {
 | 
			
		||||
        auto now = OpenWifi::Now();
 | 
			
		||||
 | 
			
		||||
        if(now > Link.expires) {
 | 
			
		||||
            StorageService()->ActionLinksDB().CancelAction(Link.id);
 | 
			
		||||
            return DoReturnA404();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        SecurityObjects::UserInfo UInfo;
 | 
			
		||||
        bool Found = Link.userAction ? StorageService()->UserDB().GetUserById(Link.userId,UInfo) : StorageService()->SubDB().GetUserById(Link.userId,UInfo);
 | 
			
		||||
        if (!Found) {
 | 
			
		||||
            Types::StringPairVec FormVars{{"UUID",       Link.id},
 | 
			
		||||
                                          {"ERROR_TEXT", "This does not appear to be a valid email verification link.."}};
 | 
			
		||||
            Poco::File FormFile{Daemon()->AssetDir() + "/email_verification_error.html"};
 | 
			
		||||
            return SendHTMLFileBack(FormFile, FormVars);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Logger_.information(fmt::format("EMAIL-VERIFICATION(%s): For ID={}", Request->clientAddress().toString(), UInfo.email));
 | 
			
		||||
        UInfo.waitingForEmailCheck = false;
 | 
			
		||||
        UInfo.validated = true;
 | 
			
		||||
        UInfo.lastEmailCheck = OpenWifi::Now();
 | 
			
		||||
        UInfo.validationDate = OpenWifi::Now();
 | 
			
		||||
        UInfo.modified  = OpenWifi::Now();
 | 
			
		||||
        if(Link.userAction)
 | 
			
		||||
            StorageService()->UserDB().UpdateUserInfo(UInfo.email, Link.userId, UInfo);
 | 
			
		||||
        else
 | 
			
		||||
            StorageService()->SubDB().UpdateUserInfo(UInfo.email, Link.userId, UInfo);
 | 
			
		||||
        Types::StringPairVec FormVars{{"UUID",     Link.id},
 | 
			
		||||
                                      {"USERNAME", UInfo.email},
 | 
			
		||||
                                      {"ACTION_LINK",MicroService::instance().GetUIURI()}};
 | 
			
		||||
        Poco::File FormFile{Daemon()->AssetDir() + "/email_verification_success.html"};
 | 
			
		||||
        StorageService()->ActionLinksDB().CompleteAction(Link.id);
 | 
			
		||||
        SendHTMLFileBack(FormFile, FormVars);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void RESTAPI_action_links::DoReturnA404() {
 | 
			
		||||
        Types::StringPairVec FormVars;
 | 
			
		||||
        Poco::File FormFile{Daemon()->AssetDir() + "/404_error.html"};
 | 
			
		||||
        SendHTMLFileBack(FormFile, FormVars);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										36
									
								
								src/RESTAPI/RESTAPI_action_links.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								src/RESTAPI/RESTAPI_action_links.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,36 @@
 | 
			
		||||
//
 | 
			
		||||
// Created by stephane bourque on 2021-06-22.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "framework/MicroService.h"
 | 
			
		||||
 | 
			
		||||
namespace OpenWifi {
 | 
			
		||||
    class RESTAPI_action_links : public RESTAPIHandler {
 | 
			
		||||
    public:
 | 
			
		||||
        RESTAPI_action_links(const RESTAPIHandler::BindingMap &bindings, Poco::Logger &L, RESTAPI_GenericServer &Server, uint64_t TransactionId, bool Internal)
 | 
			
		||||
                : RESTAPIHandler(bindings, L,
 | 
			
		||||
             std::vector<std::string>{
 | 
			
		||||
                                        Poco::Net::HTTPRequest::HTTP_GET,
 | 
			
		||||
                                        Poco::Net::HTTPRequest::HTTP_POST,
 | 
			
		||||
                                        Poco::Net::HTTPRequest::HTTP_OPTIONS},
 | 
			
		||||
                                        Server,
 | 
			
		||||
                                        TransactionId,
 | 
			
		||||
                                        Internal,
 | 
			
		||||
                                        false,
 | 
			
		||||
                                        true, RateLimit{.Interval=1000,.MaxCalls=10}) {}
 | 
			
		||||
        static auto PathName() { return std::list<std::string>{"/api/v1/actionLink"}; };
 | 
			
		||||
        void RequestResetPassword(SecurityObjects::ActionLink &Link);
 | 
			
		||||
        void CompleteResetPassword();
 | 
			
		||||
        void CompleteSubVerification();
 | 
			
		||||
        void DoEmailVerification(SecurityObjects::ActionLink &Link);
 | 
			
		||||
        void DoReturnA404();
 | 
			
		||||
        void DoNewSubVerification(SecurityObjects::ActionLink &Link);
 | 
			
		||||
 | 
			
		||||
        void DoGet() final;
 | 
			
		||||
        void DoPost() final;
 | 
			
		||||
        void DoDelete() final {};
 | 
			
		||||
        void DoPut() final {};
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
@@ -2,26 +2,23 @@
 | 
			
		||||
// Created by stephane bourque on 2021-07-10.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#include "RESTAPI_AssetServer.h"
 | 
			
		||||
#include "RESTAPI_asset_server.h"
 | 
			
		||||
#include "Poco/File.h"
 | 
			
		||||
#include "framework/ow_constants.h"
 | 
			
		||||
#include "Daemon.h"
 | 
			
		||||
#include "RESTAPI_server.h"
 | 
			
		||||
#include "Utils.h"
 | 
			
		||||
#include "RESTAPI_protocol.h"
 | 
			
		||||
 | 
			
		||||
namespace OpenWifi {
 | 
			
		||||
    void RESTAPI_AssetServer::DoGet() {
 | 
			
		||||
    void RESTAPI_asset_server::DoGet() {
 | 
			
		||||
        Poco::File  AssetFile;
 | 
			
		||||
 | 
			
		||||
        if(Request->getURI().find("/favicon.ico") != std::string::npos) {
 | 
			
		||||
            AssetFile = RESTAPI_Server()->AssetDir() + "/favicon.ico";
 | 
			
		||||
            AssetFile = Daemon()->AssetDir() + "/favicon.ico";
 | 
			
		||||
        } else {
 | 
			
		||||
            std::string AssetName = GetBinding(RESTAPI::Protocol::ID, "");
 | 
			
		||||
            AssetFile = RESTAPI_Server()->AssetDir() + "/" + AssetName;
 | 
			
		||||
            AssetFile = Daemon()->AssetDir() + "/" + AssetName;
 | 
			
		||||
        }
 | 
			
		||||
        if(!AssetFile.isFile()) {
 | 
			
		||||
            NotFound();
 | 
			
		||||
            return;
 | 
			
		||||
            return NotFound();
 | 
			
		||||
        }
 | 
			
		||||
        SendFile(AssetFile);
 | 
			
		||||
    }
 | 
			
		||||
@@ -2,15 +2,14 @@
 | 
			
		||||
// Created by stephane bourque on 2021-07-10.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#ifndef UCENTRALSEC_RESTAPI_ASSETSERVER_H
 | 
			
		||||
#define UCENTRALSEC_RESTAPI_ASSETSERVER_H
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "RESTAPI_handler.h"
 | 
			
		||||
#include "../framework/MicroService.h"
 | 
			
		||||
 | 
			
		||||
namespace OpenWifi {
 | 
			
		||||
    class RESTAPI_AssetServer : public RESTAPIHandler {
 | 
			
		||||
    class RESTAPI_asset_server : public RESTAPIHandler {
 | 
			
		||||
    public:
 | 
			
		||||
        RESTAPI_AssetServer(const RESTAPIHandler::BindingMap &bindings, Poco::Logger &L, RESTAPI_GenericServer &Server, bool Internal)
 | 
			
		||||
        RESTAPI_asset_server(const RESTAPIHandler::BindingMap &bindings, Poco::Logger &L, RESTAPI_GenericServer &Server, uint64_t TransactionId, bool Internal)
 | 
			
		||||
                : RESTAPIHandler(bindings, L,
 | 
			
		||||
                                 std::vector<std::string>
 | 
			
		||||
                                         {Poco::Net::HTTPRequest::HTTP_POST,
 | 
			
		||||
@@ -19,8 +18,9 @@ namespace OpenWifi {
 | 
			
		||||
                                          Poco::Net::HTTPRequest::HTTP_DELETE,
 | 
			
		||||
                                          Poco::Net::HTTPRequest::HTTP_OPTIONS},
 | 
			
		||||
                                          Server,
 | 
			
		||||
                                          Internal) {}
 | 
			
		||||
        static const std::list<const char *> PathName() { return std::list<const char *>{"/wwwassets/{id}" ,
 | 
			
		||||
                                          TransactionId,
 | 
			
		||||
                                          Internal, false) {}
 | 
			
		||||
        static auto PathName() { return std::list<std::string>{"/wwwassets/{id}" ,
 | 
			
		||||
                                                                                         "/favicon.ico"}; };
 | 
			
		||||
        void DoGet() final;
 | 
			
		||||
        void DoPost() final {};
 | 
			
		||||
@@ -32,5 +32,3 @@ namespace OpenWifi {
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#endif //UCENTRALSEC_RESTAPI_ASSETSERVER_H
 | 
			
		||||
							
								
								
									
										82
									
								
								src/RESTAPI/RESTAPI_avatar_handler.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										82
									
								
								src/RESTAPI/RESTAPI_avatar_handler.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,82 @@
 | 
			
		||||
//
 | 
			
		||||
// Created by stephane bourque on 2021-07-15.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#include <fstream>
 | 
			
		||||
#include <iostream>
 | 
			
		||||
 | 
			
		||||
#include "RESTAPI_avatar_handler.h"
 | 
			
		||||
#include "StorageService.h"
 | 
			
		||||
#include "Poco/Net/HTMLForm.h"
 | 
			
		||||
#include "framework/MicroService.h"
 | 
			
		||||
 | 
			
		||||
namespace OpenWifi {
 | 
			
		||||
 | 
			
		||||
    void AvatarPartHandler::handlePart(const Poco::Net::MessageHeader &Header, std::istream &Stream) {
 | 
			
		||||
        FileType_ = Header.get(RESTAPI::Protocol::CONTENTTYPE, RESTAPI::Protocol::UNSPECIFIED);
 | 
			
		||||
        if (Header.has(RESTAPI::Protocol::CONTENTDISPOSITION)) {
 | 
			
		||||
            std::string Disposition;
 | 
			
		||||
            Poco::Net::NameValueCollection Parameters;
 | 
			
		||||
            Poco::Net::MessageHeader::splitParameters(Header[RESTAPI::Protocol::CONTENTDISPOSITION], Disposition, Parameters);
 | 
			
		||||
            Name_ = Parameters.get(RESTAPI::Protocol::NAME, RESTAPI::Protocol::UNNAMED);
 | 
			
		||||
        }
 | 
			
		||||
        Poco::CountingInputStream InputStream(Stream);
 | 
			
		||||
        Poco::StreamCopier::copyStream(InputStream, OutputStream_);
 | 
			
		||||
        Length_ = OutputStream_.str().size();
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    void RESTAPI_avatar_handler::DoPost() {
 | 
			
		||||
        std::string Id = UserInfo_.userinfo.id;
 | 
			
		||||
        SecurityObjects::UserInfo UInfo;
 | 
			
		||||
 | 
			
		||||
        std::stringstream SS;
 | 
			
		||||
        AvatarPartHandler partHandler(Id, Logger_, SS);
 | 
			
		||||
        Poco::Net::HTMLForm form(*Request, Request->stream(), partHandler);
 | 
			
		||||
        Poco::JSON::Object Answer;
 | 
			
		||||
 | 
			
		||||
        if (!partHandler.Name().empty() && partHandler.Length()< MicroService::instance().ConfigGetInt("openwifi.avatar.maxsize",2000000)) {
 | 
			
		||||
            Answer.set(RESTAPI::Protocol::AVATARID, Id);
 | 
			
		||||
            Answer.set(RESTAPI::Protocol::ERRORCODE, 0);
 | 
			
		||||
            Logger_.information(fmt::format("Uploaded avatar: {} Type: {}", partHandler.Name(), partHandler.ContentType()));
 | 
			
		||||
            StorageService()->AvatarDB().SetAvatar(UserInfo_.userinfo.email,
 | 
			
		||||
                                 Id, SS.str(), partHandler.ContentType(), partHandler.Name());
 | 
			
		||||
            StorageService()->UserDB().SetAvatar(Id,"1");
 | 
			
		||||
            Logger().information(fmt::format("Adding avatar for {}",UserInfo_.userinfo.email));
 | 
			
		||||
        } else {
 | 
			
		||||
            Answer.set(RESTAPI::Protocol::AVATARID, Id);
 | 
			
		||||
            Answer.set(RESTAPI::Protocol::ERRORCODE, 13);
 | 
			
		||||
            Answer.set(RESTAPI::Protocol::ERRORTEXT, "Avatar upload could not complete.");
 | 
			
		||||
        }
 | 
			
		||||
        ReturnObject(Answer);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void RESTAPI_avatar_handler::DoGet() {
 | 
			
		||||
        std::string Id = GetBinding(RESTAPI::Protocol::ID, "");
 | 
			
		||||
        if (Id.empty()) {
 | 
			
		||||
            return NotFound();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        std::string Type, Name, AvatarContent;
 | 
			
		||||
        if (!StorageService()->AvatarDB().GetAvatar(UserInfo_.userinfo.email, Id, AvatarContent, Type, Name)) {
 | 
			
		||||
            return NotFound();
 | 
			
		||||
        }
 | 
			
		||||
        Logger().information(fmt::format("Retrieving avatar for {}, size:{}",UserInfo_.userinfo.email,AvatarContent.size()));
 | 
			
		||||
        return SendFileContent(AvatarContent, Type, Name);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void RESTAPI_avatar_handler::DoDelete() {
 | 
			
		||||
        std::string Id = GetBinding(RESTAPI::Protocol::ID, "");
 | 
			
		||||
 | 
			
		||||
        if(UserInfo_.userinfo.userRole!=SecurityObjects::ROOT && Id!=UserInfo_.userinfo.id) {
 | 
			
		||||
            return UnAuthorized(RESTAPI::Errors::ACCESS_DENIED);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (!StorageService()->AvatarDB().DeleteAvatar(UserInfo_.userinfo.email, Id)) {
 | 
			
		||||
            return NotFound();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Logger().information(fmt::format("Deleted avatar for {}",UserInfo_.userinfo.email));
 | 
			
		||||
        StorageService()->UserDB().SetAvatar(Id,"");
 | 
			
		||||
        OK();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,39 +1,38 @@
 | 
			
		||||
//
 | 
			
		||||
// Created by stephane bourque on 2021-07-15.
 | 
			
		||||
//
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#ifndef UCENTRALSEC_RESTAPI_AVATARHANDLER_H
 | 
			
		||||
#define UCENTRALSEC_RESTAPI_AVATARHANDLER_H
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#include "RESTAPI_handler.h"
 | 
			
		||||
#include "framework/MicroService.h"
 | 
			
		||||
 | 
			
		||||
namespace OpenWifi {
 | 
			
		||||
 | 
			
		||||
    class AvatarPartHandler : public Poco::Net::PartHandler {
 | 
			
		||||
    public:
 | 
			
		||||
        AvatarPartHandler(std::string Id, Poco::Logger &Logger, Poco::TemporaryFile &TmpFile) :
 | 
			
		||||
        AvatarPartHandler(std::string Id, Poco::Logger &Logger, std::stringstream & ofs) :
 | 
			
		||||
                Id_(std::move(Id)),
 | 
			
		||||
                Logger_(Logger),
 | 
			
		||||
                TempFile_(TmpFile){
 | 
			
		||||
                OutputStream_(ofs){
 | 
			
		||||
        }
 | 
			
		||||
        void handlePart(const Poco::Net::MessageHeader &Header, std::istream &Stream);
 | 
			
		||||
        [[nodiscard]] uint64_t Length() const { return Length_; }
 | 
			
		||||
        [[nodiscard]] std::string &Name() { return Name_; }
 | 
			
		||||
        [[nodiscard]] std::string &ContentType() { return FileType_; }
 | 
			
		||||
        [[nodiscard]] std::string FileName() const { return TempFile_.path(); }
 | 
			
		||||
 | 
			
		||||
    private:
 | 
			
		||||
        uint64_t        Length_ = 0;
 | 
			
		||||
        std::string     FileType_;
 | 
			
		||||
        std::string     Name_;
 | 
			
		||||
        std::string     Id_;
 | 
			
		||||
        Poco::Logger    &Logger_;
 | 
			
		||||
        Poco::TemporaryFile &TempFile_;
 | 
			
		||||
        std::stringstream &OutputStream_;
 | 
			
		||||
 | 
			
		||||
        inline Poco::Logger & Logger() { return Logger_; };
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    class RESTAPI_avatarHandler : public RESTAPIHandler {
 | 
			
		||||
    class RESTAPI_avatar_handler : public RESTAPIHandler {
 | 
			
		||||
    public:
 | 
			
		||||
        RESTAPI_avatarHandler(const RESTAPIHandler::BindingMap &bindings, Poco::Logger &L, RESTAPI_GenericServer &Server, bool Internal)
 | 
			
		||||
        RESTAPI_avatar_handler(const RESTAPIHandler::BindingMap &bindings, Poco::Logger &L, RESTAPI_GenericServer &Server, uint64_t TransactionId, bool Internal)
 | 
			
		||||
                : RESTAPIHandler(bindings, L,
 | 
			
		||||
                                 std::vector<std::string>{
 | 
			
		||||
                                         Poco::Net::HTTPRequest::HTTP_GET,
 | 
			
		||||
@@ -41,8 +40,9 @@ namespace OpenWifi {
 | 
			
		||||
                                         Poco::Net::HTTPRequest::HTTP_DELETE,
 | 
			
		||||
                                         Poco::Net::HTTPRequest::HTTP_OPTIONS},
 | 
			
		||||
                                         Server,
 | 
			
		||||
                                         TransactionId,
 | 
			
		||||
                                         Internal) {}
 | 
			
		||||
        static const std::list<const char *> PathName() { return std::list<const char *>{"/api/v1/avatar/{id}"}; };
 | 
			
		||||
        static auto PathName() { return std::list<std::string>{"/api/v1/avatar/{id}"}; };
 | 
			
		||||
 | 
			
		||||
        void DoGet() final;
 | 
			
		||||
        void DoPost() final;
 | 
			
		||||
@@ -51,4 +51,3 @@ namespace OpenWifi {
 | 
			
		||||
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
#endif //UCENTRALSEC_RESTAPI_AVATARHANDLER_H
 | 
			
		||||
							
								
								
									
										17
									
								
								src/RESTAPI/RESTAPI_db_helpers.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								src/RESTAPI/RESTAPI_db_helpers.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,17 @@
 | 
			
		||||
//
 | 
			
		||||
// Created by stephane bourque on 2022-01-01.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "framework/orm.h"
 | 
			
		||||
 | 
			
		||||
namespace OpenWifi {
 | 
			
		||||
 | 
			
		||||
    inline void Sanitize([[maybe_unused]] const SecurityObjects::UserInfoAndPolicy &User, SecurityObjects::UserInfo & U) {
 | 
			
		||||
        U.currentPassword.clear();
 | 
			
		||||
        U.lastPasswords.clear();
 | 
			
		||||
        U.oauthType.clear();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										38
									
								
								src/RESTAPI/RESTAPI_email_handler.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								src/RESTAPI/RESTAPI_email_handler.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,38 @@
 | 
			
		||||
//
 | 
			
		||||
// Created by stephane bourque on 2021-09-02.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#include "RESTAPI_email_handler.h"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#include "Poco/Exception.h"
 | 
			
		||||
#include "Poco/JSON/Parser.h"
 | 
			
		||||
 | 
			
		||||
#include "SMTPMailerService.h"
 | 
			
		||||
#include "framework/ow_constants.h"
 | 
			
		||||
#include "framework/MicroService.h"
 | 
			
		||||
 | 
			
		||||
namespace OpenWifi {
 | 
			
		||||
    void RESTAPI_email_handler::DoPost() {
 | 
			
		||||
        const auto & Obj = ParsedBody_;
 | 
			
		||||
        if (Obj->has("subject") &&
 | 
			
		||||
            Obj->has("from") &&
 | 
			
		||||
            Obj->has("text") &&
 | 
			
		||||
            Obj->has("recipients") &&
 | 
			
		||||
            Obj->isArray("recipients")) {
 | 
			
		||||
 | 
			
		||||
            Poco::JSON::Array::Ptr Recipients = Obj->getArray("recipients");
 | 
			
		||||
            auto Recipient = Recipients->get(0).toString();
 | 
			
		||||
            MessageAttributes Attrs;
 | 
			
		||||
            Attrs[RECIPIENT_EMAIL] = Recipient;
 | 
			
		||||
            Attrs[SUBJECT] = Obj->get("subject").toString();
 | 
			
		||||
            Attrs[TEXT] = Obj->get("text").toString();
 | 
			
		||||
            Attrs[SENDER] = Obj->get("from").toString();
 | 
			
		||||
            if(SMTPMailerService()->SendMessage(Recipient, "password_reset.txt", Attrs)) {
 | 
			
		||||
                return OK();
 | 
			
		||||
            }
 | 
			
		||||
            return ReturnStatus(Poco::Net::HTTPResponse::HTTP_SERVICE_UNAVAILABLE);
 | 
			
		||||
        }
 | 
			
		||||
        BadRequest(RESTAPI::Errors::MissingOrInvalidParameters);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -2,27 +2,24 @@
 | 
			
		||||
// Created by stephane bourque on 2021-09-02.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#ifndef OWSEC_RESTAPI_EMAIL_HANDLER_H
 | 
			
		||||
#define OWSEC_RESTAPI_EMAIL_HANDLER_H
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#include "RESTAPI_handler.h"
 | 
			
		||||
#include "framework/MicroService.h"
 | 
			
		||||
 | 
			
		||||
namespace OpenWifi {
 | 
			
		||||
    class RESTAPI_email_handler : public RESTAPIHandler {
 | 
			
		||||
    public:
 | 
			
		||||
        RESTAPI_email_handler(const RESTAPIHandler::BindingMap &bindings, Poco::Logger &L, RESTAPI_GenericServer &Server, bool Internal)
 | 
			
		||||
        RESTAPI_email_handler(const RESTAPIHandler::BindingMap &bindings, Poco::Logger &L, RESTAPI_GenericServer &Server, uint64_t TransactionId, bool Internal)
 | 
			
		||||
        : RESTAPIHandler(bindings, L,
 | 
			
		||||
                         std::vector<std::string>{Poco::Net::HTTPRequest::HTTP_POST,
 | 
			
		||||
                                                  Poco::Net::HTTPRequest::HTTP_OPTIONS},
 | 
			
		||||
                                                  Server,
 | 
			
		||||
                                                  TransactionId,
 | 
			
		||||
                                                  Internal) {}
 | 
			
		||||
        static const std::list<const char *> PathName() { return std::list<const char *>{"/api/v1/email"};}
 | 
			
		||||
        static auto PathName() { return std::list<std::string>{"/api/v1/email"};}
 | 
			
		||||
        void DoGet() final {};
 | 
			
		||||
        void DoPost() final;
 | 
			
		||||
        void DoDelete() final {};
 | 
			
		||||
        void DoPut() final {};
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#endif //OWSEC_RESTAPI_EMAIL_HANDLER_H
 | 
			
		||||
							
								
								
									
										178
									
								
								src/RESTAPI/RESTAPI_oauth2_handler.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										178
									
								
								src/RESTAPI/RESTAPI_oauth2_handler.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,178 @@
 | 
			
		||||
//
 | 
			
		||||
//	License type: BSD 3-Clause License
 | 
			
		||||
//	License copy: https://github.com/Telecominfraproject/wlan-cloud-ucentralgw/blob/master/LICENSE
 | 
			
		||||
//
 | 
			
		||||
//	Created by Stephane Bourque on 2021-03-04.
 | 
			
		||||
//	Arilia Wireless Inc.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#include "Poco/JSON/Parser.h"
 | 
			
		||||
 | 
			
		||||
#include "AuthService.h"
 | 
			
		||||
#include "RESTAPI_oauth2_handler.h"
 | 
			
		||||
#include "MFAServer.h"
 | 
			
		||||
#include "framework/ow_constants.h"
 | 
			
		||||
#include "framework/MicroService.h"
 | 
			
		||||
#include "StorageService.h"
 | 
			
		||||
#include "RESTAPI_db_helpers.h"
 | 
			
		||||
 | 
			
		||||
namespace OpenWifi {
 | 
			
		||||
 | 
			
		||||
    void RESTAPI_oauth2_handler::DoGet() {
 | 
			
		||||
        bool Expired = false, Contacted = false;
 | 
			
		||||
        if (!IsAuthorized(Expired, Contacted)) {
 | 
			
		||||
            if (Expired)
 | 
			
		||||
                return UnAuthorized(RESTAPI::Errors::EXPIRED_TOKEN);
 | 
			
		||||
            return UnAuthorized(RESTAPI::Errors::INVALID_TOKEN);
 | 
			
		||||
        }
 | 
			
		||||
        if (GetBoolParameter(RESTAPI::Protocol::ME)) {
 | 
			
		||||
            Logger_.information(fmt::format("REQUEST-ME({}): Request for {}", Request->clientAddress().toString(),
 | 
			
		||||
                                            UserInfo_.userinfo.email));
 | 
			
		||||
            Poco::JSON::Object Me;
 | 
			
		||||
            SecurityObjects::UserInfo ReturnedUser = UserInfo_.userinfo;
 | 
			
		||||
            Sanitize(UserInfo_, ReturnedUser);
 | 
			
		||||
            ReturnedUser.to_json(Me);
 | 
			
		||||
            return ReturnObject(Me);
 | 
			
		||||
        }
 | 
			
		||||
        BadRequest(RESTAPI::Errors::UnrecognizedRequest);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void RESTAPI_oauth2_handler::DoDelete() {
 | 
			
		||||
        auto Token = GetBinding(RESTAPI::Protocol::TOKEN, "");
 | 
			
		||||
        std::string SessionToken;
 | 
			
		||||
        try {
 | 
			
		||||
            Poco::Net::OAuth20Credentials Auth(*Request);
 | 
			
		||||
            if (Auth.getScheme() == "Bearer") {
 | 
			
		||||
                SessionToken = Auth.getBearerToken();
 | 
			
		||||
            }
 | 
			
		||||
        } catch (const Poco::Exception &E) {
 | 
			
		||||
            return BadRequest(RESTAPI::Errors::MissingOrInvalidParameters);
 | 
			
		||||
        }
 | 
			
		||||
        if (Token.empty() || (Token != SessionToken)) {
 | 
			
		||||
            return BadRequest(RESTAPI::Errors::MissingOrInvalidParameters);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        AuthService()->Logout(Token);
 | 
			
		||||
        return ReturnStatus(Poco::Net::HTTPResponse::HTTP_NO_CONTENT, true);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
	void RESTAPI_oauth2_handler::DoPost() {
 | 
			
		||||
 | 
			
		||||
        const auto & Obj = ParsedBody_;
 | 
			
		||||
        if(Obj == nullptr) {
 | 
			
		||||
            return BadRequest(RESTAPI::Errors::InvalidJSONDocument);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        auto userId = GetS(RESTAPI::Protocol::USERID, Obj);
 | 
			
		||||
        auto password = GetS(RESTAPI::Protocol::PASSWORD, Obj);
 | 
			
		||||
        auto newPassword = GetS(RESTAPI::Protocol::NEWPASSWORD, Obj);
 | 
			
		||||
        auto refreshToken = GetS("refreshToken", Obj);
 | 
			
		||||
        auto grant_type = GetParameter("grant_type");
 | 
			
		||||
 | 
			
		||||
        Poco::toLowerInPlace(userId);
 | 
			
		||||
 | 
			
		||||
        if(!refreshToken.empty() && grant_type == "refresh_token") {
 | 
			
		||||
            SecurityObjects::UserInfoAndPolicy UInfo;
 | 
			
		||||
            if(AuthService()->RefreshUserToken(*Request, refreshToken, UInfo)) {
 | 
			
		||||
                Poco::JSON::Object  Answer;
 | 
			
		||||
                UInfo.webtoken.to_json(Answer);
 | 
			
		||||
                return ReturnObject(Answer);
 | 
			
		||||
            } else {
 | 
			
		||||
                return UnAuthorized(RESTAPI::Errors::CANNOT_REFRESH_TOKEN);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if(GetBoolParameter(RESTAPI::Protocol::REQUIREMENTS)) {
 | 
			
		||||
            Logger_.information(fmt::format("POLICY-REQUEST({}): Request.", Request->clientAddress().toString()));
 | 
			
		||||
            Poco::JSON::Object  Answer;
 | 
			
		||||
            Answer.set(RESTAPI::Protocol::PASSWORDPATTERN, AuthService()->PasswordValidationExpression());
 | 
			
		||||
            Answer.set(RESTAPI::Protocol::ACCESSPOLICY, AuthService()->GetAccessPolicy());
 | 
			
		||||
            Answer.set(RESTAPI::Protocol::PASSWORDPOLICY, AuthService()->GetPasswordPolicy());
 | 
			
		||||
            return ReturnObject(Answer);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if(GetBoolParameter(RESTAPI::Protocol::FORGOTPASSWORD)) {
 | 
			
		||||
            SecurityObjects::UserInfo UInfo1;
 | 
			
		||||
            auto UserExists = StorageService()->UserDB().GetUserByEmail(userId,UInfo1);
 | 
			
		||||
            if(UserExists) {
 | 
			
		||||
                Logger_.information(fmt::format("FORGOTTEN-PASSWORD({}): Request for {}", Request->clientAddress().toString(), userId));
 | 
			
		||||
                SecurityObjects::ActionLink NewLink;
 | 
			
		||||
 | 
			
		||||
                NewLink.action = OpenWifi::SecurityObjects::LinkActions::FORGOT_PASSWORD;
 | 
			
		||||
                NewLink.id = MicroService::CreateUUID();
 | 
			
		||||
                NewLink.userId = UInfo1.id;
 | 
			
		||||
                NewLink.created = OpenWifi::Now();
 | 
			
		||||
                NewLink.expires = NewLink.created + (24*60*60);
 | 
			
		||||
                NewLink.userAction = true;
 | 
			
		||||
                StorageService()->ActionLinksDB().CreateAction(NewLink);
 | 
			
		||||
 | 
			
		||||
                Poco::JSON::Object ReturnObj;
 | 
			
		||||
                SecurityObjects::UserInfoAndPolicy UInfo;
 | 
			
		||||
                UInfo.webtoken.userMustChangePassword = true;
 | 
			
		||||
                UInfo.webtoken.to_json(ReturnObj);
 | 
			
		||||
                return ReturnObject(ReturnObj);
 | 
			
		||||
            } else {
 | 
			
		||||
                Poco::JSON::Object ReturnObj;
 | 
			
		||||
                SecurityObjects::UserInfoAndPolicy UInfo;
 | 
			
		||||
                UInfo.webtoken.userMustChangePassword = true;
 | 
			
		||||
                UInfo.webtoken.to_json(ReturnObj);
 | 
			
		||||
                return ReturnObject(ReturnObj);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if(GetBoolParameter(RESTAPI::Protocol::RESENDMFACODE)) {
 | 
			
		||||
            Logger_.information(fmt::format("RESEND-MFA-CODE({}): Request for {}", Request->clientAddress().toString(), userId));
 | 
			
		||||
            if(Obj->has("uuid")) {
 | 
			
		||||
                auto uuid = Obj->get("uuid").toString();
 | 
			
		||||
                if(MFAServer()->ResendCode(uuid))
 | 
			
		||||
                    return OK();
 | 
			
		||||
            }
 | 
			
		||||
            return UnAuthorized(RESTAPI::Errors::BAD_MFA_TRANSACTION);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if(GetBoolParameter(RESTAPI::Protocol::COMPLETEMFACHALLENGE,false)) {
 | 
			
		||||
            Logger_.information(fmt::format("COMPLETE-MFA-CHALLENGE({}): Request for {}", Request->clientAddress().toString(), userId));
 | 
			
		||||
            if(Obj->has("uuid")) {
 | 
			
		||||
                SecurityObjects::UserInfoAndPolicy UInfo;
 | 
			
		||||
                if(MFAServer()->CompleteMFAChallenge(Obj,UInfo)) {
 | 
			
		||||
                    Poco::JSON::Object ReturnObj;
 | 
			
		||||
                    UInfo.webtoken.to_json(ReturnObj);
 | 
			
		||||
                    return ReturnObject(ReturnObj);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            return UnAuthorized(RESTAPI::Errors::MFA_FAILURE);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        SecurityObjects::UserInfoAndPolicy UInfo;
 | 
			
		||||
        bool Expired=false;
 | 
			
		||||
        auto Code=AuthService()->Authorize(userId, password, newPassword, UInfo, Expired);
 | 
			
		||||
        if (Code==SUCCESS) {
 | 
			
		||||
            Poco::JSON::Object ReturnObj;
 | 
			
		||||
            if(AuthService()->RequiresMFA(UInfo)) {
 | 
			
		||||
                if(MFAServer()->StartMFAChallenge(UInfo, ReturnObj)) {
 | 
			
		||||
                    return ReturnObject(ReturnObj);
 | 
			
		||||
                }
 | 
			
		||||
                Logger_.warning("MFA Seems to be broken. Please fix. Disabling MFA checking for now.");
 | 
			
		||||
            }
 | 
			
		||||
            UInfo.webtoken.to_json(ReturnObj);
 | 
			
		||||
            return ReturnObject(ReturnObj);
 | 
			
		||||
        } else {
 | 
			
		||||
 | 
			
		||||
            switch(Code) {
 | 
			
		||||
                case INVALID_CREDENTIALS:
 | 
			
		||||
                    return UnAuthorized(RESTAPI::Errors::INVALID_CREDENTIALS);
 | 
			
		||||
                case PASSWORD_INVALID:
 | 
			
		||||
                    return UnAuthorized(RESTAPI::Errors::PASSWORD_INVALID);
 | 
			
		||||
                case PASSWORD_ALREADY_USED:
 | 
			
		||||
                    return UnAuthorized(RESTAPI::Errors::PASSWORD_ALREADY_USED);
 | 
			
		||||
                case USERNAME_PENDING_VERIFICATION:
 | 
			
		||||
                    return UnAuthorized(RESTAPI::Errors::USERNAME_PENDING_VERIFICATION);
 | 
			
		||||
                case PASSWORD_CHANGE_REQUIRED:
 | 
			
		||||
                    return UnAuthorized(RESTAPI::Errors::PASSWORD_CHANGE_REQUIRED);
 | 
			
		||||
                default:
 | 
			
		||||
                    return UnAuthorized(RESTAPI::Errors::INVALID_CREDENTIALS);
 | 
			
		||||
            }
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -6,27 +6,27 @@
 | 
			
		||||
//	Arilia Wireless Inc.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#ifndef UCENTRAL_RESTAPI_OAUTH2HANDLER_H
 | 
			
		||||
#define UCENTRAL_RESTAPI_OAUTH2HANDLER_H
 | 
			
		||||
 | 
			
		||||
#include "RESTAPI_handler.h"
 | 
			
		||||
#pragma once
 | 
			
		||||
#include "framework/MicroService.h"
 | 
			
		||||
 | 
			
		||||
namespace OpenWifi {
 | 
			
		||||
	class RESTAPI_oauth2Handler : public RESTAPIHandler {
 | 
			
		||||
	class RESTAPI_oauth2_handler : public RESTAPIHandler {
 | 
			
		||||
	  public:
 | 
			
		||||
	    RESTAPI_oauth2Handler(const RESTAPIHandler::BindingMap &bindings, Poco::Logger &L, RESTAPI_GenericServer &Server, bool Internal)
 | 
			
		||||
	    RESTAPI_oauth2_handler(const RESTAPIHandler::BindingMap &bindings, Poco::Logger &L, RESTAPI_GenericServer &Server, uint64_t TransactionId, bool Internal)
 | 
			
		||||
			: RESTAPIHandler(bindings, L,
 | 
			
		||||
							 std::vector<std::string>{Poco::Net::HTTPRequest::HTTP_POST,
 | 
			
		||||
													  Poco::Net::HTTPRequest::HTTP_DELETE,
 | 
			
		||||
                                                      Poco::Net::HTTPRequest::HTTP_GET,
 | 
			
		||||
													  Poco::Net::HTTPRequest::HTTP_OPTIONS},
 | 
			
		||||
													  Server,
 | 
			
		||||
													  Internal, false) {}
 | 
			
		||||
		static const std::list<const char *> PathName() { return std::list<const char *>{"/api/v1/oauth2/{token}","/api/v1/oauth2"}; };
 | 
			
		||||
                                                      TransactionId,
 | 
			
		||||
													  Internal, false, true , RateLimit{.Interval=1000,.MaxCalls=10}) {}
 | 
			
		||||
        static auto PathName() { return std::list<std::string>{"/api/v1/oauth2/{token}","/api/v1/oauth2"}; };
 | 
			
		||||
		void DoGet() final;
 | 
			
		||||
		void DoPost() final;
 | 
			
		||||
		void DoDelete() final;
 | 
			
		||||
		void DoPut() final {};
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
#endif //UCENTRAL_RESTAPI_OAUTH2HANDLER_H
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										36
									
								
								src/RESTAPI/RESTAPI_preferences.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								src/RESTAPI/RESTAPI_preferences.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,36 @@
 | 
			
		||||
//
 | 
			
		||||
// Created by stephane bourque on 2021-11-16.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#include "RESTAPI_preferences.h"
 | 
			
		||||
#include "StorageService.h"
 | 
			
		||||
 | 
			
		||||
namespace OpenWifi {
 | 
			
		||||
 | 
			
		||||
    void RESTAPI_preferences::DoGet() {
 | 
			
		||||
        SecurityObjects::Preferences    P;
 | 
			
		||||
        Poco::JSON::Object  Answer;
 | 
			
		||||
        StorageService()->PreferencesDB().GetPreferences(UserInfo_.userinfo.id, P);
 | 
			
		||||
        P.to_json(Answer);
 | 
			
		||||
        ReturnObject(Answer);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void RESTAPI_preferences::DoPut() {
 | 
			
		||||
 | 
			
		||||
        SecurityObjects::Preferences    P;
 | 
			
		||||
 | 
			
		||||
        const auto & RawObject = ParsedBody_;
 | 
			
		||||
        if(!P.from_json(RawObject)) {
 | 
			
		||||
            return BadRequest(RESTAPI::Errors::InvalidJSONDocument);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        P.id = UserInfo_.userinfo.id;
 | 
			
		||||
        P.modified = OpenWifi::Now();
 | 
			
		||||
        StorageService()->PreferencesDB().SetPreferences(P);
 | 
			
		||||
 | 
			
		||||
        Poco::JSON::Object  Answer;
 | 
			
		||||
        P.to_json(Answer);
 | 
			
		||||
        ReturnObject(Answer);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										27
									
								
								src/RESTAPI/RESTAPI_preferences.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								src/RESTAPI/RESTAPI_preferences.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,27 @@
 | 
			
		||||
//
 | 
			
		||||
// Created by stephane bourque on 2021-11-16.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "framework/MicroService.h"
 | 
			
		||||
 | 
			
		||||
namespace OpenWifi {
 | 
			
		||||
    class RESTAPI_preferences : public RESTAPIHandler {
 | 
			
		||||
    public:
 | 
			
		||||
        RESTAPI_preferences(const RESTAPIHandler::BindingMap &bindings, Poco::Logger &L, RESTAPI_GenericServer &Server, uint64_t TransactionId, bool Internal)
 | 
			
		||||
        : RESTAPIHandler(bindings, L,
 | 
			
		||||
                         std::vector<std::string>{
 | 
			
		||||
            Poco::Net::HTTPRequest::HTTP_GET,
 | 
			
		||||
            Poco::Net::HTTPRequest::HTTP_PUT,
 | 
			
		||||
            Poco::Net::HTTPRequest::HTTP_OPTIONS},
 | 
			
		||||
            Server,
 | 
			
		||||
            TransactionId,
 | 
			
		||||
            Internal) {}
 | 
			
		||||
        static auto PathName() { return std::list<std::string>{"/api/v1/preferences"}; };
 | 
			
		||||
        void DoGet() final;
 | 
			
		||||
        void DoPut() final;
 | 
			
		||||
        void DoPost() final {};
 | 
			
		||||
        void DoDelete() final {};
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										87
									
								
								src/RESTAPI/RESTAPI_routers.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										87
									
								
								src/RESTAPI/RESTAPI_routers.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,87 @@
 | 
			
		||||
//
 | 
			
		||||
// Created by stephane bourque on 2021-10-23.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#include "framework/MicroService.h"
 | 
			
		||||
 | 
			
		||||
#include "RESTAPI/RESTAPI_oauth2_handler.h"
 | 
			
		||||
#include "RESTAPI/RESTAPI_user_handler.h"
 | 
			
		||||
#include "RESTAPI/RESTAPI_users_handler.h"
 | 
			
		||||
#include "RESTAPI/RESTAPI_action_links.h"
 | 
			
		||||
#include "RESTAPI/RESTAPI_system_endpoints_handler.h"
 | 
			
		||||
#include "RESTAPI/RESTAPI_asset_server.h"
 | 
			
		||||
#include "RESTAPI/RESTAPI_avatar_handler.h"
 | 
			
		||||
#include "RESTAPI/RESTAPI_subavatar_handler.h"
 | 
			
		||||
#include "RESTAPI/RESTAPI_email_handler.h"
 | 
			
		||||
#include "RESTAPI/RESTAPI_sms_handler.h"
 | 
			
		||||
#include "RESTAPI/RESTAPI_validate_token_handler.h"
 | 
			
		||||
#include "RESTAPI/RESTAPI_preferences.h"
 | 
			
		||||
#include "RESTAPI/RESTAPI_subpreferences.h"
 | 
			
		||||
#include "RESTAPI/RESTAPI_suboauth2_handler.h"
 | 
			
		||||
#include "RESTAPI/RESTAPI_subuser_handler.h"
 | 
			
		||||
#include "RESTAPI/RESTAPI_subusers_handler.h"
 | 
			
		||||
#include "RESTAPI/RESTAPI_validate_sub_token_handler.h"
 | 
			
		||||
#include "RESTAPI/RESTAPI_submfa_handler.h"
 | 
			
		||||
#include "RESTAPI/RESTAPI_totp_handler.h"
 | 
			
		||||
#include "RESTAPI/RESTAPI_subtotp_handler.h"
 | 
			
		||||
#include "RESTAPI/RESTAPI_signup_handler.h"
 | 
			
		||||
 | 
			
		||||
namespace OpenWifi {
 | 
			
		||||
 | 
			
		||||
    Poco::Net::HTTPRequestHandler * RESTAPI_ExtRouter(const std::string &Path, RESTAPIHandler::BindingMap &Bindings,
 | 
			
		||||
                                                            Poco::Logger & L, RESTAPI_GenericServer & S, uint64_t TransactionId) {
 | 
			
		||||
        return RESTAPI_Router<
 | 
			
		||||
            RESTAPI_oauth2_handler,
 | 
			
		||||
            RESTAPI_user_handler,
 | 
			
		||||
            RESTAPI_users_handler,
 | 
			
		||||
            RESTAPI_system_command,
 | 
			
		||||
            RESTAPI_asset_server,
 | 
			
		||||
            RESTAPI_system_endpoints_handler,
 | 
			
		||||
            RESTAPI_action_links,
 | 
			
		||||
            RESTAPI_avatar_handler,
 | 
			
		||||
            RESTAPI_subavatar_handler,
 | 
			
		||||
            RESTAPI_email_handler,
 | 
			
		||||
            RESTAPI_sms_handler,
 | 
			
		||||
            RESTAPI_preferences,
 | 
			
		||||
            RESTAPI_subpreferences,
 | 
			
		||||
            RESTAPI_suboauth2_handler,
 | 
			
		||||
            RESTAPI_subuser_handler,
 | 
			
		||||
            RESTAPI_subusers_handler,
 | 
			
		||||
            RESTAPI_submfa_handler,
 | 
			
		||||
            RESTAPI_totp_handler,
 | 
			
		||||
            RESTAPI_subtotp_handler,
 | 
			
		||||
            RESTAPI_signup_handler,
 | 
			
		||||
            RESTAPI_validate_sub_token_handler,
 | 
			
		||||
            RESTAPI_validate_token_handler
 | 
			
		||||
        >(Path, Bindings, L, S,TransactionId);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Poco::Net::HTTPRequestHandler * RESTAPI_IntRouter(const std::string &Path, RESTAPIHandler::BindingMap &Bindings,
 | 
			
		||||
                                                            Poco::Logger & L, RESTAPI_GenericServer & S, uint64_t TransactionId) {
 | 
			
		||||
 | 
			
		||||
        return RESTAPI_Router_I<
 | 
			
		||||
            RESTAPI_oauth2_handler,
 | 
			
		||||
            RESTAPI_user_handler,
 | 
			
		||||
            RESTAPI_users_handler,
 | 
			
		||||
            RESTAPI_system_command,
 | 
			
		||||
            RESTAPI_asset_server,
 | 
			
		||||
            RESTAPI_system_endpoints_handler,
 | 
			
		||||
            RESTAPI_action_links,
 | 
			
		||||
            RESTAPI_avatar_handler,
 | 
			
		||||
            RESTAPI_subavatar_handler,
 | 
			
		||||
            RESTAPI_email_handler,
 | 
			
		||||
            RESTAPI_sms_handler,
 | 
			
		||||
            RESTAPI_preferences,
 | 
			
		||||
            RESTAPI_subpreferences,
 | 
			
		||||
            RESTAPI_suboauth2_handler,
 | 
			
		||||
            RESTAPI_subuser_handler,
 | 
			
		||||
            RESTAPI_subusers_handler,
 | 
			
		||||
            RESTAPI_submfa_handler,
 | 
			
		||||
            RESTAPI_totp_handler,
 | 
			
		||||
            RESTAPI_subtotp_handler,
 | 
			
		||||
            RESTAPI_validate_sub_token_handler,
 | 
			
		||||
            RESTAPI_validate_token_handler,
 | 
			
		||||
            RESTAPI_signup_handler
 | 
			
		||||
        >(Path, Bindings, L, S, TransactionId);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										73
									
								
								src/RESTAPI/RESTAPI_signup_handler.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										73
									
								
								src/RESTAPI/RESTAPI_signup_handler.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,73 @@
 | 
			
		||||
//
 | 
			
		||||
// Created by stephane bourque on 2022-02-20.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#include "RESTAPI_signup_handler.h"
 | 
			
		||||
#include "StorageService.h"
 | 
			
		||||
#include "RESTObjects/RESTAPI_SecurityObjects.h"
 | 
			
		||||
 | 
			
		||||
#define __DBG__ std::cout << __LINE__ << std::endl;
 | 
			
		||||
namespace OpenWifi {
 | 
			
		||||
 | 
			
		||||
    void RESTAPI_signup_handler::DoPost() {
 | 
			
		||||
        auto UserName = GetParameter("email");
 | 
			
		||||
        auto signupUUID = GetParameter("signupUUID");
 | 
			
		||||
        auto owner = GetParameter("owner");
 | 
			
		||||
        if(UserName.empty() || signupUUID.empty() || owner.empty()) {
 | 
			
		||||
            Logger().error("Signup requires: email, signupUUID, and owner.");
 | 
			
		||||
            return BadRequest(RESTAPI::Errors::MissingOrInvalidParameters);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if(!Utils::ValidEMailAddress(UserName)) {
 | 
			
		||||
            return BadRequest(RESTAPI::Errors::InvalidEmailAddress);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Do we already exist? Can only signup once...
 | 
			
		||||
        SecurityObjects::UserInfo   Existing;
 | 
			
		||||
        if(StorageService()->SubDB().GetUserByEmail(UserName,Existing)) {
 | 
			
		||||
            if(Existing.signingUp.empty()) {
 | 
			
		||||
                return BadRequest(RESTAPI::Errors::SignupAlreadySigned);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if(Existing.waitingForEmailCheck) {
 | 
			
		||||
                return BadRequest(RESTAPI::Errors::SignupEmailCheck);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return BadRequest(RESTAPI::Errors::SignupWaitingForDevice);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        SecurityObjects::UserInfo   NewSub;
 | 
			
		||||
        NewSub.signingUp = signupUUID;
 | 
			
		||||
        NewSub.waitingForEmailCheck = true;
 | 
			
		||||
        NewSub.name = UserName;
 | 
			
		||||
        NewSub.modified = OpenWifi::Now();
 | 
			
		||||
        NewSub.creationDate = OpenWifi::Now();
 | 
			
		||||
        NewSub.id = MicroService::instance().CreateUUID();
 | 
			
		||||
        NewSub.email = UserName;
 | 
			
		||||
        NewSub.userRole = SecurityObjects::SUBSCRIBER;
 | 
			
		||||
        NewSub.changePassword = true;
 | 
			
		||||
        NewSub.owner = owner;
 | 
			
		||||
 | 
			
		||||
        StorageService()->SubDB().CreateRecord(NewSub);
 | 
			
		||||
 | 
			
		||||
        Logger_.information(fmt::format("SIGNUP-PASSWORD({}): Request for {}", Request->clientAddress().toString(), UserName));
 | 
			
		||||
        SecurityObjects::ActionLink NewLink;
 | 
			
		||||
 | 
			
		||||
        NewLink.action = OpenWifi::SecurityObjects::LinkActions::SUB_SIGNUP;
 | 
			
		||||
        NewLink.id = MicroService::CreateUUID();
 | 
			
		||||
        NewLink.userId = NewSub.id;
 | 
			
		||||
        NewLink.created = OpenWifi::Now();
 | 
			
		||||
        NewLink.expires = NewLink.created + (1*60*60);  // 1 hour
 | 
			
		||||
        NewLink.userAction = false;
 | 
			
		||||
        StorageService()->ActionLinksDB().CreateAction(NewLink);
 | 
			
		||||
 | 
			
		||||
        Poco::JSON::Object  Answer;
 | 
			
		||||
        NewSub.to_json(Answer);
 | 
			
		||||
        return ReturnObject(Answer);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void RESTAPI_signup_handler::DoPut() {
 | 
			
		||||
        // TODO
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										39
									
								
								src/RESTAPI/RESTAPI_signup_handler.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								src/RESTAPI/RESTAPI_signup_handler.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,39 @@
 | 
			
		||||
//
 | 
			
		||||
// Created by stephane bourque on 2022-02-20.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "framework/MicroService.h"
 | 
			
		||||
 | 
			
		||||
namespace OpenWifi {
 | 
			
		||||
    class RESTAPI_signup_handler : public RESTAPIHandler {
 | 
			
		||||
    public:
 | 
			
		||||
        RESTAPI_signup_handler(const RESTAPIHandler::BindingMap &bindings, Poco::Logger &L, RESTAPI_GenericServer & Server, uint64_t TransactionId, bool Internal)
 | 
			
		||||
                : RESTAPIHandler(bindings, L,
 | 
			
		||||
                                 std::vector<std::string>{
 | 
			
		||||
                                         Poco::Net::HTTPRequest::HTTP_POST,
 | 
			
		||||
                                         Poco::Net::HTTPRequest::HTTP_OPTIONS,
 | 
			
		||||
                                         Poco::Net::HTTPRequest::HTTP_PUT},
 | 
			
		||||
                                 Server,
 | 
			
		||||
                                 TransactionId,
 | 
			
		||||
                                 Internal, false, true ){}
 | 
			
		||||
 | 
			
		||||
        static auto PathName() { return std::list<std::string>{"/api/v1/signup"}; };
 | 
			
		||||
 | 
			
		||||
/*        inline bool RoleIsAuthorized(std::string & Reason) {
 | 
			
		||||
            if(UserInfo_.userinfo.userRole != SecurityObjects::USER_ROLE::SUBSCRIBER) {
 | 
			
		||||
                Reason = "User must be a subscriber";
 | 
			
		||||
                return false;
 | 
			
		||||
            }
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
*/
 | 
			
		||||
        void DoGet() final {};
 | 
			
		||||
        void DoPost() final;
 | 
			
		||||
        void DoPut() final ;
 | 
			
		||||
        void DoDelete() final {};
 | 
			
		||||
    private:
 | 
			
		||||
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										55
									
								
								src/RESTAPI/RESTAPI_sms_handler.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								src/RESTAPI/RESTAPI_sms_handler.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,55 @@
 | 
			
		||||
//
 | 
			
		||||
// Created by stephane bourque on 2021-10-09.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#include "RESTAPI_sms_handler.h"
 | 
			
		||||
#include "SMSSender.h"
 | 
			
		||||
#include "framework/ow_constants.h"
 | 
			
		||||
#include "framework/MicroService.h"
 | 
			
		||||
 | 
			
		||||
namespace OpenWifi {
 | 
			
		||||
 | 
			
		||||
    void OpenWifi::RESTAPI_sms_handler::DoPost() {
 | 
			
		||||
        const auto &Obj = ParsedBody_;
 | 
			
		||||
 | 
			
		||||
        std::string Arg;
 | 
			
		||||
        if(HasParameter("validateNumber",Arg) && Arg=="true" && Obj->has("to")) {
 | 
			
		||||
            auto Number = Obj->get("to").toString();
 | 
			
		||||
            if(SMSSender()->StartValidation(Number, UserInfo_.userinfo.email)) {
 | 
			
		||||
                return OK();
 | 
			
		||||
            }
 | 
			
		||||
            return BadRequest(RESTAPI::Errors::SMSCouldNotBeSentRetry);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        std::string Code;
 | 
			
		||||
        if( HasParameter("completeValidation",Arg) &&
 | 
			
		||||
            Arg=="true" &&
 | 
			
		||||
            HasParameter("validationCode", Code) &&
 | 
			
		||||
            Obj->has("to")) {
 | 
			
		||||
            auto Number = Obj->get("to").toString();
 | 
			
		||||
            if(SMSSender()->CompleteValidation(Number, Code, UserInfo_.userinfo.email)) {
 | 
			
		||||
                return OK();
 | 
			
		||||
            }
 | 
			
		||||
            return BadRequest(RESTAPI::Errors::SMSCouldNotValidate);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if( UserInfo_.userinfo.userRole!=SecurityObjects::ROOT &&
 | 
			
		||||
            UserInfo_.userinfo.userRole!=SecurityObjects::PARTNER &&
 | 
			
		||||
            UserInfo_.userinfo.userRole!=SecurityObjects::ADMIN) {
 | 
			
		||||
            return UnAuthorized(RESTAPI::Errors::ACCESS_DENIED);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (Obj->has("to") &&
 | 
			
		||||
            Obj->has("text")) {
 | 
			
		||||
 | 
			
		||||
            std::string PhoneNumber = Obj->get("to").toString();
 | 
			
		||||
            std::string Text = Obj->get("text").toString();
 | 
			
		||||
            if(SMSSender()->Send(PhoneNumber, Text))
 | 
			
		||||
                return OK();
 | 
			
		||||
 | 
			
		||||
            return InternalError(RESTAPI::Errors::SMSCouldNotBeSentRetry);
 | 
			
		||||
        }
 | 
			
		||||
        BadRequest(RESTAPI::Errors::MissingOrInvalidParameters);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										25
									
								
								src/RESTAPI/RESTAPI_sms_handler.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								src/RESTAPI/RESTAPI_sms_handler.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,25 @@
 | 
			
		||||
//
 | 
			
		||||
// Created by stephane bourque on 2021-10-09.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "framework/MicroService.h"
 | 
			
		||||
 | 
			
		||||
namespace OpenWifi {
 | 
			
		||||
    class RESTAPI_sms_handler : public RESTAPIHandler {
 | 
			
		||||
    public:
 | 
			
		||||
        RESTAPI_sms_handler(const RESTAPIHandler::BindingMap &bindings, Poco::Logger &L, RESTAPI_GenericServer &Server, uint64_t TransactionId, bool Internal)
 | 
			
		||||
        : RESTAPIHandler(bindings, L,
 | 
			
		||||
                         std::vector<std::string>{Poco::Net::HTTPRequest::HTTP_POST,
 | 
			
		||||
                                                  Poco::Net::HTTPRequest::HTTP_OPTIONS},
 | 
			
		||||
                                                  Server,
 | 
			
		||||
                                                  TransactionId,
 | 
			
		||||
                                                  Internal) {}
 | 
			
		||||
        static auto PathName() { return std::list<std::string>{"/api/v1/sms"};}
 | 
			
		||||
        void DoGet() final {};
 | 
			
		||||
        void DoPost() final;
 | 
			
		||||
        void DoDelete() final {};
 | 
			
		||||
        void DoPut() final {};
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										81
									
								
								src/RESTAPI/RESTAPI_subavatar_handler.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										81
									
								
								src/RESTAPI/RESTAPI_subavatar_handler.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,81 @@
 | 
			
		||||
//
 | 
			
		||||
// Created by stephane bourque on 2021-07-15.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#include <fstream>
 | 
			
		||||
#include <iostream>
 | 
			
		||||
 | 
			
		||||
#include "RESTAPI_subavatar_handler.h"
 | 
			
		||||
#include "StorageService.h"
 | 
			
		||||
#include "Poco/Net/HTMLForm.h"
 | 
			
		||||
#include "framework/MicroService.h"
 | 
			
		||||
 | 
			
		||||
namespace OpenWifi {
 | 
			
		||||
 | 
			
		||||
    void SubAvatarPartHandler::handlePart(const Poco::Net::MessageHeader &Header, std::istream &Stream) {
 | 
			
		||||
        FileType_ = Header.get(RESTAPI::Protocol::CONTENTTYPE, RESTAPI::Protocol::UNSPECIFIED);
 | 
			
		||||
        if (Header.has(RESTAPI::Protocol::CONTENTDISPOSITION)) {
 | 
			
		||||
            std::string Disposition;
 | 
			
		||||
            Poco::Net::NameValueCollection Parameters;
 | 
			
		||||
            Poco::Net::MessageHeader::splitParameters(Header[RESTAPI::Protocol::CONTENTDISPOSITION], Disposition, Parameters);
 | 
			
		||||
            Name_ = Parameters.get(RESTAPI::Protocol::NAME, RESTAPI::Protocol::UNNAMED);
 | 
			
		||||
        }
 | 
			
		||||
        Poco::CountingInputStream InputStream(Stream);
 | 
			
		||||
        Poco::StreamCopier::copyStream(InputStream, OutputStream_);
 | 
			
		||||
        Length_ = OutputStream_.str().size();
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    void RESTAPI_subavatar_handler::DoPost() {
 | 
			
		||||
        std::string Id = UserInfo_.userinfo.id;
 | 
			
		||||
        SecurityObjects::UserInfo UInfo;
 | 
			
		||||
 | 
			
		||||
        std::stringstream SS;
 | 
			
		||||
        SubAvatarPartHandler partHandler(Id, Logger_, SS);
 | 
			
		||||
        Poco::Net::HTMLForm form(*Request, Request->stream(), partHandler);
 | 
			
		||||
        Poco::JSON::Object Answer;
 | 
			
		||||
 | 
			
		||||
        if (!partHandler.Name().empty() && partHandler.Length()< MicroService::instance().ConfigGetInt("openwifi.avatar.maxsize",2000000)) {
 | 
			
		||||
            Answer.set(RESTAPI::Protocol::AVATARID, Id);
 | 
			
		||||
            Answer.set(RESTAPI::Protocol::ERRORCODE, 0);
 | 
			
		||||
            Logger_.information(fmt::format("Uploaded avatar: {} Type: {}", partHandler.Name(), partHandler.ContentType()));
 | 
			
		||||
            StorageService()->SubAvatarDB().SetAvatar(UserInfo_.userinfo.email,
 | 
			
		||||
                                 Id, SS.str(), partHandler.ContentType(), partHandler.Name());
 | 
			
		||||
            StorageService()->SubDB().SetAvatar(Id,"1");
 | 
			
		||||
            Logger().information(fmt::format("Adding avatar for {}",UserInfo_.userinfo.email));
 | 
			
		||||
        } else {
 | 
			
		||||
            Answer.set(RESTAPI::Protocol::AVATARID, Id);
 | 
			
		||||
            Answer.set(RESTAPI::Protocol::ERRORCODE, 13);
 | 
			
		||||
            Answer.set(RESTAPI::Protocol::ERRORTEXT, "Avatar upload could not complete.");
 | 
			
		||||
        }
 | 
			
		||||
        ReturnObject(Answer);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void RESTAPI_subavatar_handler::DoGet() {
 | 
			
		||||
        std::string Id = GetBinding(RESTAPI::Protocol::ID, "");
 | 
			
		||||
        if (Id.empty()) {
 | 
			
		||||
            return NotFound();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        std::string Type, Name, AvatarContent;
 | 
			
		||||
        if (!StorageService()->SubAvatarDB().GetAvatar(UserInfo_.userinfo.email, Id, AvatarContent, Type, Name)) {
 | 
			
		||||
            return NotFound();
 | 
			
		||||
        }
 | 
			
		||||
        Logger().information(fmt::format("Retrieving avatar for {}",UserInfo_.userinfo.email));
 | 
			
		||||
        return SendFileContent(AvatarContent, Type, Name);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void RESTAPI_subavatar_handler::DoDelete() {
 | 
			
		||||
        std::string Id = GetBinding(RESTAPI::Protocol::ID, "");
 | 
			
		||||
 | 
			
		||||
        if(UserInfo_.userinfo.userRole!=SecurityObjects::ROOT && Id!=UserInfo_.userinfo.id) {
 | 
			
		||||
            return UnAuthorized(RESTAPI::Errors::ACCESS_DENIED);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (!StorageService()->SubAvatarDB().DeleteAvatar(UserInfo_.userinfo.email, Id)) {
 | 
			
		||||
            return NotFound();
 | 
			
		||||
        }
 | 
			
		||||
        Logger().information(fmt::format("Deleted avatar for {}",UserInfo_.userinfo.email));
 | 
			
		||||
        StorageService()->SubDB().SetAvatar(Id,"");
 | 
			
		||||
        OK();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										53
									
								
								src/RESTAPI/RESTAPI_subavatar_handler.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								src/RESTAPI/RESTAPI_subavatar_handler.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,53 @@
 | 
			
		||||
//
 | 
			
		||||
// Created by stephane bourque on 2021-07-15.
 | 
			
		||||
//
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "framework/MicroService.h"
 | 
			
		||||
 | 
			
		||||
namespace OpenWifi {
 | 
			
		||||
 | 
			
		||||
    class SubAvatarPartHandler : public Poco::Net::PartHandler {
 | 
			
		||||
    public:
 | 
			
		||||
        SubAvatarPartHandler(std::string Id, Poco::Logger &Logger, std::stringstream & ofs) :
 | 
			
		||||
                Id_(std::move(Id)),
 | 
			
		||||
                Logger_(Logger),
 | 
			
		||||
                OutputStream_(ofs){
 | 
			
		||||
        }
 | 
			
		||||
        void handlePart(const Poco::Net::MessageHeader &Header, std::istream &Stream);
 | 
			
		||||
        [[nodiscard]] uint64_t Length() const { return Length_; }
 | 
			
		||||
        [[nodiscard]] std::string &Name() { return Name_; }
 | 
			
		||||
        [[nodiscard]] std::string &ContentType() { return FileType_; }
 | 
			
		||||
 | 
			
		||||
    private:
 | 
			
		||||
        uint64_t        Length_ = 0;
 | 
			
		||||
        std::string     FileType_;
 | 
			
		||||
        std::string     Name_;
 | 
			
		||||
        std::string     Id_;
 | 
			
		||||
        Poco::Logger    &Logger_;
 | 
			
		||||
        std::stringstream &OutputStream_;
 | 
			
		||||
 | 
			
		||||
        inline Poco::Logger & Logger() { return Logger_; }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    class RESTAPI_subavatar_handler : public RESTAPIHandler {
 | 
			
		||||
    public:
 | 
			
		||||
        RESTAPI_subavatar_handler(const RESTAPIHandler::BindingMap &bindings, Poco::Logger &L, RESTAPI_GenericServer &Server, uint64_t TransactionId, bool Internal)
 | 
			
		||||
                : RESTAPIHandler(bindings, L,
 | 
			
		||||
                                 std::vector<std::string>{
 | 
			
		||||
                                         Poco::Net::HTTPRequest::HTTP_GET,
 | 
			
		||||
                                         Poco::Net::HTTPRequest::HTTP_POST,
 | 
			
		||||
                                         Poco::Net::HTTPRequest::HTTP_DELETE,
 | 
			
		||||
                                         Poco::Net::HTTPRequest::HTTP_OPTIONS},
 | 
			
		||||
                                         Server,
 | 
			
		||||
                                         TransactionId,
 | 
			
		||||
                                         Internal) {}
 | 
			
		||||
        static auto PathName() { return std::list<std::string>{"/api/v1/subavatar/{id}"}; };
 | 
			
		||||
 | 
			
		||||
        void DoGet() final;
 | 
			
		||||
        void DoPost() final;
 | 
			
		||||
        void DoDelete() final;
 | 
			
		||||
        void DoPut() final {};
 | 
			
		||||
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										128
									
								
								src/RESTAPI/RESTAPI_submfa_handler.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										128
									
								
								src/RESTAPI/RESTAPI_submfa_handler.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,128 @@
 | 
			
		||||
//
 | 
			
		||||
// Created by stephane bourque on 2021-12-01.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#include "RESTAPI_submfa_handler.h"
 | 
			
		||||
#include "StorageService.h"
 | 
			
		||||
#include "SMSSender.h"
 | 
			
		||||
 | 
			
		||||
namespace OpenWifi {
 | 
			
		||||
 | 
			
		||||
    void RESTAPI_submfa_handler::DoGet() {
 | 
			
		||||
        SecurityObjects::UserInfo   User;
 | 
			
		||||
 | 
			
		||||
        if (StorageService()->SubDB().GetUserById(UserInfo_.userinfo.id,User)) {
 | 
			
		||||
            Poco::JSON::Object              Answer;
 | 
			
		||||
            SecurityObjects::SubMfaConfig   MFC;
 | 
			
		||||
 | 
			
		||||
            MFC.id = User.id;
 | 
			
		||||
            if(User.userTypeProprietaryInfo.mfa.enabled) {
 | 
			
		||||
                if(User.userTypeProprietaryInfo.mfa.method == "sms") {
 | 
			
		||||
                    MFC.sms = User.userTypeProprietaryInfo.mobiles[0].number;
 | 
			
		||||
                    MFC.type = "sms";
 | 
			
		||||
                } else if(User.userTypeProprietaryInfo.mfa.method == "email") {
 | 
			
		||||
                    MFC.email = User.email;
 | 
			
		||||
                    MFC.type = "email";
 | 
			
		||||
                }
 | 
			
		||||
            } else {
 | 
			
		||||
                MFC.type = "disabled";
 | 
			
		||||
            }
 | 
			
		||||
            MFC.to_json(Answer);
 | 
			
		||||
            return ReturnObject(Answer);
 | 
			
		||||
        }
 | 
			
		||||
        NotFound();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void RESTAPI_submfa_handler::DoPut() {
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            const auto & Body = ParsedBody_;
 | 
			
		||||
 | 
			
		||||
            SecurityObjects::SubMfaConfig MFC;
 | 
			
		||||
 | 
			
		||||
            if (!MFC.from_json(Body)) {
 | 
			
		||||
                return BadRequest(RESTAPI::Errors::InvalidJSONDocument);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (MFC.type == "disabled") {
 | 
			
		||||
                SecurityObjects::UserInfo User;
 | 
			
		||||
                StorageService()->SubDB().GetUserById(UserInfo_.userinfo.id, User);
 | 
			
		||||
                User.userTypeProprietaryInfo.mfa.enabled = false;
 | 
			
		||||
                StorageService()->SubDB().UpdateUserInfo(UserInfo_.userinfo.email, UserInfo_.userinfo.id, User);
 | 
			
		||||
 | 
			
		||||
                Poco::JSON::Object Answer;
 | 
			
		||||
                MFC.to_json(Answer);
 | 
			
		||||
                return ReturnObject(Answer);
 | 
			
		||||
            } else if (MFC.type == "email") {
 | 
			
		||||
                SecurityObjects::UserInfo User;
 | 
			
		||||
 | 
			
		||||
                StorageService()->SubDB().GetUserById(UserInfo_.userinfo.id, User);
 | 
			
		||||
                User.userTypeProprietaryInfo.mfa.enabled = true;
 | 
			
		||||
                User.userTypeProprietaryInfo.mfa.method = "email";
 | 
			
		||||
                StorageService()->SubDB().UpdateUserInfo(UserInfo_.userinfo.email, UserInfo_.userinfo.id, User);
 | 
			
		||||
 | 
			
		||||
                MFC.sms = MFC.sms;
 | 
			
		||||
                MFC.type = "email";
 | 
			
		||||
                MFC.email = UserInfo_.userinfo.email;
 | 
			
		||||
                MFC.id = MicroService::instance().CreateUUID();
 | 
			
		||||
 | 
			
		||||
                Poco::JSON::Object Answer;
 | 
			
		||||
                MFC.to_json(Answer);
 | 
			
		||||
                return ReturnObject(Answer);
 | 
			
		||||
 | 
			
		||||
            } else if (MFC.type == "sms") {
 | 
			
		||||
                if (GetBoolParameter("startValidation", false)) {
 | 
			
		||||
                    if (MFC.sms.empty()) {
 | 
			
		||||
                        return BadRequest(RESTAPI::Errors::SMSMissingPhoneNumber);
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    if (SMSSender()->StartValidation(MFC.sms, UserInfo_.userinfo.email)) {
 | 
			
		||||
                        return OK();
 | 
			
		||||
                    } else {
 | 
			
		||||
                        return InternalError(RESTAPI::Errors::SMSTryLater);
 | 
			
		||||
                    }
 | 
			
		||||
                } else if (GetBoolParameter("completeValidation", false)) {
 | 
			
		||||
                    auto ChallengeCode = GetParameter("challengeCode", "");
 | 
			
		||||
                    if (ChallengeCode.empty()) {
 | 
			
		||||
                        return BadRequest(RESTAPI::Errors::SMSMissingChallenge);
 | 
			
		||||
                    }
 | 
			
		||||
                    if (MFC.sms.empty()) {
 | 
			
		||||
                        return BadRequest(RESTAPI::Errors::SMSMissingPhoneNumber);
 | 
			
		||||
                    }
 | 
			
		||||
                    if (SMSSender()->CompleteValidation(MFC.sms, ChallengeCode, UserInfo_.userinfo.email)) {
 | 
			
		||||
                        SecurityObjects::UserInfo User;
 | 
			
		||||
 | 
			
		||||
                        StorageService()->SubDB().GetUserById(UserInfo_.userinfo.id, User);
 | 
			
		||||
                        User.userTypeProprietaryInfo.mfa.enabled = true;
 | 
			
		||||
                        User.userTypeProprietaryInfo.mfa.method = "sms";
 | 
			
		||||
                        SecurityObjects::MobilePhoneNumber PhoneNumber;
 | 
			
		||||
                        PhoneNumber.number = MFC.sms;
 | 
			
		||||
                        PhoneNumber.primary = true;
 | 
			
		||||
                        PhoneNumber.verified = true;
 | 
			
		||||
                        User.userTypeProprietaryInfo.mobiles.clear();
 | 
			
		||||
                        User.userTypeProprietaryInfo.mobiles.push_back(PhoneNumber);
 | 
			
		||||
 | 
			
		||||
                        StorageService()->SubDB().UpdateUserInfo(UserInfo_.userinfo.email, UserInfo_.userinfo.id, User);
 | 
			
		||||
 | 
			
		||||
                        MFC.sms = MFC.sms;
 | 
			
		||||
                        MFC.type = "sms";
 | 
			
		||||
                        MFC.email = UserInfo_.userinfo.email;
 | 
			
		||||
                        MFC.id = MicroService::instance().CreateUUID();
 | 
			
		||||
 | 
			
		||||
                        Poco::JSON::Object Answer;
 | 
			
		||||
                        MFC.to_json(Answer);
 | 
			
		||||
 | 
			
		||||
                        return ReturnObject(Answer);
 | 
			
		||||
 | 
			
		||||
                    } else {
 | 
			
		||||
                        return InternalError(RESTAPI::Errors::SMSTryLater);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        } catch (const Poco::Exception &E) {
 | 
			
		||||
            Logger_.log(E);
 | 
			
		||||
        }
 | 
			
		||||
        return BadRequest(RESTAPI::Errors::MissingOrInvalidParameters);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										27
									
								
								src/RESTAPI/RESTAPI_submfa_handler.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								src/RESTAPI/RESTAPI_submfa_handler.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,27 @@
 | 
			
		||||
//
 | 
			
		||||
// Created by stephane bourque on 2021-12-01.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "framework/MicroService.h"
 | 
			
		||||
 | 
			
		||||
namespace OpenWifi {
 | 
			
		||||
    class RESTAPI_submfa_handler : public RESTAPIHandler {
 | 
			
		||||
    public:
 | 
			
		||||
        RESTAPI_submfa_handler(const RESTAPIHandler::BindingMap &bindings, Poco::Logger &L, RESTAPI_GenericServer &Server, uint64_t TransactionId, bool Internal)
 | 
			
		||||
        : RESTAPIHandler(bindings, L,
 | 
			
		||||
                         std::vector<std::string>{Poco::Net::HTTPRequest::HTTP_PUT,
 | 
			
		||||
                                                  Poco::Net::HTTPRequest::HTTP_GET,
 | 
			
		||||
                                                  Poco::Net::HTTPRequest::HTTP_OPTIONS},
 | 
			
		||||
                                                  Server,
 | 
			
		||||
                                                  TransactionId,
 | 
			
		||||
                                                  Internal, true, false , RateLimit{.Interval=1000,.MaxCalls=10},
 | 
			
		||||
                                                  true) {}
 | 
			
		||||
        static auto PathName() { return std::list<std::string>{"/api/v1/submfa"}; };
 | 
			
		||||
        void DoGet() final;
 | 
			
		||||
        void DoPost() final {};
 | 
			
		||||
        void DoDelete() final {};
 | 
			
		||||
        void DoPut() final ;
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										165
									
								
								src/RESTAPI/RESTAPI_suboauth2_handler.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										165
									
								
								src/RESTAPI/RESTAPI_suboauth2_handler.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,165 @@
 | 
			
		||||
//
 | 
			
		||||
// Created by stephane bourque on 2021-11-30.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#include "RESTAPI_suboauth2_handler.h"
 | 
			
		||||
#include "AuthService.h"
 | 
			
		||||
#include "MFAServer.h"
 | 
			
		||||
#include "framework/MicroService.h"
 | 
			
		||||
#include "StorageService.h"
 | 
			
		||||
#include "RESTAPI/RESTAPI_db_helpers.h"
 | 
			
		||||
 | 
			
		||||
namespace OpenWifi {
 | 
			
		||||
 | 
			
		||||
    void RESTAPI_suboauth2_handler::DoGet() {
 | 
			
		||||
        bool Expired = false, Contacted = false;
 | 
			
		||||
        if (!IsAuthorized(Expired, Contacted, true)) {
 | 
			
		||||
            if(Expired)
 | 
			
		||||
                return UnAuthorized(RESTAPI::Errors::EXPIRED_TOKEN);
 | 
			
		||||
            return UnAuthorized(RESTAPI::Errors::INVALID_TOKEN);
 | 
			
		||||
        }
 | 
			
		||||
        bool GetMe = GetBoolParameter(RESTAPI::Protocol::ME, false);
 | 
			
		||||
        if(GetMe) {
 | 
			
		||||
            Logger_.information(fmt::format("REQUEST-ME({}): Request for {}", Request->clientAddress().toString(),
 | 
			
		||||
                                             UserInfo_.userinfo.email));
 | 
			
		||||
            Poco::JSON::Object Me;
 | 
			
		||||
            SecurityObjects::UserInfo   ReturnedUser = UserInfo_.userinfo;
 | 
			
		||||
            Sanitize(UserInfo_, ReturnedUser);
 | 
			
		||||
            ReturnedUser.to_json(Me);
 | 
			
		||||
            return ReturnObject(Me);
 | 
			
		||||
        }
 | 
			
		||||
        BadRequest(RESTAPI::Errors::UnrecognizedRequest);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void RESTAPI_suboauth2_handler::DoDelete() {
 | 
			
		||||
        auto Token = GetBinding(RESTAPI::Protocol::TOKEN, "");
 | 
			
		||||
        std::string SessionToken;
 | 
			
		||||
        try {
 | 
			
		||||
            Poco::Net::OAuth20Credentials Auth(*Request);
 | 
			
		||||
            if (Auth.getScheme() == "Bearer") {
 | 
			
		||||
                SessionToken = Auth.getBearerToken();
 | 
			
		||||
            }
 | 
			
		||||
        } catch (const Poco::Exception &E) {
 | 
			
		||||
            return BadRequest(RESTAPI::Errors::MissingOrInvalidParameters);
 | 
			
		||||
        }
 | 
			
		||||
        if (Token.empty() || (Token != SessionToken)) {
 | 
			
		||||
            return BadRequest(RESTAPI::Errors::MissingOrInvalidParameters);
 | 
			
		||||
        }
 | 
			
		||||
        AuthService()->SubLogout(Token);
 | 
			
		||||
        return ReturnStatus(Poco::Net::HTTPResponse::HTTP_NO_CONTENT, true);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void RESTAPI_suboauth2_handler::DoPost() {
 | 
			
		||||
        const auto & Obj = ParsedBody_;
 | 
			
		||||
        auto userId = GetS(RESTAPI::Protocol::USERID, Obj);
 | 
			
		||||
        auto password = GetS(RESTAPI::Protocol::PASSWORD, Obj);
 | 
			
		||||
        auto newPassword = GetS(RESTAPI::Protocol::NEWPASSWORD, Obj);
 | 
			
		||||
        auto refreshToken = GetS("refreshToken", Obj);
 | 
			
		||||
        auto grant_type = GetParameter("grant_type");
 | 
			
		||||
 | 
			
		||||
        Poco::toLowerInPlace(userId);
 | 
			
		||||
 | 
			
		||||
        if(!refreshToken.empty() && grant_type == "refresh_token") {
 | 
			
		||||
            SecurityObjects::UserInfoAndPolicy UInfo;
 | 
			
		||||
            if(AuthService()->RefreshSubToken(*Request, refreshToken, UInfo)) {
 | 
			
		||||
                Poco::JSON::Object  Answer;
 | 
			
		||||
                UInfo.webtoken.to_json(Answer);
 | 
			
		||||
                return ReturnObject(Answer);
 | 
			
		||||
            } else {
 | 
			
		||||
                return UnAuthorized(RESTAPI::Errors::CANNOT_REFRESH_TOKEN);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if(GetBoolParameter(RESTAPI::Protocol::REQUIREMENTS)) {
 | 
			
		||||
            Logger_.information(fmt::format("POLICY-REQUEST({}): Request.", Request->clientAddress().toString()));
 | 
			
		||||
            Poco::JSON::Object  Answer;
 | 
			
		||||
            Answer.set(RESTAPI::Protocol::PASSWORDPATTERN, AuthService()->SubPasswordValidationExpression());
 | 
			
		||||
            Answer.set(RESTAPI::Protocol::ACCESSPOLICY, AuthService()->GetSubAccessPolicy());
 | 
			
		||||
            Answer.set(RESTAPI::Protocol::PASSWORDPOLICY, AuthService()->GetSubPasswordPolicy());
 | 
			
		||||
            return ReturnObject(Answer);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if(GetBoolParameter(RESTAPI::Protocol::FORGOTPASSWORD)) {
 | 
			
		||||
            SecurityObjects::UserInfo UInfo1;
 | 
			
		||||
            auto UserExists = StorageService()->SubDB().GetUserByEmail(userId,UInfo1);
 | 
			
		||||
            if(UserExists) {
 | 
			
		||||
                Logger_.information(fmt::format("FORGOTTEN-PASSWORD({}): Request for {}", Request->clientAddress().toString(), userId));
 | 
			
		||||
                SecurityObjects::ActionLink NewLink;
 | 
			
		||||
 | 
			
		||||
                NewLink.action = OpenWifi::SecurityObjects::LinkActions::SUB_FORGOT_PASSWORD;
 | 
			
		||||
                NewLink.id = MicroService::CreateUUID();
 | 
			
		||||
                NewLink.userId = UInfo1.id;
 | 
			
		||||
                NewLink.created = OpenWifi::Now();
 | 
			
		||||
                NewLink.expires = NewLink.created + (24*60*60);
 | 
			
		||||
                NewLink.userAction = false;
 | 
			
		||||
                StorageService()->ActionLinksDB().CreateAction(NewLink);
 | 
			
		||||
 | 
			
		||||
                Poco::JSON::Object ReturnObj;
 | 
			
		||||
                SecurityObjects::UserInfoAndPolicy UInfo;
 | 
			
		||||
                UInfo.webtoken.userMustChangePassword = true;
 | 
			
		||||
                UInfo.webtoken.to_json(ReturnObj);
 | 
			
		||||
                return ReturnObject(ReturnObj);
 | 
			
		||||
            } else {
 | 
			
		||||
                Poco::JSON::Object ReturnObj;
 | 
			
		||||
                SecurityObjects::UserInfoAndPolicy UInfo;
 | 
			
		||||
                UInfo.webtoken.userMustChangePassword = true;
 | 
			
		||||
                UInfo.webtoken.to_json(ReturnObj);
 | 
			
		||||
                return ReturnObject(ReturnObj);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if(GetBoolParameter(RESTAPI::Protocol::RESENDMFACODE)) {
 | 
			
		||||
            Logger_.information(fmt::format("RESEND-MFA-CODE({}): Request for {}", Request->clientAddress().toString(), userId));
 | 
			
		||||
            if(Obj->has("uuid")) {
 | 
			
		||||
                auto uuid = Obj->get("uuid").toString();
 | 
			
		||||
                if(MFAServer()->ResendCode(uuid))
 | 
			
		||||
                    return OK();
 | 
			
		||||
            }
 | 
			
		||||
            return UnAuthorized(RESTAPI::Errors::BAD_MFA_TRANSACTION);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if(GetBoolParameter(RESTAPI::Protocol::COMPLETEMFACHALLENGE)) {
 | 
			
		||||
            Logger_.information(fmt::format("COMPLETE-MFA-CHALLENGE({}): Request for {}", Request->clientAddress().toString(), userId));
 | 
			
		||||
            if(Obj->has("uuid") && Obj->has("answer")) {
 | 
			
		||||
                SecurityObjects::UserInfoAndPolicy UInfo;
 | 
			
		||||
                if(MFAServer()->CompleteMFAChallenge(Obj,UInfo)) {
 | 
			
		||||
                    Poco::JSON::Object ReturnObj;
 | 
			
		||||
                    UInfo.webtoken.to_json(ReturnObj);
 | 
			
		||||
                    return ReturnObject(ReturnObj);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            return UnAuthorized(RESTAPI::Errors::MFA_FAILURE);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        SecurityObjects::UserInfoAndPolicy UInfo;
 | 
			
		||||
        bool Expired=false;
 | 
			
		||||
        auto Code=AuthService()->AuthorizeSub(userId, password, newPassword, UInfo, Expired);
 | 
			
		||||
        if (Code==SUCCESS) {
 | 
			
		||||
            Poco::JSON::Object ReturnObj;
 | 
			
		||||
            if(AuthService()->RequiresMFA(UInfo)) {
 | 
			
		||||
                if(MFAServer()->StartMFAChallenge(UInfo, ReturnObj)) {
 | 
			
		||||
                    return ReturnObject(ReturnObj);
 | 
			
		||||
                }
 | 
			
		||||
                Logger_.warning("MFA Seems to be broken. Please fix. Disabling MFA checking for now.");
 | 
			
		||||
            }
 | 
			
		||||
            UInfo.webtoken.to_json(ReturnObj);
 | 
			
		||||
            return ReturnObject(ReturnObj);
 | 
			
		||||
        } else {
 | 
			
		||||
            switch(Code) {
 | 
			
		||||
                case INVALID_CREDENTIALS:
 | 
			
		||||
                    return UnAuthorized(RESTAPI::Errors::INVALID_CREDENTIALS);
 | 
			
		||||
                case PASSWORD_INVALID:
 | 
			
		||||
                    return UnAuthorized(RESTAPI::Errors::PASSWORD_INVALID);
 | 
			
		||||
                case PASSWORD_ALREADY_USED:
 | 
			
		||||
                    return UnAuthorized(RESTAPI::Errors::PASSWORD_ALREADY_USED);
 | 
			
		||||
                case USERNAME_PENDING_VERIFICATION:
 | 
			
		||||
                    return UnAuthorized(RESTAPI::Errors::USERNAME_PENDING_VERIFICATION);
 | 
			
		||||
                case PASSWORD_CHANGE_REQUIRED:
 | 
			
		||||
                    return UnAuthorized(RESTAPI::Errors::PASSWORD_CHANGE_REQUIRED);
 | 
			
		||||
                default:
 | 
			
		||||
                    return UnAuthorized(RESTAPI::Errors::INVALID_CREDENTIALS); break;
 | 
			
		||||
            }
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										27
									
								
								src/RESTAPI/RESTAPI_suboauth2_handler.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								src/RESTAPI/RESTAPI_suboauth2_handler.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,27 @@
 | 
			
		||||
//
 | 
			
		||||
// Created by stephane bourque on 2021-11-30.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
#include "framework/MicroService.h"
 | 
			
		||||
 | 
			
		||||
namespace OpenWifi {
 | 
			
		||||
    class RESTAPI_suboauth2_handler : public RESTAPIHandler {
 | 
			
		||||
    public:
 | 
			
		||||
        RESTAPI_suboauth2_handler(const RESTAPIHandler::BindingMap &bindings, Poco::Logger &L, RESTAPI_GenericServer &Server, uint64_t TransactionId, bool Internal)
 | 
			
		||||
        : RESTAPIHandler(bindings, L,
 | 
			
		||||
                         std::vector<std::string>{Poco::Net::HTTPRequest::HTTP_POST,
 | 
			
		||||
                                                  Poco::Net::HTTPRequest::HTTP_DELETE,
 | 
			
		||||
                                                  Poco::Net::HTTPRequest::HTTP_GET,
 | 
			
		||||
                                                  Poco::Net::HTTPRequest::HTTP_OPTIONS},
 | 
			
		||||
                                                  Server,
 | 
			
		||||
                                                  TransactionId,
 | 
			
		||||
                                                  Internal, false, false , RateLimit{.Interval=1000,.MaxCalls=10},
 | 
			
		||||
                                                  false) {}
 | 
			
		||||
        static auto PathName() { return std::list<std::string>{"/api/v1/suboauth2/{token}","/api/v1/suboauth2"}; };
 | 
			
		||||
        void DoGet() final;
 | 
			
		||||
        void DoPost() final;
 | 
			
		||||
        void DoDelete() final;
 | 
			
		||||
        void DoPut() final {};
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										36
									
								
								src/RESTAPI/RESTAPI_subpreferences.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								src/RESTAPI/RESTAPI_subpreferences.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,36 @@
 | 
			
		||||
//
 | 
			
		||||
// Created by stephane bourque on 2021-11-16.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#include "RESTAPI_subpreferences.h"
 | 
			
		||||
#include "StorageService.h"
 | 
			
		||||
 | 
			
		||||
namespace OpenWifi {
 | 
			
		||||
 | 
			
		||||
    void RESTAPI_subpreferences::DoGet() {
 | 
			
		||||
        SecurityObjects::Preferences    P;
 | 
			
		||||
        Poco::JSON::Object  Answer;
 | 
			
		||||
        StorageService()->SubPreferencesDB().GetPreferences(UserInfo_.userinfo.id, P);
 | 
			
		||||
        P.to_json(Answer);
 | 
			
		||||
        ReturnObject(Answer);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void RESTAPI_subpreferences::DoPut() {
 | 
			
		||||
 | 
			
		||||
        SecurityObjects::Preferences    P;
 | 
			
		||||
 | 
			
		||||
        const auto & RawObject = ParsedBody_;
 | 
			
		||||
        if(!P.from_json(RawObject)) {
 | 
			
		||||
            return BadRequest(RESTAPI::Errors::InvalidJSONDocument);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        P.id = UserInfo_.userinfo.id;
 | 
			
		||||
        P.modified = OpenWifi::Now();
 | 
			
		||||
        StorageService()->SubPreferencesDB().SetPreferences(P);
 | 
			
		||||
 | 
			
		||||
        Poco::JSON::Object  Answer;
 | 
			
		||||
        P.to_json(Answer);
 | 
			
		||||
        ReturnObject(Answer);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										27
									
								
								src/RESTAPI/RESTAPI_subpreferences.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								src/RESTAPI/RESTAPI_subpreferences.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,27 @@
 | 
			
		||||
//
 | 
			
		||||
// Created by stephane bourque on 2021-11-16.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "framework/MicroService.h"
 | 
			
		||||
 | 
			
		||||
namespace OpenWifi {
 | 
			
		||||
    class RESTAPI_subpreferences : public RESTAPIHandler {
 | 
			
		||||
    public:
 | 
			
		||||
        RESTAPI_subpreferences(const RESTAPIHandler::BindingMap &bindings, Poco::Logger &L, RESTAPI_GenericServer &Server, uint64_t TransactionId, bool Internal)
 | 
			
		||||
        : RESTAPIHandler(bindings, L,
 | 
			
		||||
                         std::vector<std::string>{
 | 
			
		||||
            Poco::Net::HTTPRequest::HTTP_GET,
 | 
			
		||||
            Poco::Net::HTTPRequest::HTTP_PUT,
 | 
			
		||||
            Poco::Net::HTTPRequest::HTTP_OPTIONS},
 | 
			
		||||
            Server,
 | 
			
		||||
            TransactionId,
 | 
			
		||||
            Internal) {}
 | 
			
		||||
        static auto PathName() { return std::list<std::string>{"/api/v1/subpreferences"}; };
 | 
			
		||||
        void DoGet() final;
 | 
			
		||||
        void DoPut() final;
 | 
			
		||||
        void DoPost() final {};
 | 
			
		||||
        void DoDelete() final {};
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										37
									
								
								src/RESTAPI/RESTAPI_subtotp_handler.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								src/RESTAPI/RESTAPI_subtotp_handler.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,37 @@
 | 
			
		||||
//
 | 
			
		||||
// Created by stephane bourque on 2022-01-31.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#include "RESTAPI_subtotp_handler.h"
 | 
			
		||||
 | 
			
		||||
#include "TotpCache.h"
 | 
			
		||||
 | 
			
		||||
namespace OpenWifi {
 | 
			
		||||
 | 
			
		||||
    void RESTAPI_subtotp_handler::DoGet() {
 | 
			
		||||
 | 
			
		||||
        auto Reset = GetBoolParameter("reset",false);
 | 
			
		||||
        std::string QRCode;
 | 
			
		||||
 | 
			
		||||
        if(TotpCache()->StartValidation(UserInfo_.userinfo,true,QRCode,Reset)) {
 | 
			
		||||
            return SendFileContent(QRCode, "image/svg+xml","qrcode.svg");
 | 
			
		||||
        }
 | 
			
		||||
        return BadRequest(RESTAPI::Errors::InvalidCommand);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void RESTAPI_subtotp_handler::DoPut() {
 | 
			
		||||
        auto Value = GetParameter("value","");
 | 
			
		||||
        auto nextIndex = GetParameter("index",0);
 | 
			
		||||
        bool moreCodes=false;
 | 
			
		||||
 | 
			
		||||
        RESTAPI::Errors::msg    Error;
 | 
			
		||||
        if(TotpCache()->ContinueValidation(UserInfo_.userinfo,true,Value,nextIndex,moreCodes, Error )) {
 | 
			
		||||
            Poco::JSON::Object Answer;
 | 
			
		||||
            Answer.set("nextIndex", nextIndex);
 | 
			
		||||
            Answer.set("moreCodes", moreCodes);
 | 
			
		||||
            return ReturnObject(Answer);
 | 
			
		||||
        }
 | 
			
		||||
        return BadRequest(Error);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										29
									
								
								src/RESTAPI/RESTAPI_subtotp_handler.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								src/RESTAPI/RESTAPI_subtotp_handler.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,29 @@
 | 
			
		||||
//
 | 
			
		||||
// Created by stephane bourque on 2022-01-31.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#include "framework/MicroService.h"
 | 
			
		||||
 | 
			
		||||
namespace OpenWifi {
 | 
			
		||||
    class RESTAPI_subtotp_handler : public RESTAPIHandler {
 | 
			
		||||
    public:
 | 
			
		||||
        RESTAPI_subtotp_handler(const RESTAPIHandler::BindingMap &bindings, Poco::Logger &L, RESTAPI_GenericServer &Server, uint64_t TransactionId, bool Internal)
 | 
			
		||||
                : RESTAPIHandler(bindings, L,
 | 
			
		||||
                                 std::vector<std::string>
 | 
			
		||||
                                         {
 | 
			
		||||
                                                 Poco::Net::HTTPRequest::HTTP_GET,
 | 
			
		||||
                                                 Poco::Net::HTTPRequest::HTTP_PUT,
 | 
			
		||||
                                                 Poco::Net::HTTPRequest::HTTP_OPTIONS
 | 
			
		||||
                                         },
 | 
			
		||||
                                 Server,
 | 
			
		||||
                                 TransactionId,
 | 
			
		||||
                                 Internal) {}
 | 
			
		||||
        static auto PathName() { return std::list<std::string>{"/api/v1/subtotp"}; };
 | 
			
		||||
        void DoGet() final;
 | 
			
		||||
        void DoPost() final {};
 | 
			
		||||
        void DoDelete() final {};
 | 
			
		||||
        void DoPut() final;
 | 
			
		||||
    private:
 | 
			
		||||
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										316
									
								
								src/RESTAPI/RESTAPI_subuser_handler.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										316
									
								
								src/RESTAPI/RESTAPI_subuser_handler.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,316 @@
 | 
			
		||||
//
 | 
			
		||||
// Created by stephane bourque on 2021-11-30.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#include "RESTAPI_subuser_handler.h"
 | 
			
		||||
#include "StorageService.h"
 | 
			
		||||
#include "framework/ow_constants.h"
 | 
			
		||||
#include "SMSSender.h"
 | 
			
		||||
#include "SMTPMailerService.h"
 | 
			
		||||
#include "ACLProcessor.h"
 | 
			
		||||
#include "AuthService.h"
 | 
			
		||||
#include "RESTAPI/RESTAPI_db_helpers.h"
 | 
			
		||||
#include "MFAServer.h"
 | 
			
		||||
#include "TotpCache.h"
 | 
			
		||||
 | 
			
		||||
namespace OpenWifi {
 | 
			
		||||
 | 
			
		||||
    void RESTAPI_subuser_handler::DoGet() {
 | 
			
		||||
        std::string Id = GetBinding("id", "");
 | 
			
		||||
        if(Id.empty()) {
 | 
			
		||||
            return BadRequest(RESTAPI::Errors::MissingUserID);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Poco::toLowerInPlace(Id);
 | 
			
		||||
        std::string Arg;
 | 
			
		||||
        SecurityObjects::UserInfo   UInfo;
 | 
			
		||||
        if(HasParameter("byEmail",Arg) && Arg=="true") {
 | 
			
		||||
            if(!StorageService()->SubDB().GetUserByEmail(Id,UInfo)) {
 | 
			
		||||
                return NotFound();
 | 
			
		||||
            }
 | 
			
		||||
        } else if(!StorageService()->SubDB().GetUserById(Id,UInfo)) {
 | 
			
		||||
            return NotFound();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Poco::JSON::Object  UserInfoObject;
 | 
			
		||||
        Sanitize(UserInfo_, UInfo);
 | 
			
		||||
        UInfo.to_json(UserInfoObject);
 | 
			
		||||
        ReturnObject(UserInfoObject);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void RESTAPI_subuser_handler::DoDelete() {
 | 
			
		||||
        std::string Id = GetBinding("id", "");
 | 
			
		||||
        if(Id.empty()) {
 | 
			
		||||
            return BadRequest(RESTAPI::Errors::MissingUserID);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        SecurityObjects::UserInfo TargetUser;
 | 
			
		||||
        if(!StorageService()->SubDB().GetUserById(Id,TargetUser)) {
 | 
			
		||||
            return NotFound();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if(TargetUser.userRole != SecurityObjects::SUBSCRIBER) {
 | 
			
		||||
            return BadRequest(RESTAPI::Errors::InvalidUserRole);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if(!Internal_ && !ACLProcessor::Can(UserInfo_.userinfo, TargetUser,ACLProcessor::DELETE)) {
 | 
			
		||||
            return UnAuthorized(RESTAPI::Errors::ACCESS_DENIED);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if(!StorageService()->SubDB().DeleteUser(UserInfo_.userinfo.email,Id)) {
 | 
			
		||||
            return NotFound();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        AuthService()->DeleteSubUserFromCache(Id);
 | 
			
		||||
        StorageService()->SubTokenDB().RevokeAllTokens(TargetUser.email);
 | 
			
		||||
        StorageService()->SubPreferencesDB().DeleteRecord("id", Id);
 | 
			
		||||
        StorageService()->SubAvatarDB().DeleteRecord("id", Id);
 | 
			
		||||
        Logger_.information(fmt::format("User '{}' deleted by '{}'.",Id,UserInfo_.userinfo.email));
 | 
			
		||||
        OK();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void RESTAPI_subuser_handler::DoPost() {
 | 
			
		||||
        std::string Id = GetBinding("id", "");
 | 
			
		||||
        if(Id!="0") {
 | 
			
		||||
            return BadRequest(RESTAPI::Errors::IdMustBe0);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        SecurityObjects::UserInfo   NewUser;
 | 
			
		||||
        const auto & RawObject = ParsedBody_;
 | 
			
		||||
        if(!NewUser.from_json(RawObject)) {
 | 
			
		||||
            return BadRequest(RESTAPI::Errors::InvalidJSONDocument);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if(NewUser.userRole == SecurityObjects::UNKNOWN || NewUser.userRole != SecurityObjects::SUBSCRIBER) {
 | 
			
		||||
            return BadRequest(RESTAPI::Errors::InvalidUserRole);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Poco::toLowerInPlace(NewUser.email);
 | 
			
		||||
        SecurityObjects::UserInfo   Existing;
 | 
			
		||||
        if(StorageService()->SubDB().GetUserByEmail(NewUser.email,Existing)) {
 | 
			
		||||
            return BadRequest(RESTAPI::Errors::UserAlreadyExists);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if(!Internal_ && !ACLProcessor::Can(UserInfo_.userinfo,NewUser,ACLProcessor::CREATE)) {
 | 
			
		||||
            return UnAuthorized(RESTAPI::Errors::ACCESS_DENIED);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Poco::toLowerInPlace(NewUser.email);
 | 
			
		||||
        if(!Utils::ValidEMailAddress(NewUser.email)) {
 | 
			
		||||
            return BadRequest(RESTAPI::Errors::InvalidEmailAddress);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if(!NewUser.currentPassword.empty()) {
 | 
			
		||||
            if(!AuthService()->ValidateSubPassword(NewUser.currentPassword)) {
 | 
			
		||||
                return BadRequest(RESTAPI::Errors::InvalidPassword);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if(NewUser.name.empty())
 | 
			
		||||
            NewUser.name = NewUser.email;
 | 
			
		||||
 | 
			
		||||
        //  You cannot enable MFA during user creation
 | 
			
		||||
        NewUser.userTypeProprietaryInfo.mfa.enabled = false;
 | 
			
		||||
        NewUser.userTypeProprietaryInfo.mfa.method = "";
 | 
			
		||||
        NewUser.userTypeProprietaryInfo.mobiles.clear();
 | 
			
		||||
        NewUser.userTypeProprietaryInfo.authenticatorSecret.clear();
 | 
			
		||||
 | 
			
		||||
        if(!StorageService()->SubDB().CreateUser(UserInfo_.userinfo.email, NewUser)) {
 | 
			
		||||
            Logger_.information(fmt::format("Could not add user '{}'.",NewUser.email));
 | 
			
		||||
            return BadRequest(RESTAPI::Errors::RecordNotCreated);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if(GetParameter("email_verification","false")=="true") {
 | 
			
		||||
            if(AuthService::VerifySubEmail(NewUser))
 | 
			
		||||
                Logger_.information(fmt::format("Verification e-mail requested for {}",NewUser.email));
 | 
			
		||||
            StorageService()->SubDB().UpdateUserInfo(UserInfo_.userinfo.email,NewUser.id,NewUser);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if(!StorageService()->SubDB().GetUserByEmail(NewUser.email, NewUser)) {
 | 
			
		||||
            Logger_.information(fmt::format("User '{}' but not retrieved.",NewUser.email));
 | 
			
		||||
            return NotFound();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Poco::JSON::Object  UserInfoObject;
 | 
			
		||||
        Sanitize(UserInfo_, NewUser);
 | 
			
		||||
        NewUser.to_json(UserInfoObject);
 | 
			
		||||
        ReturnObject(UserInfoObject);
 | 
			
		||||
        Logger_.information(fmt::format("User '{}' has been added by '{}')",NewUser.email, UserInfo_.userinfo.email));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void RESTAPI_subuser_handler::DoPut() {
 | 
			
		||||
        std::string Id = GetBinding("id", "");
 | 
			
		||||
        if(Id.empty()) {
 | 
			
		||||
            return BadRequest(RESTAPI::Errors::MissingUserID);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        SecurityObjects::UserInfo   Existing;
 | 
			
		||||
        if(!StorageService()->SubDB().GetUserById(Id,Existing)) {
 | 
			
		||||
            return NotFound();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if(!Internal_ && !ACLProcessor::Can(UserInfo_.userinfo,Existing,ACLProcessor::MODIFY)) {
 | 
			
		||||
            return UnAuthorized(RESTAPI::Errors::ACCESS_DENIED);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if(GetBoolParameter("resetMFA")) {
 | 
			
		||||
            if( (UserInfo_.userinfo.userRole == SecurityObjects::ROOT) ||
 | 
			
		||||
                (UserInfo_.userinfo.userRole == SecurityObjects::ADMIN && Existing.userRole!=SecurityObjects::ROOT) ||
 | 
			
		||||
                (UserInfo_.userinfo.id == Id)) {
 | 
			
		||||
                Existing.userTypeProprietaryInfo.mfa.enabled = false;
 | 
			
		||||
                Existing.userTypeProprietaryInfo.mfa.method.clear();
 | 
			
		||||
                Existing.userTypeProprietaryInfo.mobiles.clear();
 | 
			
		||||
                Existing.modified = OpenWifi::Now();
 | 
			
		||||
                Existing.notes.push_back( SecurityObjects::NoteInfo{
 | 
			
		||||
                        .created=OpenWifi::Now(),
 | 
			
		||||
                        .createdBy=UserInfo_.userinfo.email,
 | 
			
		||||
                        .note="MFA Reset by " + UserInfo_.userinfo.email});
 | 
			
		||||
                StorageService()->SubDB().UpdateUserInfo(UserInfo_.userinfo.email,Id,Existing);
 | 
			
		||||
                SecurityObjects::UserInfo   NewUserInfo;
 | 
			
		||||
                StorageService()->SubDB().GetUserByEmail(UserInfo_.userinfo.email,NewUserInfo);
 | 
			
		||||
                Poco::JSON::Object  ModifiedObject;
 | 
			
		||||
                Sanitize(UserInfo_, NewUserInfo);
 | 
			
		||||
                NewUserInfo.to_json(ModifiedObject);
 | 
			
		||||
                return ReturnObject(ModifiedObject);
 | 
			
		||||
            } else {
 | 
			
		||||
                return UnAuthorized(RESTAPI::Errors::ACCESS_DENIED);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if(GetBoolParameter("forgotPassword")) {
 | 
			
		||||
            Existing.changePassword = true;
 | 
			
		||||
            Logger_.information(fmt::format("FORGOTTEN-PASSWORD({}): Request for {}", Request->clientAddress().toString(), Existing.email));
 | 
			
		||||
 | 
			
		||||
            SecurityObjects::ActionLink NewLink;
 | 
			
		||||
            NewLink.action = OpenWifi::SecurityObjects::LinkActions::SUB_FORGOT_PASSWORD;
 | 
			
		||||
            NewLink.id = MicroService::CreateUUID();
 | 
			
		||||
            NewLink.userId = Existing.id;
 | 
			
		||||
            NewLink.created = OpenWifi::Now();
 | 
			
		||||
            NewLink.expires = NewLink.created + (24*60*60);
 | 
			
		||||
            NewLink.userAction = false;
 | 
			
		||||
            StorageService()->ActionLinksDB().CreateAction(NewLink);
 | 
			
		||||
 | 
			
		||||
            return OK();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        SecurityObjects::UserInfo   NewUser;
 | 
			
		||||
        const auto & RawObject = ParsedBody_;
 | 
			
		||||
        if(!NewUser.from_json(RawObject)) {
 | 
			
		||||
            return BadRequest(RESTAPI::Errors::InvalidJSONDocument);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // some basic validations
 | 
			
		||||
        if(RawObject->has("userRole") &&
 | 
			
		||||
            (SecurityObjects::UserTypeFromString(RawObject->get("userRole").toString())==SecurityObjects::UNKNOWN ||
 | 
			
		||||
            SecurityObjects::UserTypeFromString(RawObject->get("userRole").toString())==SecurityObjects::SUBSCRIBER)) {
 | 
			
		||||
            return BadRequest(RESTAPI::Errors::InvalidUserRole);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // The only valid things to change are: changePassword, name,
 | 
			
		||||
        AssignIfPresent(RawObject,"name", Existing.name);
 | 
			
		||||
        AssignIfPresent(RawObject,"description", Existing.description);
 | 
			
		||||
        AssignIfPresent(RawObject,"owner", Existing.owner);
 | 
			
		||||
        AssignIfPresent(RawObject,"location", Existing.location);
 | 
			
		||||
        AssignIfPresent(RawObject,"locale", Existing.locale);
 | 
			
		||||
        AssignIfPresent(RawObject,"changePassword", Existing.changePassword);
 | 
			
		||||
        AssignIfPresent(RawObject,"suspended", Existing.suspended);
 | 
			
		||||
        AssignIfPresent(RawObject,"blackListed", Existing.blackListed);
 | 
			
		||||
 | 
			
		||||
        if(RawObject->has("userRole")) {
 | 
			
		||||
            auto NewRole = SecurityObjects::UserTypeFromString(RawObject->get("userRole").toString());
 | 
			
		||||
            if(NewRole!=Existing.userRole) {
 | 
			
		||||
                if(UserInfo_.userinfo.userRole!=SecurityObjects::ROOT && NewRole==SecurityObjects::ROOT) {
 | 
			
		||||
                    return UnAuthorized(RESTAPI::Errors::ACCESS_DENIED);
 | 
			
		||||
                }
 | 
			
		||||
                if(Id==UserInfo_.userinfo.id) {
 | 
			
		||||
                    return UnAuthorized(RESTAPI::Errors::ACCESS_DENIED);
 | 
			
		||||
                }
 | 
			
		||||
                Existing.userRole = NewRole;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if(RawObject->has("notes")) {
 | 
			
		||||
            SecurityObjects::NoteInfoVec NIV;
 | 
			
		||||
            NIV = RESTAPI_utils::to_object_array<SecurityObjects::NoteInfo>(RawObject->get("notes").toString());
 | 
			
		||||
            for(auto const &i:NIV) {
 | 
			
		||||
                SecurityObjects::NoteInfo   ii{.created=(uint64_t)OpenWifi::Now(), .createdBy=UserInfo_.userinfo.email, .note=i.note};
 | 
			
		||||
                Existing.notes.push_back(ii);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        if(RawObject->has("currentPassword")) {
 | 
			
		||||
            if(!AuthService()->ValidateSubPassword(RawObject->get("currentPassword").toString())) {
 | 
			
		||||
                return BadRequest(RESTAPI::Errors::InvalidPassword);
 | 
			
		||||
            }
 | 
			
		||||
            if(!AuthService()->SetPassword(RawObject->get("currentPassword").toString(),Existing)) {
 | 
			
		||||
                return BadRequest(RESTAPI::Errors::PasswordRejected);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if(GetParameter("email_verification","false")=="true") {
 | 
			
		||||
            if(AuthService::VerifySubEmail(Existing))
 | 
			
		||||
                Logger_.information(fmt::format("Verification e-mail requested for {}",Existing.email));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if(RawObject->has("userTypeProprietaryInfo")) {
 | 
			
		||||
            if(NewUser.userTypeProprietaryInfo.mfa.enabled) {
 | 
			
		||||
                if (!MFAMETHODS::Validate(NewUser.userTypeProprietaryInfo.mfa.method)) {
 | 
			
		||||
                    return BadRequest(RESTAPI::Errors::BadMFAMethod);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if( NewUser.userTypeProprietaryInfo.mfa.enabled &&
 | 
			
		||||
                    NewUser.userTypeProprietaryInfo.mfa.method == MFAMETHODS::SMS &&
 | 
			
		||||
                    !SMSSender()->Enabled()) {
 | 
			
		||||
                    return BadRequest(RESTAPI::Errors::SMSMFANotEnabled);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if( NewUser.userTypeProprietaryInfo.mfa.enabled &&
 | 
			
		||||
                    NewUser.userTypeProprietaryInfo.mfa.method == MFAMETHODS::EMAIL &&
 | 
			
		||||
                    !SMTPMailerService()->Enabled()) {
 | 
			
		||||
                    return BadRequest(RESTAPI::Errors::EMailMFANotEnabled);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                Existing.userTypeProprietaryInfo.mfa.method = NewUser.userTypeProprietaryInfo.mfa.method;
 | 
			
		||||
                Existing.userTypeProprietaryInfo.mfa.enabled = true;
 | 
			
		||||
 | 
			
		||||
                if (NewUser.userTypeProprietaryInfo.mfa.method == MFAMETHODS::SMS) {
 | 
			
		||||
                    if(NewUser.userTypeProprietaryInfo.mobiles.empty()) {
 | 
			
		||||
                        return BadRequest(RESTAPI::Errors::NeedMobileNumber);
 | 
			
		||||
                    }
 | 
			
		||||
                    if (!SMSSender()->IsNumberValid(NewUser.userTypeProprietaryInfo.mobiles[0].number,UserInfo_.userinfo.email)) {
 | 
			
		||||
                        return BadRequest(RESTAPI::Errors::NeedMobileNumber);
 | 
			
		||||
                    }
 | 
			
		||||
                    Existing.userTypeProprietaryInfo.mobiles = NewUser.userTypeProprietaryInfo.mobiles;
 | 
			
		||||
                    Existing.userTypeProprietaryInfo.mobiles[0].verified = true;
 | 
			
		||||
                    Existing.userTypeProprietaryInfo.authenticatorSecret.clear();
 | 
			
		||||
                } else if (NewUser.userTypeProprietaryInfo.mfa.method == MFAMETHODS::AUTHENTICATOR) {
 | 
			
		||||
                    std::string Secret;
 | 
			
		||||
                    Existing.userTypeProprietaryInfo.mobiles.clear();
 | 
			
		||||
                    if(Existing.userTypeProprietaryInfo.authenticatorSecret.empty() && TotpCache()->CompleteValidation(UserInfo_.userinfo,false,Secret)) {
 | 
			
		||||
                        Existing.userTypeProprietaryInfo.authenticatorSecret = Secret;
 | 
			
		||||
                    } else if (!Existing.userTypeProprietaryInfo.authenticatorSecret.empty()) {
 | 
			
		||||
                        // we allow someone to use their old secret
 | 
			
		||||
                    } else {
 | 
			
		||||
                        return BadRequest(RESTAPI::Errors::AuthenticatorVerificationIncomplete);
 | 
			
		||||
                    }
 | 
			
		||||
                } else if (NewUser.userTypeProprietaryInfo.mfa.method == MFAMETHODS::EMAIL) {
 | 
			
		||||
                    Existing.userTypeProprietaryInfo.mobiles.clear();
 | 
			
		||||
                    Existing.userTypeProprietaryInfo.authenticatorSecret.clear();
 | 
			
		||||
                }
 | 
			
		||||
            } else {
 | 
			
		||||
                Existing.userTypeProprietaryInfo.authenticatorSecret.clear();
 | 
			
		||||
                Existing.userTypeProprietaryInfo.mobiles.clear();
 | 
			
		||||
                Existing.userTypeProprietaryInfo.mfa.enabled = false;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if(StorageService()->SubDB().UpdateUserInfo(UserInfo_.userinfo.email,Id,Existing)) {
 | 
			
		||||
            SecurityObjects::UserInfo   NewUserInfo;
 | 
			
		||||
            StorageService()->SubDB().GetUserById(Id,NewUserInfo);
 | 
			
		||||
            Poco::JSON::Object  ModifiedObject;
 | 
			
		||||
            Sanitize(UserInfo_, NewUserInfo);
 | 
			
		||||
            NewUserInfo.to_json(ModifiedObject);
 | 
			
		||||
            return ReturnObject(ModifiedObject);
 | 
			
		||||
        }
 | 
			
		||||
        BadRequest(RESTAPI::Errors::RecordNotUpdated);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										31
									
								
								src/RESTAPI/RESTAPI_subuser_handler.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								src/RESTAPI/RESTAPI_subuser_handler.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,31 @@
 | 
			
		||||
//
 | 
			
		||||
// Created by stephane bourque on 2021-11-30.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "framework/MicroService.h"
 | 
			
		||||
 | 
			
		||||
namespace OpenWifi {
 | 
			
		||||
    class RESTAPI_subuser_handler : public RESTAPIHandler {
 | 
			
		||||
    public:
 | 
			
		||||
        RESTAPI_subuser_handler(const RESTAPIHandler::BindingMap &bindings, Poco::Logger &L, RESTAPI_GenericServer &Server, uint64_t TransactionId, bool Internal)
 | 
			
		||||
        : RESTAPIHandler(bindings, L,
 | 
			
		||||
                         std::vector<std::string>
 | 
			
		||||
                         {Poco::Net::HTTPRequest::HTTP_POST,
 | 
			
		||||
                          Poco::Net::HTTPRequest::HTTP_GET,
 | 
			
		||||
                          Poco::Net::HTTPRequest::HTTP_PUT,
 | 
			
		||||
                          Poco::Net::HTTPRequest::HTTP_DELETE,
 | 
			
		||||
                          Poco::Net::HTTPRequest::HTTP_OPTIONS},
 | 
			
		||||
                          Server,
 | 
			
		||||
                          TransactionId,
 | 
			
		||||
                          Internal) {}
 | 
			
		||||
        static auto PathName() { return std::list<std::string>{"/api/v1/subuser/{id}"}; };
 | 
			
		||||
        void DoGet() final;
 | 
			
		||||
        void DoPost() final;
 | 
			
		||||
        void DoDelete() final;
 | 
			
		||||
        void DoPut() final;
 | 
			
		||||
    private:
 | 
			
		||||
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										80
									
								
								src/RESTAPI/RESTAPI_subusers_handler.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										80
									
								
								src/RESTAPI/RESTAPI_subusers_handler.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,80 @@
 | 
			
		||||
//
 | 
			
		||||
// Created by stephane bourque on 2021-11-30.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#include "RESTAPI_subusers_handler.h"
 | 
			
		||||
#include "StorageService.h"
 | 
			
		||||
#include "framework/MicroService.h"
 | 
			
		||||
#include "RESTAPI/RESTAPI_db_helpers.h"
 | 
			
		||||
 | 
			
		||||
namespace OpenWifi {
 | 
			
		||||
 | 
			
		||||
    void RESTAPI_subusers_handler::DoGet() {
 | 
			
		||||
        bool IdOnly = GetBoolParameter("idOnly");
 | 
			
		||||
        auto operatorId = GetParameter("operatorId");
 | 
			
		||||
        auto nameSearch = GetParameter("nameSearch");
 | 
			
		||||
        auto emailSearch = GetParameter("emailSearch");
 | 
			
		||||
 | 
			
		||||
        std::string baseQuery;
 | 
			
		||||
        if(!nameSearch.empty() || !emailSearch.empty()) {
 | 
			
		||||
            if(!nameSearch.empty())
 | 
			
		||||
                baseQuery = fmt::format(" Lower(name) like('%{}%') ", Poco::toLower(nameSearch) );
 | 
			
		||||
            if(!emailSearch.empty())
 | 
			
		||||
                baseQuery += baseQuery.empty() ? fmt::format(" Lower(email) like('%{}%') ", Poco::toLower(emailSearch))
 | 
			
		||||
                : fmt::format(" and Lower(email) like('%{}%') ", Poco::toLower(emailSearch));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if(QB_.CountOnly) {
 | 
			
		||||
            std::string whereClause;
 | 
			
		||||
            if(!operatorId.empty()) {
 | 
			
		||||
                whereClause = baseQuery.empty() ? fmt::format(" owner='{}' ", operatorId) :
 | 
			
		||||
                              fmt::format(" owner='{}' and {} ", operatorId, baseQuery);
 | 
			
		||||
                auto count = StorageService()->SubDB().Count(whereClause);
 | 
			
		||||
                return ReturnCountOnly(count);
 | 
			
		||||
            }
 | 
			
		||||
            auto count = StorageService()->UserDB().Count();
 | 
			
		||||
            return ReturnCountOnly(count);
 | 
			
		||||
        } else if(QB_.Select.empty()) {
 | 
			
		||||
            std::string whereClause;
 | 
			
		||||
            if(!operatorId.empty()) {
 | 
			
		||||
                whereClause = baseQuery.empty() ? fmt::format(" owner='{}' ", operatorId) :
 | 
			
		||||
                              fmt::format(" owner='{}' and {} ", operatorId, baseQuery);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            SecurityObjects::UserInfoList   Users;
 | 
			
		||||
            if (StorageService()->SubDB().GetUsers(QB_.Offset, QB_.Limit, Users.users, whereClause)) {
 | 
			
		||||
                for (auto &i : Users.users) {
 | 
			
		||||
                    Sanitize(UserInfo_, i);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if(IdOnly) {
 | 
			
		||||
                Poco::JSON::Array   Arr;
 | 
			
		||||
                Poco::JSON::Object  Answer;
 | 
			
		||||
 | 
			
		||||
                for(const auto &i:Users.users) {
 | 
			
		||||
                    Arr.add(i.id);
 | 
			
		||||
                }
 | 
			
		||||
                Answer.set("users",Arr);
 | 
			
		||||
                return ReturnObject(Answer);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            Poco::JSON::Object  Answer;
 | 
			
		||||
            Users.to_json(Answer);
 | 
			
		||||
            return ReturnObject(Answer);
 | 
			
		||||
        } else {
 | 
			
		||||
            SecurityObjects::UserInfoList   Users;
 | 
			
		||||
            for(auto &i:SelectedRecords()) {
 | 
			
		||||
                SecurityObjects::UserInfo   UInfo;
 | 
			
		||||
                if(StorageService()->SubDB().GetUserById(i,UInfo)) {
 | 
			
		||||
                    Poco::JSON::Object Obj;
 | 
			
		||||
                    Sanitize(UserInfo_, UInfo);
 | 
			
		||||
                    Users.users.emplace_back(UInfo);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            Poco::JSON::Object Answer;
 | 
			
		||||
            Users.to_json(Answer);
 | 
			
		||||
            return ReturnObject(Answer);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										26
									
								
								src/RESTAPI/RESTAPI_subusers_handler.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								src/RESTAPI/RESTAPI_subusers_handler.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,26 @@
 | 
			
		||||
//
 | 
			
		||||
// Created by stephane bourque on 2021-11-30.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "framework/MicroService.h"
 | 
			
		||||
 | 
			
		||||
namespace OpenWifi {
 | 
			
		||||
    class RESTAPI_subusers_handler : public RESTAPIHandler {
 | 
			
		||||
    public:
 | 
			
		||||
        RESTAPI_subusers_handler(const RESTAPIHandler::BindingMap &bindings, Poco::Logger &L, RESTAPI_GenericServer &Server, uint64_t TransactionId, bool Internal)
 | 
			
		||||
        : RESTAPIHandler(bindings, L,
 | 
			
		||||
                         std::vector<std::string>
 | 
			
		||||
                         {Poco::Net::HTTPRequest::HTTP_GET,
 | 
			
		||||
                          Poco::Net::HTTPRequest::HTTP_OPTIONS},
 | 
			
		||||
                          Server,
 | 
			
		||||
                          TransactionId,
 | 
			
		||||
                          Internal) {}
 | 
			
		||||
        static auto PathName() { return std::list<std::string>{"/api/v1/subusers"}; };
 | 
			
		||||
        void DoGet() final;
 | 
			
		||||
        void DoPost() final {};
 | 
			
		||||
        void DoDelete() final {};
 | 
			
		||||
        void DoPut() final {};
 | 
			
		||||
    };
 | 
			
		||||
};
 | 
			
		||||
@@ -2,14 +2,13 @@
 | 
			
		||||
// Created by stephane bourque on 2021-07-01.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#include "RESTAPI_systemEndpoints_handler.h"
 | 
			
		||||
#include "Daemon.h"
 | 
			
		||||
#include "RESTAPI_SecurityObjects.h"
 | 
			
		||||
#include "RESTAPI_system_endpoints_handler.h"
 | 
			
		||||
#include "RESTObjects/RESTAPI_SecurityObjects.h"
 | 
			
		||||
 | 
			
		||||
namespace OpenWifi {
 | 
			
		||||
 | 
			
		||||
    void RESTAPI_systemEndpoints_handler::DoGet() {
 | 
			
		||||
        auto Services = Daemon()->GetServices();
 | 
			
		||||
    void RESTAPI_system_endpoints_handler::DoGet() {
 | 
			
		||||
        auto Services = MicroService::instance().GetServices();
 | 
			
		||||
        SecurityObjects::SystemEndpointList L;
 | 
			
		||||
        for(const auto &i:Services) {
 | 
			
		||||
            SecurityObjects::SystemEndpoint S{
 | 
			
		||||
@@ -2,25 +2,24 @@
 | 
			
		||||
// Created by stephane bourque on 2021-07-01.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#ifndef UCENTRALSEC_RESTAPI_SYSTEMENDPOINTS_HANDLER_H
 | 
			
		||||
#define UCENTRALSEC_RESTAPI_SYSTEMENDPOINTS_HANDLER_H
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "../framework/MicroService.h"
 | 
			
		||||
 | 
			
		||||
#include "RESTAPI_handler.h"
 | 
			
		||||
namespace OpenWifi {
 | 
			
		||||
    class RESTAPI_systemEndpoints_handler : public RESTAPIHandler {
 | 
			
		||||
    class RESTAPI_system_endpoints_handler : public RESTAPIHandler {
 | 
			
		||||
    public:
 | 
			
		||||
        RESTAPI_systemEndpoints_handler(const RESTAPIHandler::BindingMap &bindings, Poco::Logger &L, RESTAPI_GenericServer &Server, bool Internal)
 | 
			
		||||
        RESTAPI_system_endpoints_handler(const RESTAPIHandler::BindingMap &bindings, Poco::Logger &L, RESTAPI_GenericServer &Server, uint64_t TransactionId, bool Internal)
 | 
			
		||||
                : RESTAPIHandler(bindings, L,
 | 
			
		||||
                                 std::vector<std::string>{Poco::Net::HTTPRequest::HTTP_GET,
 | 
			
		||||
                                                          Poco::Net::HTTPRequest::HTTP_OPTIONS},
 | 
			
		||||
                                                          Server,
 | 
			
		||||
                                                          TransactionId,
 | 
			
		||||
                                                          Internal) {}
 | 
			
		||||
        static const std::list<const char *> PathName() { return std::list<const char *>{"/api/v1/systemEndpoints"}; };
 | 
			
		||||
        static auto PathName() { return std::list<std::string>{"/api/v1/systemEndpoints"}; };
 | 
			
		||||
        void DoGet() final;
 | 
			
		||||
        void DoPost() final {};
 | 
			
		||||
        void DoDelete() final {};
 | 
			
		||||
        void DoPut() final {};
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#endif //UCENTRALSEC_RESTAPI_SYSTEMENDPOINTS_HANDLER_H
 | 
			
		||||
							
								
								
									
										35
									
								
								src/RESTAPI/RESTAPI_totp_handler.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								src/RESTAPI/RESTAPI_totp_handler.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,35 @@
 | 
			
		||||
//
 | 
			
		||||
// Created by stephane bourque on 2022-01-31.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#include "RESTAPI_totp_handler.h"
 | 
			
		||||
#include "TotpCache.h"
 | 
			
		||||
 | 
			
		||||
namespace OpenWifi {
 | 
			
		||||
 | 
			
		||||
    void RESTAPI_totp_handler::DoGet() {
 | 
			
		||||
 | 
			
		||||
        auto Reset = GetBoolParameter("reset",false);
 | 
			
		||||
        std::string QRCode;
 | 
			
		||||
        if(TotpCache()->StartValidation(UserInfo_.userinfo,false,QRCode,Reset)) {
 | 
			
		||||
            return SendFileContent(QRCode, "image/svg+xml","qrcode.svg");
 | 
			
		||||
        }
 | 
			
		||||
        return BadRequest(RESTAPI::Errors::InvalidCommand);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void RESTAPI_totp_handler::DoPut() {
 | 
			
		||||
        auto Value = GetParameter("value","");
 | 
			
		||||
        auto nextIndex = GetParameter("index",0);
 | 
			
		||||
        bool moreCodes=false;
 | 
			
		||||
 | 
			
		||||
        RESTAPI::Errors::msg Err;
 | 
			
		||||
        if(TotpCache()->ContinueValidation(UserInfo_.userinfo,false,Value,nextIndex,moreCodes, Err)) {
 | 
			
		||||
            Poco::JSON::Object Answer;
 | 
			
		||||
            Answer.set("nextIndex", nextIndex);
 | 
			
		||||
            Answer.set("moreCodes", moreCodes);
 | 
			
		||||
            return ReturnObject(Answer);
 | 
			
		||||
        }
 | 
			
		||||
        return BadRequest(Err);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										31
									
								
								src/RESTAPI/RESTAPI_totp_handler.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								src/RESTAPI/RESTAPI_totp_handler.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,31 @@
 | 
			
		||||
//
 | 
			
		||||
// Created by stephane bourque on 2022-01-31.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "framework/MicroService.h"
 | 
			
		||||
 | 
			
		||||
namespace OpenWifi {
 | 
			
		||||
    class RESTAPI_totp_handler : public RESTAPIHandler {
 | 
			
		||||
    public:
 | 
			
		||||
        RESTAPI_totp_handler(const RESTAPIHandler::BindingMap &bindings, Poco::Logger &L, RESTAPI_GenericServer &Server, uint64_t TransactionId, bool Internal)
 | 
			
		||||
                : RESTAPIHandler(bindings, L,
 | 
			
		||||
                                 std::vector<std::string>
 | 
			
		||||
                                         {
 | 
			
		||||
                                              Poco::Net::HTTPRequest::HTTP_GET,
 | 
			
		||||
                                              Poco::Net::HTTPRequest::HTTP_PUT,
 | 
			
		||||
                                              Poco::Net::HTTPRequest::HTTP_OPTIONS
 | 
			
		||||
                                          },
 | 
			
		||||
                                 Server,
 | 
			
		||||
                                 TransactionId,
 | 
			
		||||
                                 Internal) {}
 | 
			
		||||
        static auto PathName() { return std::list<std::string>{"/api/v1/totp"}; };
 | 
			
		||||
        void DoGet() final;
 | 
			
		||||
        void DoPost() final {};
 | 
			
		||||
        void DoDelete() final {};
 | 
			
		||||
        void DoPut() final;
 | 
			
		||||
    private:
 | 
			
		||||
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										327
									
								
								src/RESTAPI/RESTAPI_user_handler.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										327
									
								
								src/RESTAPI/RESTAPI_user_handler.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,327 @@
 | 
			
		||||
//
 | 
			
		||||
// Created by stephane bourque on 2021-06-21.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#include "RESTAPI_user_handler.h"
 | 
			
		||||
#include "StorageService.h"
 | 
			
		||||
#include "framework/ow_constants.h"
 | 
			
		||||
#include "SMSSender.h"
 | 
			
		||||
#include "SMTPMailerService.h"
 | 
			
		||||
#include "ACLProcessor.h"
 | 
			
		||||
#include "AuthService.h"
 | 
			
		||||
#include "RESTAPI/RESTAPI_db_helpers.h"
 | 
			
		||||
#include "MFAServer.h"
 | 
			
		||||
#include "TotpCache.h"
 | 
			
		||||
 | 
			
		||||
namespace OpenWifi {
 | 
			
		||||
 | 
			
		||||
    void RESTAPI_user_handler::DoGet() {
 | 
			
		||||
        std::string Id = GetBinding("id", "");
 | 
			
		||||
        if(Id.empty()) {
 | 
			
		||||
            return BadRequest(RESTAPI::Errors::MissingUserID);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Poco::toLowerInPlace(Id);
 | 
			
		||||
        std::string Arg;
 | 
			
		||||
        SecurityObjects::UserInfo   UInfo;
 | 
			
		||||
        if(HasParameter("byEmail",Arg) && Arg=="true") {
 | 
			
		||||
            if(!StorageService()->UserDB().GetUserByEmail(Id,UInfo)) {
 | 
			
		||||
                return NotFound();
 | 
			
		||||
            }
 | 
			
		||||
        } else if(!StorageService()->UserDB().GetUserById(Id,UInfo)) {
 | 
			
		||||
            return NotFound();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if(!ACLProcessor::Can(UserInfo_.userinfo, UInfo,ACLProcessor::READ)) {
 | 
			
		||||
            return UnAuthorized(RESTAPI::Errors::ACCESS_DENIED);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Poco::JSON::Object  UserInfoObject;
 | 
			
		||||
        Sanitize(UserInfo_, UInfo);
 | 
			
		||||
        UInfo.to_json(UserInfoObject);
 | 
			
		||||
        ReturnObject(UserInfoObject);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void RESTAPI_user_handler::DoDelete() {
 | 
			
		||||
        std::string Id = GetBinding("id", "");
 | 
			
		||||
        if(Id.empty()) {
 | 
			
		||||
            return BadRequest(RESTAPI::Errors::MissingUserID);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        SecurityObjects::UserInfo UInfo;
 | 
			
		||||
        if(!StorageService()->UserDB().GetUserById(Id,UInfo)) {
 | 
			
		||||
            return NotFound();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if(!ACLProcessor::Can(UserInfo_.userinfo, UInfo,ACLProcessor::DELETE)) {
 | 
			
		||||
            return UnAuthorized(RESTAPI::Errors::ACCESS_DENIED);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if(!StorageService()->UserDB().DeleteUser(UserInfo_.userinfo.email,Id)) {
 | 
			
		||||
            return NotFound();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        AuthService()->DeleteUserFromCache(Id);
 | 
			
		||||
        StorageService()->AvatarDB().DeleteAvatar(UserInfo_.userinfo.email,Id);
 | 
			
		||||
        StorageService()->PreferencesDB().DeletePreferences(UserInfo_.userinfo.email,Id);
 | 
			
		||||
        StorageService()->UserTokenDB().RevokeAllTokens(Id);
 | 
			
		||||
        Logger_.information(fmt::format("User '{}' deleted by '{}'.",Id,UserInfo_.userinfo.email));
 | 
			
		||||
        OK();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void RESTAPI_user_handler::DoPost() {
 | 
			
		||||
 | 
			
		||||
        std::string Id = GetBinding("id", "");
 | 
			
		||||
        if(Id!="0") {
 | 
			
		||||
            return BadRequest(RESTAPI::Errors::IdMustBe0);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        SecurityObjects::UserInfo   NewUser;
 | 
			
		||||
        const auto & RawObject = ParsedBody_;
 | 
			
		||||
        if(!NewUser.from_json(RawObject)) {
 | 
			
		||||
            return BadRequest(RESTAPI::Errors::InvalidJSONDocument);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if(NewUser.userRole == SecurityObjects::UNKNOWN) {
 | 
			
		||||
            return BadRequest(RESTAPI::Errors::InvalidUserRole);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if(UserInfo_.userinfo.userRole==SecurityObjects::ROOT) {
 | 
			
		||||
            NewUser.owner = GetParameter("entity","");
 | 
			
		||||
        } else {
 | 
			
		||||
            NewUser.owner = UserInfo_.userinfo.owner;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if(!ACLProcessor::Can(UserInfo_.userinfo,NewUser,ACLProcessor::CREATE)) {
 | 
			
		||||
            return UnAuthorized(RESTAPI::Errors::ACCESS_DENIED);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Poco::toLowerInPlace(NewUser.email);
 | 
			
		||||
        if(!Utils::ValidEMailAddress(NewUser.email)) {
 | 
			
		||||
            return BadRequest(RESTAPI::Errors::InvalidEmailAddress);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        SecurityObjects::UserInfo   Existing;
 | 
			
		||||
        if(StorageService()->SubDB().GetUserByEmail(NewUser.email,Existing)) {
 | 
			
		||||
            return BadRequest(RESTAPI::Errors::UserAlreadyExists);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if(!NewUser.currentPassword.empty()) {
 | 
			
		||||
            if(!AuthService()->ValidatePassword(NewUser.currentPassword)) {
 | 
			
		||||
                return BadRequest(RESTAPI::Errors::InvalidPassword);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if(NewUser.name.empty())
 | 
			
		||||
            NewUser.name = NewUser.email;
 | 
			
		||||
 | 
			
		||||
        //  You cannot enable MFA during user creation
 | 
			
		||||
        NewUser.userTypeProprietaryInfo.mfa.enabled = false;
 | 
			
		||||
        NewUser.userTypeProprietaryInfo.mfa.method = "";
 | 
			
		||||
        NewUser.userTypeProprietaryInfo.mobiles.clear();
 | 
			
		||||
        NewUser.userTypeProprietaryInfo.authenticatorSecret.clear();
 | 
			
		||||
        NewUser.validated = true;
 | 
			
		||||
 | 
			
		||||
        if(!StorageService()->UserDB().CreateUser(NewUser.email,NewUser)) {
 | 
			
		||||
            Logger_.information(fmt::format("Could not add user '{}'.",NewUser.email));
 | 
			
		||||
            return BadRequest(RESTAPI::Errors::RecordNotCreated);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if(GetBoolParameter("email_verification")) {
 | 
			
		||||
            if(AuthService::VerifyEmail(NewUser))
 | 
			
		||||
                Logger_.information(fmt::format("Verification e-mail requested for {}",NewUser.email));
 | 
			
		||||
            StorageService()->UserDB().UpdateUserInfo(UserInfo_.userinfo.email,NewUser.id,NewUser);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if(!StorageService()->UserDB().GetUserByEmail(NewUser.email, NewUser)) {
 | 
			
		||||
            Logger_.information(fmt::format("User '{}' but not retrieved.",NewUser.email));
 | 
			
		||||
            return NotFound();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Poco::JSON::Object  UserInfoObject;
 | 
			
		||||
        Sanitize(UserInfo_, NewUser);
 | 
			
		||||
        NewUser.to_json(UserInfoObject);
 | 
			
		||||
        ReturnObject(UserInfoObject);
 | 
			
		||||
        Logger_.information(fmt::format("User '{}' has been added by '{}')",NewUser.email, UserInfo_.userinfo.email));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void RESTAPI_user_handler::DoPut() {
 | 
			
		||||
 | 
			
		||||
        std::string Id = GetBinding("id", "");
 | 
			
		||||
        if(Id.empty()) {
 | 
			
		||||
            return BadRequest(RESTAPI::Errors::MissingUserID);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        SecurityObjects::UserInfo   Existing;
 | 
			
		||||
        if(!StorageService()->UserDB().GetUserById(Id,Existing)) {
 | 
			
		||||
            return NotFound();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if(!ACLProcessor::Can(UserInfo_.userinfo,Existing,ACLProcessor::MODIFY)) {
 | 
			
		||||
            return UnAuthorized(RESTAPI::Errors::ACCESS_DENIED);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if(GetBoolParameter("resetMFA")) {
 | 
			
		||||
            if( (UserInfo_.userinfo.userRole == SecurityObjects::ROOT) ||
 | 
			
		||||
                (UserInfo_.userinfo.userRole == SecurityObjects::ADMIN && Existing.userRole!=SecurityObjects::ROOT) ||
 | 
			
		||||
                (UserInfo_.userinfo.id == Id)) {
 | 
			
		||||
                Existing.userTypeProprietaryInfo.mfa.enabled = false;
 | 
			
		||||
                Existing.userTypeProprietaryInfo.mfa.method.clear();
 | 
			
		||||
                Existing.userTypeProprietaryInfo.mobiles.clear();
 | 
			
		||||
                Existing.modified = OpenWifi::Now();
 | 
			
		||||
                Existing.notes.push_back( SecurityObjects::NoteInfo{
 | 
			
		||||
                            .created=OpenWifi::Now(),
 | 
			
		||||
                            .createdBy=UserInfo_.userinfo.email,
 | 
			
		||||
                            .note="MFA Reset by " + UserInfo_.userinfo.email});
 | 
			
		||||
                StorageService()->UserDB().UpdateUserInfo(UserInfo_.userinfo.email,Id,Existing);
 | 
			
		||||
                SecurityObjects::UserInfo   NewUserInfo;
 | 
			
		||||
                StorageService()->UserDB().GetUserByEmail(UserInfo_.userinfo.email,NewUserInfo);
 | 
			
		||||
                Poco::JSON::Object  ModifiedObject;
 | 
			
		||||
                Sanitize(UserInfo_, NewUserInfo);
 | 
			
		||||
                NewUserInfo.to_json(ModifiedObject);
 | 
			
		||||
                return ReturnObject(ModifiedObject);
 | 
			
		||||
            } else {
 | 
			
		||||
                return UnAuthorized(RESTAPI::Errors::ACCESS_DENIED);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if(GetBoolParameter("forgotPassword")) {
 | 
			
		||||
            Existing.changePassword = true;
 | 
			
		||||
            Logger_.information(fmt::format("FORGOTTEN-PASSWORD({}): Request for {}", Request->clientAddress().toString(), Existing.email));
 | 
			
		||||
            SecurityObjects::ActionLink NewLink;
 | 
			
		||||
 | 
			
		||||
            NewLink.action = OpenWifi::SecurityObjects::LinkActions::FORGOT_PASSWORD;
 | 
			
		||||
            NewLink.id = MicroService::CreateUUID();
 | 
			
		||||
            NewLink.userId = Existing.id;
 | 
			
		||||
            NewLink.created = OpenWifi::Now();
 | 
			
		||||
            NewLink.expires = NewLink.created + (24*60*60);
 | 
			
		||||
            NewLink.userAction = true;
 | 
			
		||||
            StorageService()->ActionLinksDB().CreateAction(NewLink);
 | 
			
		||||
            return OK();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        SecurityObjects::UserInfo   NewUser;
 | 
			
		||||
        const auto & RawObject = ParsedBody_;
 | 
			
		||||
        if(!NewUser.from_json(RawObject)) {
 | 
			
		||||
            return BadRequest(RESTAPI::Errors::InvalidJSONDocument);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // some basic validations
 | 
			
		||||
        if(RawObject->has("userRole") && SecurityObjects::UserTypeFromString(RawObject->get("userRole").toString())==SecurityObjects::UNKNOWN) {
 | 
			
		||||
            return BadRequest(RESTAPI::Errors::InvalidUserRole);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if(RawObject->has("owner")) {
 | 
			
		||||
            if (UserInfo_.userinfo.userRole == SecurityObjects::ROOT && Existing.owner.empty()) {
 | 
			
		||||
                AssignIfPresent(RawObject, "owner", Existing.owner);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // The only valid things to change are: changePassword, name,
 | 
			
		||||
        AssignIfPresent(RawObject,"name", Existing.name);
 | 
			
		||||
        AssignIfPresent(RawObject,"description", Existing.description);
 | 
			
		||||
        AssignIfPresent(RawObject,"location", Existing.location);
 | 
			
		||||
        AssignIfPresent(RawObject,"locale", Existing.locale);
 | 
			
		||||
        AssignIfPresent(RawObject,"changePassword", Existing.changePassword);
 | 
			
		||||
        AssignIfPresent(RawObject,"suspended", Existing.suspended);
 | 
			
		||||
        AssignIfPresent(RawObject,"blackListed", Existing.blackListed);
 | 
			
		||||
 | 
			
		||||
        if(RawObject->has("userRole")) {
 | 
			
		||||
            auto NewRole = SecurityObjects::UserTypeFromString(RawObject->get("userRole").toString());
 | 
			
		||||
            if(NewRole!=Existing.userRole) {
 | 
			
		||||
                if(UserInfo_.userinfo.userRole!=SecurityObjects::ROOT && NewRole==SecurityObjects::ROOT) {
 | 
			
		||||
                    return UnAuthorized(RESTAPI::Errors::ACCESS_DENIED);
 | 
			
		||||
                }
 | 
			
		||||
                if(Id==UserInfo_.userinfo.id) {
 | 
			
		||||
                    return UnAuthorized(RESTAPI::Errors::ACCESS_DENIED);
 | 
			
		||||
                }
 | 
			
		||||
                Existing.userRole = NewRole;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if(RawObject->has("notes")) {
 | 
			
		||||
            SecurityObjects::NoteInfoVec NIV;
 | 
			
		||||
            NIV = RESTAPI_utils::to_object_array<SecurityObjects::NoteInfo>(RawObject->get("notes").toString());
 | 
			
		||||
            for(auto const &i:NIV) {
 | 
			
		||||
                SecurityObjects::NoteInfo   ii{.created=(uint64_t)OpenWifi::Now(), .createdBy=UserInfo_.userinfo.email, .note=i.note};
 | 
			
		||||
                Existing.notes.push_back(ii);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        if(RawObject->has("currentPassword")) {
 | 
			
		||||
            if(!AuthService()->ValidatePassword(RawObject->get("currentPassword").toString())) {
 | 
			
		||||
                return BadRequest(RESTAPI::Errors::InvalidPassword);
 | 
			
		||||
            }
 | 
			
		||||
            if(!AuthService()->SetPassword(RawObject->get("currentPassword").toString(),Existing)) {
 | 
			
		||||
                return BadRequest(RESTAPI::Errors::PasswordRejected);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if(GetBoolParameter("email_verification")) {
 | 
			
		||||
            if(AuthService::VerifyEmail(Existing))
 | 
			
		||||
                Logger_.information(fmt::format("Verification e-mail requested for {}",Existing.email));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if(RawObject->has("userTypeProprietaryInfo")) {
 | 
			
		||||
            if(NewUser.userTypeProprietaryInfo.mfa.enabled) {
 | 
			
		||||
                if (!MFAMETHODS::Validate(NewUser.userTypeProprietaryInfo.mfa.method)) {
 | 
			
		||||
                    return BadRequest(RESTAPI::Errors::BadMFAMethod);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if( NewUser.userTypeProprietaryInfo.mfa.enabled &&
 | 
			
		||||
                    NewUser.userTypeProprietaryInfo.mfa.method == MFAMETHODS::SMS &&
 | 
			
		||||
                    !SMSSender()->Enabled()) {
 | 
			
		||||
                    return BadRequest(RESTAPI::Errors::SMSMFANotEnabled);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                if( NewUser.userTypeProprietaryInfo.mfa.enabled &&
 | 
			
		||||
                    NewUser.userTypeProprietaryInfo.mfa.method == MFAMETHODS::EMAIL &&
 | 
			
		||||
                    !SMTPMailerService()->Enabled()) {
 | 
			
		||||
                    return BadRequest(RESTAPI::Errors::EMailMFANotEnabled);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                Existing.userTypeProprietaryInfo.mfa.method = NewUser.userTypeProprietaryInfo.mfa.method;
 | 
			
		||||
                Existing.userTypeProprietaryInfo.mfa.enabled = true;
 | 
			
		||||
 | 
			
		||||
                if (NewUser.userTypeProprietaryInfo.mfa.method == MFAMETHODS::SMS) {
 | 
			
		||||
                    if(NewUser.userTypeProprietaryInfo.mobiles.empty()) {
 | 
			
		||||
                        return BadRequest(RESTAPI::Errors::NeedMobileNumber);
 | 
			
		||||
                    }
 | 
			
		||||
                    if (!SMSSender()->IsNumberValid(NewUser.userTypeProprietaryInfo.mobiles[0].number,UserInfo_.userinfo.email)) {
 | 
			
		||||
                        return BadRequest(RESTAPI::Errors::NeedMobileNumber);
 | 
			
		||||
                    }
 | 
			
		||||
                    Existing.userTypeProprietaryInfo.mobiles = NewUser.userTypeProprietaryInfo.mobiles;
 | 
			
		||||
                    Existing.userTypeProprietaryInfo.mobiles[0].verified = true;
 | 
			
		||||
                    Existing.userTypeProprietaryInfo.authenticatorSecret.clear();
 | 
			
		||||
                } else if (NewUser.userTypeProprietaryInfo.mfa.method == MFAMETHODS::AUTHENTICATOR) {
 | 
			
		||||
                    std::string Secret;
 | 
			
		||||
                    Existing.userTypeProprietaryInfo.mobiles.clear();
 | 
			
		||||
                    if(Existing.userTypeProprietaryInfo.authenticatorSecret.empty() && TotpCache()->CompleteValidation(UserInfo_.userinfo,false,Secret)) {
 | 
			
		||||
                        Existing.userTypeProprietaryInfo.authenticatorSecret = Secret;
 | 
			
		||||
                    } else if (!Existing.userTypeProprietaryInfo.authenticatorSecret.empty()) {
 | 
			
		||||
                        // we allow someone to use their old secret
 | 
			
		||||
                    } else {
 | 
			
		||||
                        return BadRequest(RESTAPI::Errors::AuthenticatorVerificationIncomplete);
 | 
			
		||||
                    }
 | 
			
		||||
                } else if (NewUser.userTypeProprietaryInfo.mfa.method == MFAMETHODS::EMAIL) {
 | 
			
		||||
                    Existing.userTypeProprietaryInfo.mobiles.clear();
 | 
			
		||||
                    Existing.userTypeProprietaryInfo.authenticatorSecret.clear();
 | 
			
		||||
                }
 | 
			
		||||
            } else {
 | 
			
		||||
                Existing.userTypeProprietaryInfo.authenticatorSecret.clear();
 | 
			
		||||
                Existing.userTypeProprietaryInfo.mobiles.clear();
 | 
			
		||||
                Existing.userTypeProprietaryInfo.mfa.enabled = false;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Existing.modified = OpenWifi::Now();
 | 
			
		||||
        if(StorageService()->UserDB().UpdateUserInfo(UserInfo_.userinfo.email,Id,Existing)) {
 | 
			
		||||
            SecurityObjects::UserInfo   NewUserInfo;
 | 
			
		||||
            StorageService()->UserDB().GetUserByEmail(UserInfo_.userinfo.email,NewUserInfo);
 | 
			
		||||
            Poco::JSON::Object  ModifiedObject;
 | 
			
		||||
            Sanitize(UserInfo_, NewUserInfo);
 | 
			
		||||
            NewUserInfo.to_json(ModifiedObject);
 | 
			
		||||
            return ReturnObject(ModifiedObject);
 | 
			
		||||
        }
 | 
			
		||||
        BadRequest(RESTAPI::Errors::RecordNotUpdated);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -2,15 +2,14 @@
 | 
			
		||||
// Created by stephane bourque on 2021-06-21.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#ifndef UCENTRALSEC_RESTAPI_USER_HANDLER_H
 | 
			
		||||
#define UCENTRALSEC_RESTAPI_USER_HANDLER_H
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "RESTAPI_handler.h"
 | 
			
		||||
#include "framework/MicroService.h"
 | 
			
		||||
 | 
			
		||||
namespace OpenWifi {
 | 
			
		||||
    class RESTAPI_user_handler : public RESTAPIHandler {
 | 
			
		||||
    public:
 | 
			
		||||
        RESTAPI_user_handler(const RESTAPIHandler::BindingMap &bindings, Poco::Logger &L, RESTAPI_GenericServer &Server, bool Internal)
 | 
			
		||||
        RESTAPI_user_handler(const RESTAPIHandler::BindingMap &bindings, Poco::Logger &L, RESTAPI_GenericServer &Server, uint64_t TransactionId, bool Internal)
 | 
			
		||||
                : RESTAPIHandler(bindings, L,
 | 
			
		||||
                                 std::vector<std::string>
 | 
			
		||||
                                         {Poco::Net::HTTPRequest::HTTP_POST,
 | 
			
		||||
@@ -19,8 +18,9 @@ namespace OpenWifi {
 | 
			
		||||
                                          Poco::Net::HTTPRequest::HTTP_DELETE,
 | 
			
		||||
                                          Poco::Net::HTTPRequest::HTTP_OPTIONS},
 | 
			
		||||
                                          Server,
 | 
			
		||||
                                          TransactionId,
 | 
			
		||||
                                          Internal) {}
 | 
			
		||||
        static const std::list<const char *> PathName() { return std::list<const char *>{"/api/v1/user/{id}"}; };
 | 
			
		||||
        static auto PathName() { return std::list<std::string>{"/api/v1/user/{id}"}; };
 | 
			
		||||
        void DoGet() final;
 | 
			
		||||
        void DoPost() final;
 | 
			
		||||
        void DoDelete() final;
 | 
			
		||||
@@ -29,6 +29,3 @@ namespace OpenWifi {
 | 
			
		||||
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#endif //UCENTRALSEC_RESTAPI_USER_HANDLER_H
 | 
			
		||||
							
								
								
									
										58
									
								
								src/RESTAPI/RESTAPI_users_handler.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								src/RESTAPI/RESTAPI_users_handler.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,58 @@
 | 
			
		||||
//
 | 
			
		||||
// Created by stephane bourque on 2021-06-21.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#include "RESTAPI_users_handler.h"
 | 
			
		||||
#include "StorageService.h"
 | 
			
		||||
#include "framework/MicroService.h"
 | 
			
		||||
#include "RESTAPI/RESTAPI_db_helpers.h"
 | 
			
		||||
 | 
			
		||||
namespace OpenWifi {
 | 
			
		||||
    void RESTAPI_users_handler::DoGet() {
 | 
			
		||||
        bool IdOnly = (GetParameter("idOnly","false")=="true");
 | 
			
		||||
        auto nameSearch = GetParameter("nameSearch");
 | 
			
		||||
        auto emailSearch = GetParameter("emailSearch");
 | 
			
		||||
 | 
			
		||||
        std::string baseQuery;
 | 
			
		||||
        if(!nameSearch.empty() || !emailSearch.empty()) {
 | 
			
		||||
            if(!nameSearch.empty())
 | 
			
		||||
                baseQuery = fmt::format(" Lower(name) like('%{}%') ", Poco::toLower(nameSearch) );
 | 
			
		||||
            if(!emailSearch.empty())
 | 
			
		||||
                baseQuery += baseQuery.empty() ? fmt::format(" Lower(email) like('%{}%') ", Poco::toLower(emailSearch))
 | 
			
		||||
                                               : fmt::format(" and Lower(email) like('%{}%') ", Poco::toLower(emailSearch));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if(QB_.Select.empty()) {
 | 
			
		||||
            SecurityObjects::UserInfoList   Users;
 | 
			
		||||
            if(StorageService()->UserDB().GetUsers(QB_.Offset, QB_.Limit, Users.users, baseQuery)) {
 | 
			
		||||
                for (auto &i : Users.users) {
 | 
			
		||||
                    Sanitize(UserInfo_, i);
 | 
			
		||||
                }
 | 
			
		||||
                if(IdOnly) {
 | 
			
		||||
                    Poco::JSON::Array   Arr;
 | 
			
		||||
                    for(const auto &i:Users.users)
 | 
			
		||||
                        Arr.add(i.id);
 | 
			
		||||
                    Poco::JSON::Object  Answer;
 | 
			
		||||
                    Answer.set("users", Arr);
 | 
			
		||||
                    return ReturnObject(Answer);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            Poco::JSON::Object  Answer;
 | 
			
		||||
            Users.to_json(Answer);
 | 
			
		||||
            return ReturnObject(Answer);
 | 
			
		||||
        } else {
 | 
			
		||||
            SecurityObjects::UserInfoList   Users;
 | 
			
		||||
            for(auto &i:SelectedRecords()) {
 | 
			
		||||
                SecurityObjects::UserInfo   UInfo;
 | 
			
		||||
                if(StorageService()->UserDB().GetUserById(i,UInfo)) {
 | 
			
		||||
                    Poco::JSON::Object Obj;
 | 
			
		||||
                    Sanitize(UserInfo_, UInfo);
 | 
			
		||||
                    Users.users.emplace_back(UInfo);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            Poco::JSON::Object Answer;
 | 
			
		||||
            Users.to_json(Answer);
 | 
			
		||||
            return ReturnObject(Answer);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -2,22 +2,22 @@
 | 
			
		||||
// Created by stephane bourque on 2021-06-21.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#ifndef UCENTRALSEC_RESTAPI_USERS_HANDLER_H
 | 
			
		||||
#define UCENTRALSEC_RESTAPI_USERS_HANDLER_H
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "RESTAPI_handler.h"
 | 
			
		||||
#include "framework/MicroService.h"
 | 
			
		||||
 | 
			
		||||
namespace OpenWifi {
 | 
			
		||||
    class RESTAPI_users_handler : public RESTAPIHandler {
 | 
			
		||||
    public:
 | 
			
		||||
        RESTAPI_users_handler(const RESTAPIHandler::BindingMap &bindings, Poco::Logger &L, RESTAPI_GenericServer &Server, bool Internal)
 | 
			
		||||
        RESTAPI_users_handler(const RESTAPIHandler::BindingMap &bindings, Poco::Logger &L, RESTAPI_GenericServer &Server, uint64_t TransactionId, bool Internal)
 | 
			
		||||
                : RESTAPIHandler(bindings, L,
 | 
			
		||||
                                 std::vector<std::string>
 | 
			
		||||
                                 {Poco::Net::HTTPRequest::HTTP_GET,
 | 
			
		||||
                                  Poco::Net::HTTPRequest::HTTP_OPTIONS},
 | 
			
		||||
                                  Server,
 | 
			
		||||
                                  TransactionId,
 | 
			
		||||
                                  Internal) {}
 | 
			
		||||
        static const std::list<const char *> PathName() { return std::list<const char *>{"/api/v1/users"}; };
 | 
			
		||||
        static auto PathName() { return std::list<std::string>{"/api/v1/users"}; };
 | 
			
		||||
        void DoGet() final;
 | 
			
		||||
        void DoPost() final {};
 | 
			
		||||
        void DoDelete() final {};
 | 
			
		||||
@@ -25,5 +25,3 @@ namespace OpenWifi {
 | 
			
		||||
    };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#endif //UCENTRALSEC_RESTAPI_USERS_HANDLER_H
 | 
			
		||||
							
								
								
									
										26
									
								
								src/RESTAPI/RESTAPI_validate_sub_token_handler.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								src/RESTAPI/RESTAPI_validate_sub_token_handler.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,26 @@
 | 
			
		||||
//
 | 
			
		||||
// Created by stephane bourque on 2021-11-30.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#include "RESTAPI_validate_sub_token_handler.h"
 | 
			
		||||
#include "AuthService.h"
 | 
			
		||||
 | 
			
		||||
namespace OpenWifi {
 | 
			
		||||
    void RESTAPI_validate_sub_token_handler::DoGet() {
 | 
			
		||||
        Poco::URI URI(Request->getURI());
 | 
			
		||||
        auto Parameters = URI.getQueryParameters();
 | 
			
		||||
        for(auto const &i:Parameters) {
 | 
			
		||||
            if (i.first == "token") {
 | 
			
		||||
                //  can we find this token?
 | 
			
		||||
                SecurityObjects::UserInfoAndPolicy SecObj;
 | 
			
		||||
                bool Expired = false;
 | 
			
		||||
                if (AuthService()->IsValidSubToken(i.second, SecObj.webtoken, SecObj.userinfo, Expired)) {
 | 
			
		||||
                    Poco::JSON::Object Obj;
 | 
			
		||||
                    SecObj.to_json(Obj);
 | 
			
		||||
                    return ReturnObject(Obj);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return NotFound();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										26
									
								
								src/RESTAPI/RESTAPI_validate_sub_token_handler.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								src/RESTAPI/RESTAPI_validate_sub_token_handler.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,26 @@
 | 
			
		||||
//
 | 
			
		||||
// Created by stephane bourque on 2021-11-30.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "framework/MicroService.h"
 | 
			
		||||
 | 
			
		||||
namespace OpenWifi {
 | 
			
		||||
    class RESTAPI_validate_sub_token_handler : public RESTAPIHandler {
 | 
			
		||||
    public:
 | 
			
		||||
        RESTAPI_validate_sub_token_handler(const RESTAPIHandler::BindingMap &bindings, Poco::Logger &L, RESTAPI_GenericServer &Server, uint64_t TransactionId, bool Internal)
 | 
			
		||||
        : RESTAPIHandler(bindings, L,
 | 
			
		||||
                         std::vector<std::string>
 | 
			
		||||
                         {Poco::Net::HTTPRequest::HTTP_GET,
 | 
			
		||||
                          Poco::Net::HTTPRequest::HTTP_OPTIONS},
 | 
			
		||||
                          Server,
 | 
			
		||||
                          TransactionId,
 | 
			
		||||
                          Internal) {};
 | 
			
		||||
        static auto PathName() { return std::list<std::string>{"/api/v1/validateSubToken"}; };
 | 
			
		||||
        void DoGet() final;
 | 
			
		||||
        void DoPost() final {};
 | 
			
		||||
        void DoDelete() final {};
 | 
			
		||||
        void DoPut() final {};
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
@@ -2,27 +2,25 @@
 | 
			
		||||
// Created by stephane bourque on 2021-07-01.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#include "RESTAPI_validateToken_handler.h"
 | 
			
		||||
#include "Daemon.h"
 | 
			
		||||
#include "RESTAPI_validate_token_handler.h"
 | 
			
		||||
#include "AuthService.h"
 | 
			
		||||
#include "Utils.h"
 | 
			
		||||
 | 
			
		||||
namespace OpenWifi {
 | 
			
		||||
    void RESTAPI_validateToken_handler::DoGet() {
 | 
			
		||||
    void RESTAPI_validate_token_handler::DoGet() {
 | 
			
		||||
        Poco::URI URI(Request->getURI());
 | 
			
		||||
        auto Parameters = URI.getQueryParameters();
 | 
			
		||||
        for(auto const &i:Parameters) {
 | 
			
		||||
            if (i.first == "token") {
 | 
			
		||||
                //  can we find this token?
 | 
			
		||||
                SecurityObjects::UserInfoAndPolicy SecObj;
 | 
			
		||||
                if (AuthService()->IsValidToken(i.second, SecObj.webtoken, SecObj.userinfo)) {
 | 
			
		||||
                bool Expired = false;
 | 
			
		||||
                if (AuthService()->IsValidToken(i.second, SecObj.webtoken, SecObj.userinfo, Expired)) {
 | 
			
		||||
                    Poco::JSON::Object Obj;
 | 
			
		||||
                    SecObj.to_json(Obj);
 | 
			
		||||
                    ReturnObject(Obj);
 | 
			
		||||
                    return;
 | 
			
		||||
                    return ReturnObject(Obj);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        NotFound();
 | 
			
		||||
        return NotFound();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -2,22 +2,22 @@
 | 
			
		||||
// Created by stephane bourque on 2021-07-01.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#ifndef UCENTRALSEC_RESTAPI_VALIDATETOKEN_HANDLER_H
 | 
			
		||||
#define UCENTRALSEC_RESTAPI_VALIDATETOKEN_HANDLER_H
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "RESTAPI_handler.h"
 | 
			
		||||
#include "framework/MicroService.h"
 | 
			
		||||
 | 
			
		||||
namespace OpenWifi {
 | 
			
		||||
    class RESTAPI_validateToken_handler : public RESTAPIHandler {
 | 
			
		||||
    class RESTAPI_validate_token_handler : public RESTAPIHandler {
 | 
			
		||||
    public:
 | 
			
		||||
        RESTAPI_validateToken_handler(const RESTAPIHandler::BindingMap &bindings, Poco::Logger &L, RESTAPI_GenericServer &Server, bool Internal)
 | 
			
		||||
        RESTAPI_validate_token_handler(const RESTAPIHandler::BindingMap &bindings, Poco::Logger &L, RESTAPI_GenericServer &Server, uint64_t TransactionId, bool Internal)
 | 
			
		||||
                : RESTAPIHandler(bindings, L,
 | 
			
		||||
                                 std::vector<std::string>
 | 
			
		||||
                                         {Poco::Net::HTTPRequest::HTTP_GET,
 | 
			
		||||
                                          Poco::Net::HTTPRequest::HTTP_OPTIONS},
 | 
			
		||||
                                          Server,
 | 
			
		||||
                                          TransactionId,
 | 
			
		||||
                                          Internal) {};
 | 
			
		||||
        static const std::list<const char *> PathName() { return std::list<const char *>{"/api/v1/validateToken"}; };
 | 
			
		||||
        static auto PathName() { return std::list<std::string>{"/api/v1/validateToken"}; };
 | 
			
		||||
        void DoGet() final;
 | 
			
		||||
        void DoPost() final {};
 | 
			
		||||
        void DoDelete() final {};
 | 
			
		||||
@@ -25,4 +25,3 @@ namespace OpenWifi {
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#endif //UCENTRALSEC_RESTAPI_VALIDATETOKEN_HANDLER_H
 | 
			
		||||
@@ -1,5 +0,0 @@
 | 
			
		||||
//
 | 
			
		||||
// Created by stephane bourque on 2021-09-15.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#include "RESTAPI_GenericServer.h"
 | 
			
		||||
@@ -1,78 +0,0 @@
 | 
			
		||||
//
 | 
			
		||||
// Created by stephane bourque on 2021-09-15.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#ifndef OWPROV_RESTAPI_GENERICSERVER_H
 | 
			
		||||
#define OWPROV_RESTAPI_GENERICSERVER_H
 | 
			
		||||
 | 
			
		||||
#include <vector>
 | 
			
		||||
#include <string>
 | 
			
		||||
 | 
			
		||||
#include "Daemon.h"
 | 
			
		||||
#include "Poco/StringTokenizer.h"
 | 
			
		||||
#include "Poco/Net/HTTPRequest.h"
 | 
			
		||||
 | 
			
		||||
namespace OpenWifi {
 | 
			
		||||
 | 
			
		||||
    class RESTAPI_GenericServer {
 | 
			
		||||
    public:
 | 
			
		||||
 | 
			
		||||
        enum {
 | 
			
		||||
            LOG_GET=0,
 | 
			
		||||
            LOG_DELETE,
 | 
			
		||||
            LOG_PUT,
 | 
			
		||||
            LOG_POST
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        void inline SetFlags(bool External, const std::string &Methods) {
 | 
			
		||||
            Poco::StringTokenizer   Tokens(Methods,",");
 | 
			
		||||
            auto Offset = (External ? 0 : 4);
 | 
			
		||||
            for(const auto &i:Tokens) {
 | 
			
		||||
                if(Poco::icompare(i,Poco::Net::HTTPRequest::HTTP_DELETE)==0)
 | 
			
		||||
                    LogFlags_[Offset+LOG_DELETE]=true;
 | 
			
		||||
                else if(Poco::icompare(i,Poco::Net::HTTPRequest::HTTP_PUT)==0)
 | 
			
		||||
                    LogFlags_[Offset+LOG_PUT]=true;
 | 
			
		||||
                else if(Poco::icompare(i,Poco::Net::HTTPRequest::HTTP_POST)==0)
 | 
			
		||||
                    LogFlags_[Offset+LOG_POST]=true;
 | 
			
		||||
                else if(Poco::icompare(i,Poco::Net::HTTPRequest::HTTP_GET)==0)
 | 
			
		||||
                    LogFlags_[Offset+LOG_GET]=true;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        inline void InitLogging() {
 | 
			
		||||
            std::string Public = Daemon()->ConfigGetString("apilogging.public.methods","PUT,POST,DELETE");
 | 
			
		||||
            SetFlags(true, Public);
 | 
			
		||||
            std::string Private = Daemon()->ConfigGetString("apilogging.private.methods","PUT,POST,DELETE");
 | 
			
		||||
            SetFlags(false, Private);
 | 
			
		||||
 | 
			
		||||
            std::string PublicBadTokens = Daemon()->ConfigGetString("apilogging.public.badtokens.methods","");
 | 
			
		||||
            LogBadTokens_[0] = (Poco::icompare(PublicBadTokens,"true")==0);
 | 
			
		||||
            std::string PrivateBadTokens = Daemon()->ConfigGetString("apilogging.private.badtokens.methods","");
 | 
			
		||||
            LogBadTokens_[1] = (Poco::icompare(PrivateBadTokens,"true")==0);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [[nodiscard]] inline bool LogIt(const std::string &Method, bool External) const {
 | 
			
		||||
            auto Offset = (External ? 0 : 4);
 | 
			
		||||
            if(Method == Poco::Net::HTTPRequest::HTTP_GET)
 | 
			
		||||
                return LogFlags_[Offset+LOG_GET];
 | 
			
		||||
            if(Method == Poco::Net::HTTPRequest::HTTP_POST)
 | 
			
		||||
                return LogFlags_[Offset+LOG_POST];
 | 
			
		||||
            if(Method == Poco::Net::HTTPRequest::HTTP_PUT)
 | 
			
		||||
                return LogFlags_[Offset+LOG_PUT];
 | 
			
		||||
            if(Method == Poco::Net::HTTPRequest::HTTP_DELETE)
 | 
			
		||||
                return LogFlags_[Offset+LOG_DELETE];
 | 
			
		||||
            return false;
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        [[nodiscard]] inline bool LogBadTokens(bool External) const {
 | 
			
		||||
            return LogBadTokens_[ (External ? 0 : 1) ];
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
    private:
 | 
			
		||||
        std::array<bool,8>       LogFlags_{false};
 | 
			
		||||
        std::array<bool,2>       LogBadTokens_{false};
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#endif //OWPROV_RESTAPI_GENERICSERVER_H
 | 
			
		||||
@@ -1,80 +0,0 @@
 | 
			
		||||
//
 | 
			
		||||
// Created by stephane bourque on 2021-06-29.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#include "Poco/URI.h"
 | 
			
		||||
 | 
			
		||||
#include "RESTAPI_system_command.h"
 | 
			
		||||
#include "RESTAPI_user_handler.h"
 | 
			
		||||
#include "RESTAPI_users_handler.h"
 | 
			
		||||
#include "RESTAPI_action_links.h"
 | 
			
		||||
#include "RESTAPI_validateToken_handler.h"
 | 
			
		||||
#include "RESTAPI_InternalServer.h"
 | 
			
		||||
 | 
			
		||||
#include "Utils.h"
 | 
			
		||||
 | 
			
		||||
namespace OpenWifi {
 | 
			
		||||
 | 
			
		||||
    class RESTAPI_InternalServer *RESTAPI_InternalServer::instance_ = nullptr;
 | 
			
		||||
 | 
			
		||||
    RESTAPI_InternalServer::RESTAPI_InternalServer() noexcept:
 | 
			
		||||
        SubSystemServer("RESTAPIInternalServer", "REST-ISRV", "openwifi.internal.restapi")
 | 
			
		||||
    {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    int RESTAPI_InternalServer::Start() {
 | 
			
		||||
        Logger_.information("Starting.");
 | 
			
		||||
        Server_.InitLogging();
 | 
			
		||||
 | 
			
		||||
        for(const auto & Svr: ConfigServersList_) {
 | 
			
		||||
            Logger_.information(Poco::format("Starting: %s:%s Keyfile:%s CertFile: %s", Svr.Address(), std::to_string(Svr.Port()),
 | 
			
		||||
                                             Svr.KeyFile(),Svr.CertFile()));
 | 
			
		||||
 | 
			
		||||
            auto Sock{Svr.CreateSecureSocket(Logger_)};
 | 
			
		||||
 | 
			
		||||
            Svr.LogCert(Logger_);
 | 
			
		||||
            if(!Svr.RootCA().empty())
 | 
			
		||||
                Svr.LogCas(Logger_);
 | 
			
		||||
 | 
			
		||||
            auto Params = new Poco::Net::HTTPServerParams;
 | 
			
		||||
            Params->setMaxThreads(50);
 | 
			
		||||
            Params->setMaxQueued(200);
 | 
			
		||||
            Params->setKeepAlive(true);
 | 
			
		||||
 | 
			
		||||
            auto NewServer = std::make_unique<Poco::Net::HTTPServer>(new InternalRequestHandlerFactory(Server_), Pool_, Sock, Params);
 | 
			
		||||
            NewServer->start();
 | 
			
		||||
            RESTServers_.push_back(std::move(NewServer));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void RESTAPI_InternalServer::Stop() {
 | 
			
		||||
        Logger_.information("Stopping ");
 | 
			
		||||
        for( const auto & svr : RESTServers_ )
 | 
			
		||||
            svr->stop();
 | 
			
		||||
        RESTServers_.clear();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void RESTAPI_InternalServer::reinitialize(Poco::Util::Application &self) {
 | 
			
		||||
        Daemon()->LoadConfigurationFile();
 | 
			
		||||
        Logger_.information("Reinitializing.");
 | 
			
		||||
        Stop();
 | 
			
		||||
        Start();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Poco::Net::HTTPRequestHandler *InternalRequestHandlerFactory::createRequestHandler(const Poco::Net::HTTPServerRequest & Request) {
 | 
			
		||||
        Poco::URI uri(Request.getURI());
 | 
			
		||||
        const auto & Path = uri.getPath();
 | 
			
		||||
        RESTAPIHandler::BindingMap Bindings;
 | 
			
		||||
        return RESTAPI_Router_I<
 | 
			
		||||
                RESTAPI_users_handler,
 | 
			
		||||
                RESTAPI_user_handler,
 | 
			
		||||
                RESTAPI_system_command,
 | 
			
		||||
                RESTAPI_action_links,
 | 
			
		||||
                RESTAPI_validateToken_handler
 | 
			
		||||
        >(Path,Bindings,Logger_, Server_);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -1,57 +0,0 @@
 | 
			
		||||
//
 | 
			
		||||
// Created by stephane bourque on 2021-06-29.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#ifndef UCENTRALSEC_RESTAPI_INTERNALSERVER_H
 | 
			
		||||
#define UCENTRALSEC_RESTAPI_INTERNALSERVER_H
 | 
			
		||||
 | 
			
		||||
#include "SubSystemServer.h"
 | 
			
		||||
#include "Poco/Net/HTTPServer.h"
 | 
			
		||||
#include "Poco/Net/HTTPRequestHandler.h"
 | 
			
		||||
#include "Poco/Net/HTTPRequestHandlerFactory.h"
 | 
			
		||||
#include "Poco/Net/HTTPServerRequest.h"
 | 
			
		||||
#include "Poco/Net/NetException.h"
 | 
			
		||||
#include "RESTAPI_GenericServer.h"
 | 
			
		||||
 | 
			
		||||
namespace OpenWifi {
 | 
			
		||||
 | 
			
		||||
    class RESTAPI_InternalServer : public SubSystemServer {
 | 
			
		||||
        public:
 | 
			
		||||
            RESTAPI_InternalServer() noexcept;
 | 
			
		||||
 | 
			
		||||
            static RESTAPI_InternalServer *instance() {
 | 
			
		||||
                if (instance_ == nullptr) {
 | 
			
		||||
                    instance_ = new RESTAPI_InternalServer;
 | 
			
		||||
                }
 | 
			
		||||
                return instance_;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            int Start() override;
 | 
			
		||||
            void Stop() override;
 | 
			
		||||
            void reinitialize(Poco::Util::Application &self) override;
 | 
			
		||||
 | 
			
		||||
        private:
 | 
			
		||||
            static RESTAPI_InternalServer *instance_;
 | 
			
		||||
            std::vector<std::unique_ptr<Poco::Net::HTTPServer>>   RESTServers_;
 | 
			
		||||
            Poco::ThreadPool	    Pool_;
 | 
			
		||||
            RESTAPI_GenericServer   Server_;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    inline RESTAPI_InternalServer * RESTAPI_InternalServer() { return RESTAPI_InternalServer::instance(); };
 | 
			
		||||
 | 
			
		||||
    class InternalRequestHandlerFactory : public Poco::Net::HTTPRequestHandlerFactory {
 | 
			
		||||
        public:
 | 
			
		||||
        explicit InternalRequestHandlerFactory(RESTAPI_GenericServer & Server) :
 | 
			
		||||
                    Logger_(RESTAPI_InternalServer()->Logger()),
 | 
			
		||||
                    Server_(Server){}
 | 
			
		||||
 | 
			
		||||
            Poco::Net::HTTPRequestHandler *createRequestHandler(const Poco::Net::HTTPServerRequest &request) override;
 | 
			
		||||
        private:
 | 
			
		||||
            Poco::Logger    & Logger_;
 | 
			
		||||
            RESTAPI_GenericServer & Server_;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
} //   namespace
 | 
			
		||||
 | 
			
		||||
#endif //UCENTRALSEC_RESTAPI_INTERNALSERVER_H
 | 
			
		||||
@@ -1,181 +0,0 @@
 | 
			
		||||
//
 | 
			
		||||
//	License type: BSD 3-Clause License
 | 
			
		||||
//	License copy: https://github.com/Telecominfraproject/wlan-cloud-ucentralgw/blob/master/LICENSE
 | 
			
		||||
//
 | 
			
		||||
//	Created by Stephane Bourque on 2021-03-04.
 | 
			
		||||
//	Arilia Wireless Inc.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#ifndef UCENTRAL_RESTAPI_SECURITYOBJECTS_H
 | 
			
		||||
#define UCENTRAL_RESTAPI_SECURITYOBJECTS_H
 | 
			
		||||
 | 
			
		||||
#include "Poco/JSON/Object.h"
 | 
			
		||||
#include "OpenWifiTypes.h"
 | 
			
		||||
 | 
			
		||||
namespace OpenWifi::SecurityObjects {
 | 
			
		||||
 | 
			
		||||
	struct AclTemplate {
 | 
			
		||||
		bool Read_ = true;
 | 
			
		||||
		bool ReadWrite_ = true;
 | 
			
		||||
		bool ReadWriteCreate_ = true;
 | 
			
		||||
		bool Delete_ = true;
 | 
			
		||||
		bool PortalLogin_ = true;
 | 
			
		||||
 | 
			
		||||
		void to_json(Poco::JSON::Object &Obj) const;
 | 
			
		||||
		bool from_json(const Poco::JSON::Object::Ptr &Obj);	};
 | 
			
		||||
 | 
			
		||||
	struct WebToken {
 | 
			
		||||
		std::string access_token_;
 | 
			
		||||
		std::string refresh_token_;
 | 
			
		||||
		std::string id_token_;
 | 
			
		||||
		std::string token_type_;
 | 
			
		||||
		std::string username_;
 | 
			
		||||
		bool userMustChangePassword=false;
 | 
			
		||||
        uint64_t errorCode=0;
 | 
			
		||||
		uint64_t expires_in_=0;
 | 
			
		||||
		uint64_t idle_timeout_=0;
 | 
			
		||||
		AclTemplate acl_template_;
 | 
			
		||||
		uint64_t created_=0;
 | 
			
		||||
 | 
			
		||||
		void to_json(Poco::JSON::Object &Obj) const;
 | 
			
		||||
		bool from_json(const Poco::JSON::Object::Ptr &Obj);
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
    enum USER_ROLE {
 | 
			
		||||
        UNKNOWN, ROOT, ADMIN, SUBSCRIBER, CSR, SYSTEM, SPECIAL
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    USER_ROLE UserTypeFromString(const std::string &U);
 | 
			
		||||
    std::string UserTypeToString(USER_ROLE U);
 | 
			
		||||
 | 
			
		||||
    struct NoteInfo {
 | 
			
		||||
		uint64_t created = std::time(nullptr);
 | 
			
		||||
		std::string createdBy;
 | 
			
		||||
		std::string note;
 | 
			
		||||
		void to_json(Poco::JSON::Object &Obj) const;
 | 
			
		||||
		bool from_json(Poco::JSON::Object::Ptr Obj);
 | 
			
		||||
	};
 | 
			
		||||
	typedef std::vector<NoteInfo>	NoteInfoVec;
 | 
			
		||||
 | 
			
		||||
	struct UserInfo {
 | 
			
		||||
        std::string Id;
 | 
			
		||||
		std::string name;
 | 
			
		||||
		std::string description;
 | 
			
		||||
		std::string avatar;
 | 
			
		||||
		std::string email;
 | 
			
		||||
		bool validated = false;
 | 
			
		||||
		std::string validationEmail;
 | 
			
		||||
		uint64_t validationDate = 0;
 | 
			
		||||
		uint64_t creationDate = 0;
 | 
			
		||||
		std::string validationURI;
 | 
			
		||||
		bool changePassword = false;
 | 
			
		||||
		uint64_t lastLogin = 0;
 | 
			
		||||
		std::string currentLoginURI;
 | 
			
		||||
		uint64_t lastPasswordChange = 0;
 | 
			
		||||
		uint64_t lastEmailCheck = 0;
 | 
			
		||||
		bool waitingForEmailCheck = false;
 | 
			
		||||
		std::string locale;
 | 
			
		||||
		NoteInfoVec notes;
 | 
			
		||||
		std::string location;
 | 
			
		||||
		std::string owner;
 | 
			
		||||
		bool suspended = false;
 | 
			
		||||
		bool blackListed = false;
 | 
			
		||||
        USER_ROLE userRole;
 | 
			
		||||
		std::string userTypeProprietaryInfo;
 | 
			
		||||
		std::string securityPolicy;
 | 
			
		||||
		uint64_t securityPolicyChange = 0 ;
 | 
			
		||||
		std::string currentPassword;
 | 
			
		||||
		Types::StringVec lastPasswords;
 | 
			
		||||
		std::string oauthType;
 | 
			
		||||
		std::string oauthUserInfo;
 | 
			
		||||
 | 
			
		||||
		void to_json(Poco::JSON::Object &Obj) const;
 | 
			
		||||
		bool from_json(const Poco::JSON::Object::Ptr &Obj);
 | 
			
		||||
	};
 | 
			
		||||
	typedef std::vector<UserInfo>   UserInfoVec;
 | 
			
		||||
 | 
			
		||||
	bool append_from_json(Poco::JSON::Object::Ptr Obj, const UserInfo &UInfo, NoteInfoVec & Notes);
 | 
			
		||||
 | 
			
		||||
	struct InternalServiceInfo {
 | 
			
		||||
		std::string privateURI;
 | 
			
		||||
		std::string publicURI;
 | 
			
		||||
		std::string token;
 | 
			
		||||
		void to_json(Poco::JSON::Object &Obj) const;
 | 
			
		||||
		bool from_json(const Poco::JSON::Object::Ptr &Obj);
 | 
			
		||||
	};
 | 
			
		||||
	typedef std::vector<InternalServiceInfo>	InternalServiceInfoVec;
 | 
			
		||||
 | 
			
		||||
	struct InternalSystemServices {
 | 
			
		||||
		std::string key;
 | 
			
		||||
		std::string version;
 | 
			
		||||
		InternalServiceInfoVec services;
 | 
			
		||||
		void to_json(Poco::JSON::Object &Obj) const;
 | 
			
		||||
		bool from_json(const Poco::JSON::Object::Ptr &Obj);
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	struct SystemEndpoint {
 | 
			
		||||
		std::string type;
 | 
			
		||||
		uint64_t 	id = 0;
 | 
			
		||||
		std::string vendor{"OpenWiFi"};
 | 
			
		||||
		std::string uri;
 | 
			
		||||
		std::string authenticationType{"internal_v1"};
 | 
			
		||||
		void to_json(Poco::JSON::Object &Obj) const;
 | 
			
		||||
		bool from_json(const Poco::JSON::Object::Ptr &Obj);
 | 
			
		||||
	};
 | 
			
		||||
	typedef std::vector<SystemEndpoint> SystemEndpointVec;
 | 
			
		||||
 | 
			
		||||
	struct SystemEndpointList {
 | 
			
		||||
		SystemEndpointVec	endpoints;
 | 
			
		||||
		void to_json(Poco::JSON::Object &Obj) const;
 | 
			
		||||
		bool from_json(const Poco::JSON::Object::Ptr &Obj);
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	struct UserInfoAndPolicy {
 | 
			
		||||
		WebToken	webtoken;
 | 
			
		||||
		UserInfo	userinfo;
 | 
			
		||||
		void to_json(Poco::JSON::Object &Obj) const;
 | 
			
		||||
		bool from_json(const Poco::JSON::Object::Ptr &Obj);
 | 
			
		||||
	};
 | 
			
		||||
	typedef std::map<std::string,SecurityObjects::UserInfoAndPolicy>	UserInfoCache;
 | 
			
		||||
 | 
			
		||||
	enum ResourceAccessType {
 | 
			
		||||
		NONE,
 | 
			
		||||
		READ,
 | 
			
		||||
		MODIFY,
 | 
			
		||||
		DELETE,
 | 
			
		||||
		CREATE,
 | 
			
		||||
		TEST,
 | 
			
		||||
		MOVE
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	ResourceAccessType ResourceAccessTypeFromString(const std::string &s);
 | 
			
		||||
	std::string ResourceAccessTypeToString(const ResourceAccessType & T);
 | 
			
		||||
 | 
			
		||||
	struct ProfileAction {
 | 
			
		||||
		std::string resource;
 | 
			
		||||
		ResourceAccessType access;
 | 
			
		||||
		void to_json(Poco::JSON::Object &Obj) const;
 | 
			
		||||
		bool from_json(Poco::JSON::Object::Ptr Obj);
 | 
			
		||||
	};
 | 
			
		||||
	typedef std::vector<ProfileAction>	ProfileActionVec;
 | 
			
		||||
 | 
			
		||||
	struct SecurityProfile {
 | 
			
		||||
		uint64_t id=0;
 | 
			
		||||
		std::string name;
 | 
			
		||||
		std::string description;
 | 
			
		||||
		ProfileActionVec policy;
 | 
			
		||||
		std::string role;
 | 
			
		||||
		NoteInfoVec notes;
 | 
			
		||||
		void to_json(Poco::JSON::Object &Obj) const;
 | 
			
		||||
		bool from_json(Poco::JSON::Object::Ptr Obj);
 | 
			
		||||
	};
 | 
			
		||||
	typedef std::vector<SecurityProfile> SecurityProfileVec;
 | 
			
		||||
 | 
			
		||||
	struct SecurityProfileList {
 | 
			
		||||
		SecurityProfileVec profiles;
 | 
			
		||||
		void to_json(Poco::JSON::Object &Obj) const;
 | 
			
		||||
		bool from_json(Poco::JSON::Object::Ptr Obj);
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#endif //UCENTRAL_RESTAPI_SECURITYOBJECTS_H
 | 
			
		||||
@@ -1,132 +0,0 @@
 | 
			
		||||
//
 | 
			
		||||
// Created by stephane bourque on 2021-06-22.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#include "RESTAPI_action_links.h"
 | 
			
		||||
#include "StorageService.h"
 | 
			
		||||
#include "Utils.h"
 | 
			
		||||
#include "RESTAPI_utils.h"
 | 
			
		||||
 | 
			
		||||
#include "Poco/JSON/Parser.h"
 | 
			
		||||
#include "Poco/Net/HTMLForm.h"
 | 
			
		||||
#include "RESTAPI_server.h"
 | 
			
		||||
#include "Daemon.h"
 | 
			
		||||
 | 
			
		||||
namespace OpenWifi {
 | 
			
		||||
    void RESTAPI_action_links::DoGet() {
 | 
			
		||||
 | 
			
		||||
        auto Action = GetParameter("action","");
 | 
			
		||||
        auto Id = GetParameter("id","");
 | 
			
		||||
 | 
			
		||||
        if(Action=="password_reset")
 | 
			
		||||
            RequestResetPassword(Id);
 | 
			
		||||
        else if(Action=="email_verification")
 | 
			
		||||
            DoEmailVerification(Id);
 | 
			
		||||
        else
 | 
			
		||||
            DoReturnA404();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void RESTAPI_action_links::DoPost() {
 | 
			
		||||
        auto Action = GetParameter("action","");
 | 
			
		||||
        auto Id = GetParameter("id","");
 | 
			
		||||
 | 
			
		||||
        Logger_.information(Poco::format("COMPLETE-PASSWORD-RESET(%s): For ID=%s", Request->clientAddress().toString(), Id));
 | 
			
		||||
        if(Action=="password_reset")
 | 
			
		||||
            CompleteResetPassword(Id);
 | 
			
		||||
        else
 | 
			
		||||
            DoReturnA404();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void RESTAPI_action_links::RequestResetPassword(std::string &Id) {
 | 
			
		||||
        Logger_.information(Poco::format("REQUEST-PASSWORD-RESET(%s): For ID=%s", Request->clientAddress().toString(), Id));
 | 
			
		||||
        Poco::File  FormFile{ RESTAPI_Server()->AssetDir() + "/password_reset.html"};
 | 
			
		||||
        Types::StringPairVec    FormVars{ {"UUID", Id},
 | 
			
		||||
                                          {"PASSWORD_VALIDATION", AuthService()->PasswordValidationExpression()}};
 | 
			
		||||
        SendHTMLFileBack(FormFile,FormVars);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void RESTAPI_action_links::CompleteResetPassword(std::string &Id) {
 | 
			
		||||
        //  form has been posted...
 | 
			
		||||
        RESTAPI_PartHandler PartHandler;
 | 
			
		||||
        Poco::Net::HTMLForm Form(*Request, Request->stream(), PartHandler);
 | 
			
		||||
        if (!Form.empty()) {
 | 
			
		||||
            auto Password1 = Form.get("password1","bla");
 | 
			
		||||
            auto Password2 = Form.get("password1","blu");
 | 
			
		||||
            Id = Form.get("id","");
 | 
			
		||||
            if(Password1!=Password2 || !AuthService()->ValidatePassword(Password2) || !AuthService()->ValidatePassword(Password1)) {
 | 
			
		||||
                Poco::File  FormFile{ RESTAPI_Server()->AssetDir() + "/password_reset_error.html"};
 | 
			
		||||
                Types::StringPairVec    FormVars{ {"UUID", Id},
 | 
			
		||||
                                                  {"ERROR_TEXT", "For some reason, the passwords entered do not match or they do not comply with"
 | 
			
		||||
                                                                 " accepted password creation restrictions. Please consult our on-line help"
 | 
			
		||||
                                                                 " to look at the our password policy. If you would like to contact us, please mention"
 | 
			
		||||
                                                                 " id(" + Id + ")"}};
 | 
			
		||||
                SendHTMLFileBack(FormFile,FormVars);
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            SecurityObjects::UserInfo   UInfo;
 | 
			
		||||
            if(!Storage()->GetUserById(Id,UInfo)) {
 | 
			
		||||
                Poco::File  FormFile{ RESTAPI_Server()->AssetDir() + "/password_reset_error.html"};
 | 
			
		||||
                Types::StringPairVec    FormVars{ {"UUID", Id},
 | 
			
		||||
                                                  {"ERROR_TEXT", "This request does not contain a valid user ID. Please contact your system administrator."}};
 | 
			
		||||
                SendHTMLFileBack(FormFile,FormVars);
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if(UInfo.blackListed || UInfo.suspended) {
 | 
			
		||||
                Poco::File  FormFile{ RESTAPI_Server()->AssetDir() + "/password_reset_error.html"};
 | 
			
		||||
                Types::StringPairVec    FormVars{ {"UUID", Id},
 | 
			
		||||
                                                  {"ERROR_TEXT", "Please contact our system administrators. We have identified an error in your account that must be resolved first."}};
 | 
			
		||||
                SendHTMLFileBack(FormFile,FormVars);
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if(!AuthService()->SetPassword(Password1,UInfo)) {
 | 
			
		||||
                Poco::File  FormFile{ RESTAPI_Server()->AssetDir() + "/password_reset_error.html"};
 | 
			
		||||
                Types::StringPairVec    FormVars{ {"UUID", Id},
 | 
			
		||||
                                                  {"ERROR_TEXT", "You cannot reuse one of your recent passwords."}};
 | 
			
		||||
                SendHTMLFileBack(FormFile,FormVars);
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            Storage()->UpdateUserInfo(UInfo.email,Id,UInfo);
 | 
			
		||||
            Poco::File  FormFile{ RESTAPI_Server()->AssetDir() + "/password_reset_success.html"};
 | 
			
		||||
            Types::StringPairVec    FormVars{ {"UUID", Id},
 | 
			
		||||
                                              {"USERNAME", UInfo.email},
 | 
			
		||||
                                              {"ACTION_LINK",Daemon()->GetUIURI()}};
 | 
			
		||||
            SendHTMLFileBack(FormFile,FormVars);
 | 
			
		||||
        } else {
 | 
			
		||||
            DoReturnA404();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void RESTAPI_action_links::DoEmailVerification(std::string &Id) {
 | 
			
		||||
        SecurityObjects::UserInfo UInfo;
 | 
			
		||||
 | 
			
		||||
        Logger_.information(Poco::format("EMAIL-VERIFICATION(%s): For ID=%s", Request->clientAddress().toString(), Id));
 | 
			
		||||
        if (!Storage()->GetUserById(Id, UInfo)) {
 | 
			
		||||
            Types::StringPairVec FormVars{{"UUID",       Id},
 | 
			
		||||
                                          {"ERROR_TEXT", "This does not appear to be a valid email verification link.."}};
 | 
			
		||||
            Poco::File FormFile{RESTAPI_Server()->AssetDir() + "/email_verification_error.html"};
 | 
			
		||||
            SendHTMLFileBack(FormFile, FormVars);
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        UInfo.waitingForEmailCheck = false;
 | 
			
		||||
        UInfo.validated = true;
 | 
			
		||||
        UInfo.lastEmailCheck = std::time(nullptr);
 | 
			
		||||
        UInfo.validationDate = std::time(nullptr);
 | 
			
		||||
        Storage()->UpdateUserInfo(UInfo.email, Id, UInfo);
 | 
			
		||||
        Types::StringPairVec FormVars{{"UUID",     Id},
 | 
			
		||||
                                      {"USERNAME", UInfo.email},
 | 
			
		||||
                                      {"ACTION_LINK",Daemon()->GetUIURI()}};
 | 
			
		||||
        Poco::File FormFile{RESTAPI_Server()->AssetDir() + "/email_verification_success.html"};
 | 
			
		||||
        SendHTMLFileBack(FormFile, FormVars);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void RESTAPI_action_links::DoReturnA404() {
 | 
			
		||||
        Types::StringPairVec FormVars;
 | 
			
		||||
        Poco::File FormFile{RESTAPI_Server()->AssetDir() + "/404_error.html"};
 | 
			
		||||
        SendHTMLFileBack(FormFile, FormVars);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -1,43 +0,0 @@
 | 
			
		||||
//
 | 
			
		||||
// Created by stephane bourque on 2021-06-22.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#ifndef UCENTRALSEC_RESTAPI_ACTION_LINKS_H
 | 
			
		||||
#define UCENTRALSEC_RESTAPI_ACTION_LINKS_H
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#include "RESTAPI_handler.h"
 | 
			
		||||
#include "Poco/Net/PartHandler.h"
 | 
			
		||||
#include "Poco/Message.h"
 | 
			
		||||
#include "Poco/Net/MessageHeader.h"
 | 
			
		||||
#include "Poco/Net/NameValueCollection.h"
 | 
			
		||||
#include "Poco/NullStream.h"
 | 
			
		||||
#include "Poco/StreamCopier.h"
 | 
			
		||||
#include "Poco/CountingStream.h"
 | 
			
		||||
 | 
			
		||||
namespace OpenWifi {
 | 
			
		||||
    class RESTAPI_action_links : public RESTAPIHandler {
 | 
			
		||||
    public:
 | 
			
		||||
        RESTAPI_action_links(const RESTAPIHandler::BindingMap &bindings, Poco::Logger &L, RESTAPI_GenericServer &Server, bool Internal)
 | 
			
		||||
                : RESTAPIHandler(bindings, L,
 | 
			
		||||
             std::vector<std::string>{
 | 
			
		||||
                                        Poco::Net::HTTPRequest::HTTP_GET,
 | 
			
		||||
                                        Poco::Net::HTTPRequest::HTTP_POST,
 | 
			
		||||
                                        Poco::Net::HTTPRequest::HTTP_OPTIONS},
 | 
			
		||||
                                        Server,
 | 
			
		||||
                                        Internal,
 | 
			
		||||
                                        false) {}
 | 
			
		||||
        static const std::list<const char *> PathName() { return std::list<const char *>{"/api/v1/actionLink"}; };
 | 
			
		||||
        void RequestResetPassword(std::string &Id);
 | 
			
		||||
        void CompleteResetPassword(std::string &Id);
 | 
			
		||||
        void DoEmailVerification(std::string &Id);
 | 
			
		||||
        void DoReturnA404();
 | 
			
		||||
 | 
			
		||||
        void DoGet() final;
 | 
			
		||||
        void DoPost() final;
 | 
			
		||||
        void DoDelete() final {};
 | 
			
		||||
        void DoPut() final {};
 | 
			
		||||
    };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#endif //UCENTRALSEC_RESTAPI_ACTION_LINKS_H
 | 
			
		||||
@@ -1,89 +0,0 @@
 | 
			
		||||
//
 | 
			
		||||
// Created by stephane bourque on 2021-07-15.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#include <fstream>
 | 
			
		||||
#include <iostream>
 | 
			
		||||
 | 
			
		||||
#include "RESTAPI_avatarHandler.h"
 | 
			
		||||
#include "StorageService.h"
 | 
			
		||||
#include "Daemon.h"
 | 
			
		||||
#include "Poco/Net/HTMLForm.h"
 | 
			
		||||
#include "Utils.h"
 | 
			
		||||
#include "RESTAPI_protocol.h"
 | 
			
		||||
 | 
			
		||||
namespace OpenWifi {
 | 
			
		||||
 | 
			
		||||
    void AvatarPartHandler::handlePart(const Poco::Net::MessageHeader &Header, std::istream &Stream) {
 | 
			
		||||
        FileType_ = Header.get(RESTAPI::Protocol::CONTENTTYPE, RESTAPI::Protocol::UNSPECIFIED);
 | 
			
		||||
        if (Header.has(RESTAPI::Protocol::CONTENTDISPOSITION)) {
 | 
			
		||||
            std::string Disposition;
 | 
			
		||||
            Poco::Net::NameValueCollection Parameters;
 | 
			
		||||
            Poco::Net::MessageHeader::splitParameters(Header[RESTAPI::Protocol::CONTENTDISPOSITION], Disposition, Parameters);
 | 
			
		||||
            Name_ = Parameters.get(RESTAPI::Protocol::NAME, RESTAPI::Protocol::UNNAMED);
 | 
			
		||||
        }
 | 
			
		||||
        Poco::CountingInputStream InputStream(Stream);
 | 
			
		||||
        std::ofstream OutputStream(TempFile_.path(), std::ofstream::out);
 | 
			
		||||
        Poco::StreamCopier::copyStream(InputStream, OutputStream);
 | 
			
		||||
        Length_ = InputStream.chars();
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    void RESTAPI_avatarHandler::DoPost() {
 | 
			
		||||
        std::string Id = GetBinding(RESTAPI::Protocol::ID, "");
 | 
			
		||||
        SecurityObjects::UserInfo UInfo;
 | 
			
		||||
 | 
			
		||||
        if (Id.empty() || !Storage()->GetUserById(Id, UInfo)) {
 | 
			
		||||
            NotFound();
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        //  if there is an avatar, just remove it...
 | 
			
		||||
        Storage()->DeleteAvatar(UserInfo_.userinfo.email,Id);
 | 
			
		||||
 | 
			
		||||
        Poco::TemporaryFile TmpFile;
 | 
			
		||||
        AvatarPartHandler partHandler(Id, Logger_, TmpFile);
 | 
			
		||||
 | 
			
		||||
        Poco::Net::HTMLForm form(*Request, Request->stream(), partHandler);
 | 
			
		||||
        Poco::JSON::Object Answer;
 | 
			
		||||
        if (!partHandler.Name().empty() && partHandler.Length()<Daemon()->ConfigGetInt("openwifi.avatar.maxsize",2000000)) {
 | 
			
		||||
            Answer.set(RESTAPI::Protocol::AVATARID, Id);
 | 
			
		||||
            Answer.set(RESTAPI::Protocol::ERRORCODE, 0);
 | 
			
		||||
            Logger_.information(Poco::format("Uploaded avatar: %s Type: %s", partHandler.Name(), partHandler.ContentType()));
 | 
			
		||||
            Storage()->SetAvatar(UserInfo_.userinfo.email,
 | 
			
		||||
                                 Id, TmpFile, partHandler.ContentType(), partHandler.Name());
 | 
			
		||||
        } else {
 | 
			
		||||
            Answer.set(RESTAPI::Protocol::AVATARID, Id);
 | 
			
		||||
            Answer.set(RESTAPI::Protocol::ERRORCODE, 13);
 | 
			
		||||
            Answer.set(RESTAPI::Protocol::ERRORTEXT, "Avatar upload could not complete.");
 | 
			
		||||
        }
 | 
			
		||||
        ReturnObject(Answer);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void RESTAPI_avatarHandler::DoGet() {
 | 
			
		||||
        std::string Id = GetBinding(RESTAPI::Protocol::ID, "");
 | 
			
		||||
        if (Id.empty()) {
 | 
			
		||||
            NotFound();
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        Poco::TemporaryFile TempAvatar;
 | 
			
		||||
        std::string Type, Name;
 | 
			
		||||
        if (!Storage()->GetAvatar(UserInfo_.userinfo.email, Id, TempAvatar, Type, Name)) {
 | 
			
		||||
            NotFound();
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        SendFile(TempAvatar, Type, Name);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void RESTAPI_avatarHandler::DoDelete() {
 | 
			
		||||
        std::string Id = GetBinding(RESTAPI::Protocol::ID, "");
 | 
			
		||||
        if (Id.empty()) {
 | 
			
		||||
            NotFound();
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        if (!Storage()->DeleteAvatar(UserInfo_.userinfo.email, Id)) {
 | 
			
		||||
            NotFound();
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        OK();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,36 +0,0 @@
 | 
			
		||||
//
 | 
			
		||||
// Created by stephane bourque on 2021-09-02.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#include "RESTAPI_email_handler.h"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#include "Poco/Exception.h"
 | 
			
		||||
#include "Poco/JSON/Parser.h"
 | 
			
		||||
 | 
			
		||||
#include "Daemon.h"
 | 
			
		||||
#include "SMTPMailerService.h"
 | 
			
		||||
#include "RESTAPI_errors.h"
 | 
			
		||||
 | 
			
		||||
namespace OpenWifi {
 | 
			
		||||
    void RESTAPI_email_handler::DoPost() {
 | 
			
		||||
        auto Obj = ParseStream();
 | 
			
		||||
        if (Obj->has("subject") &&
 | 
			
		||||
            Obj->has("from") &&
 | 
			
		||||
            Obj->has("text") &&
 | 
			
		||||
            Obj->has("recipients")) {
 | 
			
		||||
            auto   Recipients = Obj->getArray("recipients");
 | 
			
		||||
            MessageAttributes Attrs;
 | 
			
		||||
            Attrs[RECIPIENT_EMAIL] = Recipients->get(0).toString();
 | 
			
		||||
            Attrs[SUBJECT] = Obj->get("subject").toString();
 | 
			
		||||
            Attrs[TEXT] = Obj->get("text").toString();
 | 
			
		||||
            if(SMTPMailerService()->SendMessage(Recipients->get(0).toString(), "password_reset.txt", Attrs)) {
 | 
			
		||||
                OK();
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            ReturnStatus(Poco::Net::HTTPResponse::HTTP_SERVICE_UNAVAILABLE);
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        BadRequest(RESTAPI::Errors::MissingOrInvalidParameters);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,55 +0,0 @@
 | 
			
		||||
//
 | 
			
		||||
// Created by stephane bourque on 2021-09-12.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#ifndef OWPROV_RESTAPI_ERRORS_H
 | 
			
		||||
#define OWPROV_RESTAPI_ERRORS_H
 | 
			
		||||
 | 
			
		||||
namespace OpenWifi::RESTAPI::Errors {
 | 
			
		||||
    static const std::string MissingUUID{"Missing UUID."};
 | 
			
		||||
    static const std::string MissingSerialNumber{"Missing Serial Number."};
 | 
			
		||||
    static const std::string InternalError{"Internal error. Please try later."};
 | 
			
		||||
    static const std::string InvalidJSONDocument{"Invalid JSON document."};
 | 
			
		||||
    static const std::string UnsupportedHTTPMethod{"Unsupported HTTP Method"};
 | 
			
		||||
    static const std::string StillInUse{"Element still in use."};
 | 
			
		||||
    static const std::string CouldNotBeDeleted{"Element could not be deleted."};
 | 
			
		||||
    static const std::string NameMustBeSet{"The name property must be set."};
 | 
			
		||||
    static const std::string ConfigBlockInvalid{"Configuration block type invalid."};
 | 
			
		||||
    static const std::string UnknownId{"Unknown management policy."};
 | 
			
		||||
    static const std::string InvalidDeviceTypes{"Unknown or invalid device type(s)."};
 | 
			
		||||
    static const std::string RecordNotCreated{"Record could not be created."};
 | 
			
		||||
    static const std::string RecordNotUpdated{"Record could not be updated."};
 | 
			
		||||
    static const std::string UnknownManagementPolicyUUID{"Unknown management policy UUID."};
 | 
			
		||||
    static const std::string CannotDeleteRoot{"Root Entity cannot be removed, only modified."};
 | 
			
		||||
    static const std::string MustCreateRootFirst{"Root entity must be created first."};
 | 
			
		||||
    static const std::string ParentUUIDMustExist{"Parent UUID must exist."};
 | 
			
		||||
    static const std::string ConfigurationMustExist{"Configuration must exist."};
 | 
			
		||||
    static const std::string MissingOrInvalidParameters{"Invalid or missing parameters."};
 | 
			
		||||
    static const std::string UnknownSerialNumber{"Unknown Serial Number."};
 | 
			
		||||
    static const std::string InvalidSerialNumber{"Invalid Serial Number."};
 | 
			
		||||
    static const std::string SerialNumberExists{"Serial Number already exists."};
 | 
			
		||||
    static const std::string ValidNonRootUUID{"Must be a non-root, and valid UUID."};
 | 
			
		||||
    static const std::string VenueMustExist{"Venue does not exist."};
 | 
			
		||||
    static const std::string NotBoth{"You cannot specify both Entity and Venue"};
 | 
			
		||||
    static const std::string EntityMustExist{"Entity must exist."};
 | 
			
		||||
    static const std::string ParentOrEntityMustBeSet{"Parent or Entity must be set."};
 | 
			
		||||
    static const std::string ContactMustExist{"Contact must exist."};
 | 
			
		||||
    static const std::string LocationMustExist{"Location must exist."};
 | 
			
		||||
    static const std::string OnlyWSSupported{"This endpoint only supports WebSocket."};
 | 
			
		||||
    static const std::string SerialNumberMismatch{"Serial Number mismatch."};
 | 
			
		||||
    static const std::string InvalidCommand{"Invalid command."};
 | 
			
		||||
    static const std::string NoRecordsDeleted{"No records deleted."};
 | 
			
		||||
    static const std::string DeviceNotConnected{"Device is not currently connected."};
 | 
			
		||||
    static const std::string CannotCreateWS{"Telemetry system could not create WS endpoint. Please try again."};
 | 
			
		||||
    static const std::string BothDeviceTypeRevision{"Both deviceType and revision must be set."};
 | 
			
		||||
    static const std::string IdOrSerialEmpty{"SerialNumber and Id must not be empty."};
 | 
			
		||||
    static const std::string MissingUserID{"Missing user ID."};
 | 
			
		||||
    static const std::string IdMustBe0{"To create a user, you must set the ID to 0"};
 | 
			
		||||
    static const std::string InvalidUserRole{"Invalid userRole."};
 | 
			
		||||
    static const std::string InvalidEmailAddress{"Invalid email address."};
 | 
			
		||||
    static const std::string InvalidPassword{"Invalid password."};
 | 
			
		||||
    static const std::string PasswordRejected{"Password was rejected. This maybe an old password."};
 | 
			
		||||
    static const std::string InvalidIPRanges{"Invalid IP range specifications."};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#endif //OWPROV_RESTAPI_ERRORS_H
 | 
			
		||||
@@ -1,479 +0,0 @@
 | 
			
		||||
//
 | 
			
		||||
//	License type: BSD 3-Clause License
 | 
			
		||||
//	License copy: https://github.com/Telecominfraproject/wlan-cloud-ucentralgw/blob/master/LICENSE
 | 
			
		||||
//
 | 
			
		||||
//	Created by Stephane Bourque on 2021-03-04.
 | 
			
		||||
//	Arilia Wireless Inc.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#include <cctype>
 | 
			
		||||
#include <algorithm>
 | 
			
		||||
#include <functional>
 | 
			
		||||
#include <iostream>
 | 
			
		||||
#include <iterator>
 | 
			
		||||
#include <future>
 | 
			
		||||
#include <chrono>
 | 
			
		||||
 | 
			
		||||
#include "Poco/URI.h"
 | 
			
		||||
#include "Poco/Net/OAuth20Credentials.h"
 | 
			
		||||
 | 
			
		||||
#include "RESTAPI_errors.h"
 | 
			
		||||
 | 
			
		||||
#ifdef	TIP_SECURITY_SERVICE
 | 
			
		||||
#include "AuthService.h"
 | 
			
		||||
#else
 | 
			
		||||
#include "AuthClient.h"
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#include "RESTAPI_handler.h"
 | 
			
		||||
#include "RESTAPI_protocol.h"
 | 
			
		||||
#include "Utils.h"
 | 
			
		||||
#include "Daemon.h"
 | 
			
		||||
 | 
			
		||||
namespace OpenWifi {
 | 
			
		||||
 | 
			
		||||
    void RESTAPIHandler::handleRequest(Poco::Net::HTTPServerRequest &RequestIn,
 | 
			
		||||
                       Poco::Net::HTTPServerResponse &ResponseIn) {
 | 
			
		||||
		try {
 | 
			
		||||
			Request = &RequestIn;
 | 
			
		||||
			Response = &ResponseIn;
 | 
			
		||||
 | 
			
		||||
			if (!ContinueProcessing())
 | 
			
		||||
				return;
 | 
			
		||||
 | 
			
		||||
			if (AlwaysAuthorize_ && !IsAuthorized())
 | 
			
		||||
				return;
 | 
			
		||||
 | 
			
		||||
			ParseParameters();
 | 
			
		||||
			if (Request->getMethod() == Poco::Net::HTTPRequest::HTTP_GET)
 | 
			
		||||
				DoGet();
 | 
			
		||||
			else if (Request->getMethod() == Poco::Net::HTTPRequest::HTTP_POST)
 | 
			
		||||
				DoPost();
 | 
			
		||||
			else if (Request->getMethod() == Poco::Net::HTTPRequest::HTTP_DELETE)
 | 
			
		||||
				DoDelete();
 | 
			
		||||
			else if (Request->getMethod() == Poco::Net::HTTPRequest::HTTP_PUT)
 | 
			
		||||
				DoPut();
 | 
			
		||||
			else
 | 
			
		||||
				BadRequest(RESTAPI::Errors::UnsupportedHTTPMethod);
 | 
			
		||||
			return;
 | 
			
		||||
		} catch (const Poco::Exception &E) {
 | 
			
		||||
			Logger_.log(E);
 | 
			
		||||
			BadRequest(RESTAPI::Errors::InternalError);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
    const Poco::JSON::Object::Ptr &RESTAPIHandler::ParseStream() {
 | 
			
		||||
        return IncomingParser_.parse(Request->stream()).extract<Poco::JSON::Object::Ptr>();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
	bool RESTAPIHandler::ParseBindings(const std::string & Request, const std::list<const char *> & EndPoints, BindingMap &bindings) {
 | 
			
		||||
		bindings.clear();
 | 
			
		||||
		std::vector<std::string> PathItems = Utils::Split(Request, '/');
 | 
			
		||||
 | 
			
		||||
		for(const auto &EndPoint:EndPoints) {
 | 
			
		||||
			std::vector<std::string> ParamItems = Utils::Split(EndPoint, '/');
 | 
			
		||||
			if (PathItems.size() != ParamItems.size())
 | 
			
		||||
				continue;
 | 
			
		||||
 | 
			
		||||
			bool Matched = true;
 | 
			
		||||
			for (auto i = 0; i != PathItems.size() && Matched; i++) {
 | 
			
		||||
				if (PathItems[i] != ParamItems[i]) {
 | 
			
		||||
					if (ParamItems[i][0] == '{') {
 | 
			
		||||
						auto ParamName = ParamItems[i].substr(1, ParamItems[i].size() - 2);
 | 
			
		||||
						bindings[Poco::toLower(ParamName)] = PathItems[i];
 | 
			
		||||
					} else {
 | 
			
		||||
						Matched = false;
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			if(Matched)
 | 
			
		||||
				return true;
 | 
			
		||||
		}
 | 
			
		||||
		return false;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	void RESTAPIHandler::PrintBindings() {
 | 
			
		||||
		for (const auto &[key, value] : Bindings_)
 | 
			
		||||
			std::cout << "Key = " << key << "  Value= " << value << std::endl;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	void RESTAPIHandler::ParseParameters() {
 | 
			
		||||
		Poco::URI uri(Request->getURI());
 | 
			
		||||
		Parameters_ = uri.getQueryParameters();
 | 
			
		||||
		InitQueryBlock();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	static bool is_number(const std::string &s) {
 | 
			
		||||
		return !s.empty() && std::all_of(s.begin(), s.end(), ::isdigit);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	static bool is_bool(const std::string &s) {
 | 
			
		||||
		if (s == "true" || s == "false")
 | 
			
		||||
			return true;
 | 
			
		||||
		return false;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	uint64_t RESTAPIHandler::GetParameter(const std::string &Name, const uint64_t Default) {
 | 
			
		||||
        auto Hint = std::find_if(Parameters_.begin(),Parameters_.end(),[Name](const std::pair<std::string,std::string> &S){ return S.first==Name; });
 | 
			
		||||
        if(Hint==Parameters_.end() || !is_number(Hint->second))
 | 
			
		||||
            return Default;
 | 
			
		||||
        return std::stoull(Hint->second);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	bool RESTAPIHandler::GetBoolParameter(const std::string &Name, bool Default) {
 | 
			
		||||
        auto Hint = std::find_if(begin(Parameters_),end(Parameters_),[Name](const std::pair<std::string,std::string> &S){ return S.first==Name; });
 | 
			
		||||
        if(Hint==end(Parameters_) || !is_bool(Hint->second))
 | 
			
		||||
            return Default;
 | 
			
		||||
		return Hint->second=="true";
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	std::string RESTAPIHandler::GetParameter(const std::string &Name, const std::string &Default) {
 | 
			
		||||
        auto Hint = std::find_if(begin(Parameters_),end(Parameters_),[Name](const std::pair<std::string,std::string> &S){ return S.first==Name; });
 | 
			
		||||
        if(Hint==end(Parameters_))
 | 
			
		||||
            return Default;
 | 
			
		||||
        return Hint->second;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	bool RESTAPIHandler::HasParameter(const std::string &Name, std::string &Value) {
 | 
			
		||||
        auto Hint = std::find_if(begin(Parameters_),end(Parameters_),[Name](const std::pair<std::string,std::string> &S){ return S.first==Name; });
 | 
			
		||||
        if(Hint==end(Parameters_))
 | 
			
		||||
            return false;
 | 
			
		||||
        Value = Hint->second;
 | 
			
		||||
        return true;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	bool RESTAPIHandler::HasParameter(const std::string &Name, uint64_t & Value) {
 | 
			
		||||
        auto Hint = std::find_if(begin(Parameters_),end(Parameters_),[Name](const std::pair<std::string,std::string> &S){ return S.first==Name; });
 | 
			
		||||
        if(Hint==end(Parameters_))
 | 
			
		||||
            return false;
 | 
			
		||||
        Value = std::stoull(Hint->second);
 | 
			
		||||
        return true;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	const std::string &RESTAPIHandler::GetBinding(const std::string &Name, const std::string &Default) {
 | 
			
		||||
		auto E = Bindings_.find(Poco::toLower(Name));
 | 
			
		||||
		if (E == Bindings_.end())
 | 
			
		||||
			return Default;
 | 
			
		||||
 | 
			
		||||
		return E->second;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	static std::string MakeList(const std::vector<std::string> &L) {
 | 
			
		||||
		std::string Return;
 | 
			
		||||
		for (const auto &i : L)
 | 
			
		||||
			if (Return.empty())
 | 
			
		||||
				Return = i;
 | 
			
		||||
			else
 | 
			
		||||
				Return += ", " + i;
 | 
			
		||||
 | 
			
		||||
		return Return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	bool RESTAPIHandler::AssignIfPresent(const Poco::JSON::Object::Ptr &O, const std::string &Field, std::string &Value) {
 | 
			
		||||
	    if(O->has(Field)) {
 | 
			
		||||
	        Value = O->get(Field).toString();
 | 
			
		||||
	        return true;
 | 
			
		||||
	    }
 | 
			
		||||
	    return false;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	bool RESTAPIHandler::AssignIfPresent(const Poco::JSON::Object::Ptr &O, const std::string &Field, uint64_t &Value) {
 | 
			
		||||
	    if(O->has(Field)) {
 | 
			
		||||
	        Value = O->get(Field);
 | 
			
		||||
	        return true;
 | 
			
		||||
	    }
 | 
			
		||||
	    return false;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	void RESTAPIHandler::AddCORS() {
 | 
			
		||||
		auto Origin = Request->find("Origin");
 | 
			
		||||
		if (Origin != Request->end()) {
 | 
			
		||||
			Response->set("Access-Control-Allow-Origin", Origin->second);
 | 
			
		||||
			Response->set("Vary", "Origin");
 | 
			
		||||
		} else {
 | 
			
		||||
			Response->set("Access-Control-Allow-Origin", "*");
 | 
			
		||||
		}
 | 
			
		||||
		Response->set("Access-Control-Allow-Headers", "*");
 | 
			
		||||
		Response->set("Access-Control-Allow-Methods", MakeList(Methods_));
 | 
			
		||||
		Response->set("Access-Control-Max-Age", "86400");
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	void RESTAPIHandler::SetCommonHeaders(bool CloseConnection) {
 | 
			
		||||
		Response->setVersion(Poco::Net::HTTPMessage::HTTP_1_1);
 | 
			
		||||
		Response->setChunkedTransferEncoding(true);
 | 
			
		||||
		Response->setContentType("application/json");
 | 
			
		||||
		if(CloseConnection) {
 | 
			
		||||
			Response->set("Connection", "close");
 | 
			
		||||
			Response->setKeepAlive(false);
 | 
			
		||||
		} else {
 | 
			
		||||
			Response->setKeepAlive(true);
 | 
			
		||||
			Response->set("Connection", "Keep-Alive");
 | 
			
		||||
			Response->set("Keep-Alive", "timeout=5, max=1000");
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	void RESTAPIHandler::ProcessOptions() {
 | 
			
		||||
		AddCORS();
 | 
			
		||||
		SetCommonHeaders();
 | 
			
		||||
		Response->setContentLength(0);
 | 
			
		||||
		Response->set("Access-Control-Allow-Credentials", "true");
 | 
			
		||||
		Response->setStatus(Poco::Net::HTTPResponse::HTTP_OK);
 | 
			
		||||
		Response->set("Vary", "Origin, Access-Control-Request-Headers, Access-Control-Request-Method");
 | 
			
		||||
		Response->send();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	void RESTAPIHandler::PrepareResponse( Poco::Net::HTTPResponse::HTTPStatus Status,
 | 
			
		||||
										 bool CloseConnection) {
 | 
			
		||||
		Response->setStatus(Status);
 | 
			
		||||
		AddCORS();
 | 
			
		||||
		SetCommonHeaders(CloseConnection);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	void RESTAPIHandler::BadRequest(const std::string & Reason) {
 | 
			
		||||
		PrepareResponse(Poco::Net::HTTPResponse::HTTP_BAD_REQUEST);
 | 
			
		||||
		Poco::JSON::Object	ErrorObject;
 | 
			
		||||
		ErrorObject.set("ErrorCode",400);
 | 
			
		||||
		ErrorObject.set("ErrorDetails",Request->getMethod());
 | 
			
		||||
		ErrorObject.set("ErrorDescription",Reason.empty() ? "Command is missing parameters or wrong values." : Reason) ;
 | 
			
		||||
		std::ostream &Answer = Response->send();
 | 
			
		||||
		Poco::JSON::Stringifier::stringify(ErrorObject, Answer);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	void RESTAPIHandler::InternalError(const std::string & Reason) {
 | 
			
		||||
        PrepareResponse(Poco::Net::HTTPResponse::HTTP_INTERNAL_SERVER_ERROR);
 | 
			
		||||
        Poco::JSON::Object	ErrorObject;
 | 
			
		||||
        ErrorObject.set("ErrorCode",500);
 | 
			
		||||
        ErrorObject.set("ErrorDetails",Request->getMethod());
 | 
			
		||||
        ErrorObject.set("ErrorDescription",Reason.empty() ? "Please try later or review the data submitted." : Reason) ;
 | 
			
		||||
        std::ostream &Answer = Response->send();
 | 
			
		||||
        Poco::JSON::Stringifier::stringify(ErrorObject, Answer);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
	void RESTAPIHandler::UnAuthorized(const std::string & Reason) {
 | 
			
		||||
		PrepareResponse(Poco::Net::HTTPResponse::HTTP_FORBIDDEN);
 | 
			
		||||
		Poco::JSON::Object	ErrorObject;
 | 
			
		||||
		ErrorObject.set("ErrorCode",403);
 | 
			
		||||
		ErrorObject.set("ErrorDetails",Request->getMethod());
 | 
			
		||||
		ErrorObject.set("ErrorDescription",Reason.empty() ? "No access allowed." : Reason) ;
 | 
			
		||||
		std::ostream &Answer = Response->send();
 | 
			
		||||
		Poco::JSON::Stringifier::stringify(ErrorObject, Answer);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	void RESTAPIHandler::NotFound() {
 | 
			
		||||
		PrepareResponse(Poco::Net::HTTPResponse::HTTP_NOT_FOUND);
 | 
			
		||||
		Poco::JSON::Object	ErrorObject;
 | 
			
		||||
		ErrorObject.set("ErrorCode",404);
 | 
			
		||||
		ErrorObject.set("ErrorDetails",Request->getMethod());
 | 
			
		||||
		ErrorObject.set("ErrorDescription","This resource does not exist.");
 | 
			
		||||
		std::ostream &Answer = Response->send();
 | 
			
		||||
		Poco::JSON::Stringifier::stringify(ErrorObject, Answer);
 | 
			
		||||
		Logger_.debug(Poco::format("RES-NOTFOUND: User='%s' Method='%s' Path='%s",
 | 
			
		||||
                                   Utils::FormatIPv6(Request->clientAddress().toString()),
 | 
			
		||||
                                   Request->getMethod(),
 | 
			
		||||
                                   Request->getURI()));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	void RESTAPIHandler::OK() {
 | 
			
		||||
		PrepareResponse();
 | 
			
		||||
		if(	Request->getMethod()==Poco::Net::HTTPRequest::HTTP_DELETE ||
 | 
			
		||||
			Request->getMethod()==Poco::Net::HTTPRequest::HTTP_OPTIONS) {
 | 
			
		||||
			Response->send();
 | 
			
		||||
		} else {
 | 
			
		||||
			Poco::JSON::Object ErrorObject;
 | 
			
		||||
			ErrorObject.set("Code", 0);
 | 
			
		||||
			ErrorObject.set("Operation", Request->getMethod());
 | 
			
		||||
			ErrorObject.set("Details", "Command completed.");
 | 
			
		||||
			std::ostream &Answer = Response->send();
 | 
			
		||||
			Poco::JSON::Stringifier::stringify(ErrorObject, Answer);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	void RESTAPIHandler::SendFile(Poco::File & File, const std::string & UUID) {
 | 
			
		||||
		Response->set("Content-Type","application/octet-stream");
 | 
			
		||||
		Response->set("Content-Disposition", "attachment; filename=" + UUID );
 | 
			
		||||
		Response->set("Content-Transfer-Encoding","binary");
 | 
			
		||||
		Response->set("Accept-Ranges", "bytes");
 | 
			
		||||
		Response->set("Cache-Control", "private");
 | 
			
		||||
		Response->set("Pragma", "private");
 | 
			
		||||
		Response->set("Expires", "Mon, 26 Jul 2027 05:00:00 GMT");
 | 
			
		||||
		Response->set("Content-Length", std::to_string(File.getSize()));
 | 
			
		||||
		AddCORS();
 | 
			
		||||
		Response->sendFile(File.path(),"application/octet-stream");
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
    void RESTAPIHandler::SendFile(Poco::File & File) {
 | 
			
		||||
        Poco::Path  P(File.path());
 | 
			
		||||
        auto MT = Utils::FindMediaType(File);
 | 
			
		||||
        if(MT.Encoding==Utils::BINARY) {
 | 
			
		||||
            Response->set("Content-Transfer-Encoding","binary");
 | 
			
		||||
            Response->set("Accept-Ranges", "bytes");
 | 
			
		||||
        }
 | 
			
		||||
        Response->set("Cache-Control", "private");
 | 
			
		||||
        Response->set("Pragma", "private");
 | 
			
		||||
        Response->set("Expires", "Mon, 26 Jul 2027 05:00:00 GMT");
 | 
			
		||||
        AddCORS();
 | 
			
		||||
        Response->sendFile(File.path(),MT.ContentType);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void RESTAPIHandler::SendFile(Poco::TemporaryFile &TempAvatar, const std::string &Type, const std::string & Name) {
 | 
			
		||||
        auto MT = Utils::FindMediaType(Name);
 | 
			
		||||
        if(MT.Encoding==Utils::BINARY) {
 | 
			
		||||
            Response->set("Content-Transfer-Encoding","binary");
 | 
			
		||||
            Response->set("Accept-Ranges", "bytes");
 | 
			
		||||
        }
 | 
			
		||||
        Response->set("Content-Disposition", "attachment; filename=" + Name );
 | 
			
		||||
        Response->set("Accept-Ranges", "bytes");
 | 
			
		||||
        Response->set("Cache-Control", "private");
 | 
			
		||||
        Response->set("Pragma", "private");
 | 
			
		||||
        Response->set("Expires", "Mon, 26 Jul 2027 05:00:00 GMT");
 | 
			
		||||
        AddCORS();
 | 
			
		||||
        Response->sendFile(TempAvatar.path(),MT.ContentType);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
    void RESTAPIHandler::SendHTMLFileBack(Poco::File & File,
 | 
			
		||||
                          const Types::StringPairVec & FormVars) {
 | 
			
		||||
        Response->set("Pragma", "private");
 | 
			
		||||
        Response->set("Expires", "Mon, 26 Jul 2027 05:00:00 GMT");
 | 
			
		||||
        Response->set("Content-Length", std::to_string(File.getSize()));
 | 
			
		||||
        AddCORS();
 | 
			
		||||
        auto FormContent = Utils::LoadFile(File.path());
 | 
			
		||||
        Utils::ReplaceVariables(FormContent, FormVars);
 | 
			
		||||
        Response->setChunkedTransferEncoding(true);
 | 
			
		||||
        Response->setContentType("text/html");
 | 
			
		||||
        std::ostream& ostr = Response->send();
 | 
			
		||||
        ostr << FormContent;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
    void RESTAPIHandler::ReturnStatus(Poco::Net::HTTPResponse::HTTPStatus Status, bool CloseConnection) {
 | 
			
		||||
		PrepareResponse(Status, CloseConnection);
 | 
			
		||||
		if(Status == Poco::Net::HTTPResponse::HTTP_NO_CONTENT) {
 | 
			
		||||
			Response->setContentLength(0);
 | 
			
		||||
			Response->erase("Content-Type");
 | 
			
		||||
			Response->setChunkedTransferEncoding(false);
 | 
			
		||||
		}
 | 
			
		||||
		Response->send();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	bool RESTAPIHandler::ContinueProcessing() {
 | 
			
		||||
		if (Request->getMethod() == Poco::Net::HTTPRequest::HTTP_OPTIONS) {
 | 
			
		||||
			ProcessOptions();
 | 
			
		||||
			return false;
 | 
			
		||||
		} else if (std::find(Methods_.begin(), Methods_.end(), Request->getMethod()) == Methods_.end()) {
 | 
			
		||||
			BadRequest(RESTAPI::Errors::UnsupportedHTTPMethod);
 | 
			
		||||
			return false;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return true;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	bool RESTAPIHandler::IsAuthorized() {
 | 
			
		||||
	    if(Internal_) {
 | 
			
		||||
	        auto Allowed = Daemon()->IsValidAPIKEY(*Request);
 | 
			
		||||
	        if(!Allowed) {
 | 
			
		||||
	            if(Server_.LogBadTokens(false)) {
 | 
			
		||||
	                Logger_.debug(Poco::format("I-REQ-DENIED(%s): Method='%s' Path='%s",
 | 
			
		||||
                                               Utils::FormatIPv6(Request->clientAddress().toString()),
 | 
			
		||||
                                               Request->getMethod(), Request->getURI()));
 | 
			
		||||
	            }
 | 
			
		||||
	        } else {
 | 
			
		||||
	            auto Id = Request->get("X-INTERNAL-NAME", "unknown");
 | 
			
		||||
	            if(Server_.LogIt(Request->getMethod(),true)) {
 | 
			
		||||
	                Logger_.debug(Poco::format("I-REQ-ALLOWED(%s): User='%s' Method='%s' Path='%s",
 | 
			
		||||
                                               Utils::FormatIPv6(Request->clientAddress().toString()), Id,
 | 
			
		||||
                                               Request->getMethod(), Request->getURI()));
 | 
			
		||||
	            }
 | 
			
		||||
	        }
 | 
			
		||||
            return Allowed;
 | 
			
		||||
	    } else {
 | 
			
		||||
            if (SessionToken_.empty()) {
 | 
			
		||||
                try {
 | 
			
		||||
                    Poco::Net::OAuth20Credentials Auth(*Request);
 | 
			
		||||
                    if (Auth.getScheme() == "Bearer") {
 | 
			
		||||
                        SessionToken_ = Auth.getBearerToken();
 | 
			
		||||
                    }
 | 
			
		||||
                } catch (const Poco::Exception &E) {
 | 
			
		||||
                    Logger_.log(E);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
#ifdef    TIP_SECURITY_SERVICE
 | 
			
		||||
            if (AuthService()->IsAuthorized(*Request, SessionToken_, UserInfo_)) {
 | 
			
		||||
#else
 | 
			
		||||
            if (AuthClient()->IsAuthorized(*Request, SessionToken_, UserInfo_)) {
 | 
			
		||||
#endif
 | 
			
		||||
                if(Server_.LogIt(Request->getMethod(),true)) {
 | 
			
		||||
                    Logger_.debug(Poco::format("X-REQ-ALLOWED(%s): User='%s@%s' Method='%s' Path='%s",
 | 
			
		||||
                         Utils::FormatIPv6(Request->clientAddress().toString()),
 | 
			
		||||
                         UserInfo_.userinfo.email,
 | 
			
		||||
                         Request->clientAddress().toString(),
 | 
			
		||||
                         Request->getMethod(),
 | 
			
		||||
                         Request->getURI()));
 | 
			
		||||
                }
 | 
			
		||||
                return true;
 | 
			
		||||
            } else {
 | 
			
		||||
                if(Server_.LogBadTokens(true)) {
 | 
			
		||||
                    Logger_.debug(Poco::format("X-REQ-DENIED(%s): Method='%s' Path='%s",
 | 
			
		||||
                         Utils::FormatIPv6(Request->clientAddress().toString()),
 | 
			
		||||
                         Request->getMethod(), Request->getURI()));
 | 
			
		||||
                }
 | 
			
		||||
                UnAuthorized();
 | 
			
		||||
            }
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	void RESTAPIHandler::ReturnObject(Poco::JSON::Object &Object) {
 | 
			
		||||
		PrepareResponse();
 | 
			
		||||
		std::ostream &Answer = Response->send();
 | 
			
		||||
		Poco::JSON::Stringifier::stringify(Object, Answer);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	void RESTAPIHandler::ReturnCountOnly(uint64_t Count) {
 | 
			
		||||
	    Poco::JSON::Object  Answer;
 | 
			
		||||
	    Answer.set("count", Count);
 | 
			
		||||
        ReturnObject(Answer);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	bool RESTAPIHandler::InitQueryBlock() {
 | 
			
		||||
	    if(QueryBlockInitialized_)
 | 
			
		||||
	        return true;
 | 
			
		||||
	    QueryBlockInitialized_=true;
 | 
			
		||||
		QB_.SerialNumber = GetParameter(RESTAPI::Protocol::SERIALNUMBER, "");
 | 
			
		||||
		QB_.StartDate = GetParameter(RESTAPI::Protocol::STARTDATE, 0);
 | 
			
		||||
		QB_.EndDate = GetParameter(RESTAPI::Protocol::ENDDATE, 0);
 | 
			
		||||
		QB_.Offset = GetParameter(RESTAPI::Protocol::OFFSET, 1);
 | 
			
		||||
		QB_.Limit = GetParameter(RESTAPI::Protocol::LIMIT, 100);
 | 
			
		||||
		QB_.Filter = GetParameter(RESTAPI::Protocol::FILTER, "");
 | 
			
		||||
		QB_.Select = GetParameter(RESTAPI::Protocol::SELECT, "");
 | 
			
		||||
		QB_.Lifetime = GetBoolParameter(RESTAPI::Protocol::LIFETIME,false);
 | 
			
		||||
		QB_.LogType = GetParameter(RESTAPI::Protocol::LOGTYPE,0);
 | 
			
		||||
		QB_.LastOnly = GetBoolParameter(RESTAPI::Protocol::LASTONLY,false);
 | 
			
		||||
		QB_.Newest = GetBoolParameter(RESTAPI::Protocol::NEWEST,false);
 | 
			
		||||
		QB_.CountOnly = GetBoolParameter(RESTAPI::Protocol::COUNTONLY,false);
 | 
			
		||||
 | 
			
		||||
		if(QB_.Offset<1)
 | 
			
		||||
		    QB_.Offset=1;
 | 
			
		||||
		return true;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	[[nodiscard]] uint64_t RESTAPIHandler::Get(const char *Parameter,const Poco::JSON::Object::Ptr &Obj, uint64_t Default){
 | 
			
		||||
		if(Obj->has(Parameter))
 | 
			
		||||
			return Obj->get(Parameter);
 | 
			
		||||
		return Default;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	[[nodiscard]] std::string RESTAPIHandler::GetS(const char *Parameter,const Poco::JSON::Object::Ptr &Obj, const std::string & Default){
 | 
			
		||||
		if(Obj->has(Parameter))
 | 
			
		||||
			return Obj->get(Parameter).toString();
 | 
			
		||||
		return Default;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	[[nodiscard]] bool RESTAPIHandler::GetB(const char *Parameter,const Poco::JSON::Object::Ptr &Obj, bool Default){
 | 
			
		||||
		if(Obj->has(Parameter))
 | 
			
		||||
			return Obj->get(Parameter).toString()=="true";
 | 
			
		||||
		return Default;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	[[nodiscard]] uint64_t RESTAPIHandler::GetWhen(const Poco::JSON::Object::Ptr &Obj) {
 | 
			
		||||
		return RESTAPIHandler::Get(RESTAPI::Protocol::WHEN, Obj);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -1,233 +0,0 @@
 | 
			
		||||
//
 | 
			
		||||
//	License type: BSD 3-Clause License
 | 
			
		||||
//	License copy: https://github.com/Telecominfraproject/wlan-cloud-ucentralgw/blob/master/LICENSE
 | 
			
		||||
//
 | 
			
		||||
//	Created by Stephane Bourque on 2021-03-04.
 | 
			
		||||
//	Arilia Wireless Inc.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#ifndef UCENTRAL_RESTAPI_HANDLER_H
 | 
			
		||||
#define UCENTRAL_RESTAPI_HANDLER_H
 | 
			
		||||
 | 
			
		||||
#include "Poco/URI.h"
 | 
			
		||||
#include "Poco/Net/HTTPRequestHandler.h"
 | 
			
		||||
#include "Poco/Net/HTTPRequestHandlerFactory.h"
 | 
			
		||||
#include "Poco/Net/HTTPServerRequest.h"
 | 
			
		||||
#include "Poco/Net/HTTPServerResponse.h"
 | 
			
		||||
#include "Poco/Net/NetException.h"
 | 
			
		||||
#include "Poco/Net/PartHandler.h"
 | 
			
		||||
 | 
			
		||||
#include "Poco/Logger.h"
 | 
			
		||||
#include "Poco/File.h"
 | 
			
		||||
#include "Poco/TemporaryFile.h"
 | 
			
		||||
#include "Poco/JSON/Object.h"
 | 
			
		||||
#include "Poco/CountingStream.h"
 | 
			
		||||
#include "Poco/NullStream.h"
 | 
			
		||||
 | 
			
		||||
#include "RESTAPI_SecurityObjects.h"
 | 
			
		||||
#include "RESTAPI_utils.h"
 | 
			
		||||
#include "RESTAPI_GenericServer.h"
 | 
			
		||||
 | 
			
		||||
namespace OpenWifi {
 | 
			
		||||
 | 
			
		||||
    class RESTAPI_PartHandler: public Poco::Net::PartHandler
 | 
			
		||||
    {
 | 
			
		||||
    public:
 | 
			
		||||
        RESTAPI_PartHandler():
 | 
			
		||||
                _length(0)
 | 
			
		||||
        {
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        void handlePart(const Poco::Net::MessageHeader& header, std::istream& stream) override
 | 
			
		||||
        {
 | 
			
		||||
            _type = header.get("Content-Type", "(unspecified)");
 | 
			
		||||
            if (header.has("Content-Disposition"))
 | 
			
		||||
            {
 | 
			
		||||
                std::string disp;
 | 
			
		||||
                Poco::Net::NameValueCollection params;
 | 
			
		||||
                Poco::Net::MessageHeader::splitParameters(header["Content-Disposition"], disp, params);
 | 
			
		||||
                _name = params.get("name", "(unnamed)");
 | 
			
		||||
                _fileName = params.get("filename", "(unnamed)");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            Poco::CountingInputStream istr(stream);
 | 
			
		||||
            Poco::NullOutputStream ostr;
 | 
			
		||||
            Poco::StreamCopier::copyStream(istr, ostr);
 | 
			
		||||
            _length = (int)istr.chars();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [[nodiscard]] int length() const
 | 
			
		||||
        {
 | 
			
		||||
            return _length;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [[nodiscard]] const std::string& name() const
 | 
			
		||||
        {
 | 
			
		||||
            return _name;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [[nodiscard]] const std::string& fileName() const
 | 
			
		||||
        {
 | 
			
		||||
            return _fileName;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [[nodiscard]] const std::string& contentType() const
 | 
			
		||||
        {
 | 
			
		||||
            return _type;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    private:
 | 
			
		||||
        int _length;
 | 
			
		||||
        std::string _type;
 | 
			
		||||
        std::string _name;
 | 
			
		||||
        std::string _fileName;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    class RESTAPIHandler : public Poco::Net::HTTPRequestHandler {
 | 
			
		||||
	  public:
 | 
			
		||||
		struct QueryBlock {
 | 
			
		||||
			uint64_t StartDate = 0 , EndDate = 0 , Offset = 0 , Limit = 0, LogType = 0 ;
 | 
			
		||||
			std::string SerialNumber, Filter, Select;
 | 
			
		||||
			bool Lifetime=false, LastOnly=false, Newest=false, CountOnly=false;
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		typedef std::map<std::string, std::string> BindingMap;
 | 
			
		||||
 | 
			
		||||
		RESTAPIHandler(BindingMap map, Poco::Logger &l, std::vector<std::string> Methods, RESTAPI_GenericServer & Server, bool Internal=false, bool AlwaysAuthorize=true)
 | 
			
		||||
		: Bindings_(std::move(map)), Logger_(l), Methods_(std::move(Methods)), Server_(Server), Internal_(Internal), AlwaysAuthorize_(AlwaysAuthorize) {}
 | 
			
		||||
 | 
			
		||||
		static bool ParseBindings(const std::string & Request, const std::list<const char *> & EndPoints, BindingMap &Keys);
 | 
			
		||||
		void PrintBindings();
 | 
			
		||||
		void ParseParameters();
 | 
			
		||||
 | 
			
		||||
		void AddCORS();
 | 
			
		||||
	 	void SetCommonHeaders(bool CloseConnection=false);
 | 
			
		||||
		void ProcessOptions();
 | 
			
		||||
		void
 | 
			
		||||
		PrepareResponse(Poco::Net::HTTPResponse::HTTPStatus Status = Poco::Net::HTTPResponse::HTTP_OK,
 | 
			
		||||
						bool CloseConnection = false);
 | 
			
		||||
		bool ContinueProcessing();
 | 
			
		||||
		bool IsAuthorized();
 | 
			
		||||
 | 
			
		||||
		uint64_t GetParameter(const std::string &Name, uint64_t Default);
 | 
			
		||||
		std::string GetParameter(const std::string &Name, const std::string &Default);
 | 
			
		||||
		bool GetBoolParameter(const std::string &Name, bool Default);
 | 
			
		||||
 | 
			
		||||
		void BadRequest(const std::string &Reason );
 | 
			
		||||
		void InternalError(const std::string &Reason = "");
 | 
			
		||||
		void UnAuthorized(const std::string &Reason = "");
 | 
			
		||||
		void ReturnObject(Poco::JSON::Object &Object);
 | 
			
		||||
		void NotFound();
 | 
			
		||||
		void OK();
 | 
			
		||||
		void ReturnStatus(Poco::Net::HTTPResponse::HTTPStatus Status,
 | 
			
		||||
						  bool CloseConnection=false);
 | 
			
		||||
		void SendFile(Poco::File & File, const std::string & UUID);
 | 
			
		||||
		void SendHTMLFileBack(Poco::File & File,
 | 
			
		||||
                              const Types::StringPairVec & FormVars);
 | 
			
		||||
        void SendFile(Poco::TemporaryFile &TempAvatar, const std::string &Type, const std::string & Name);
 | 
			
		||||
 | 
			
		||||
        void SendFile(Poco::File & File);
 | 
			
		||||
 | 
			
		||||
        const std::string &GetBinding(const std::string &Name, const std::string &Default);
 | 
			
		||||
		bool InitQueryBlock();
 | 
			
		||||
 | 
			
		||||
		void ReturnCountOnly(uint64_t Count);
 | 
			
		||||
 | 
			
		||||
		[[nodiscard]] static uint64_t Get(const char *Parameter,const Poco::JSON::Object::Ptr &Obj, uint64_t Default=0);
 | 
			
		||||
		[[nodiscard]] static std::string GetS(const char *Parameter,const Poco::JSON::Object::Ptr &Obj, const std::string & Default="");
 | 
			
		||||
		[[nodiscard]] static bool GetB(const char *Parameter,const Poco::JSON::Object::Ptr &Obj, bool Default=false);
 | 
			
		||||
		[[nodiscard]] static uint64_t GetWhen(const Poco::JSON::Object::Ptr &Obj);
 | 
			
		||||
		bool HasParameter(const std::string &QueryParameter, std::string &Value);
 | 
			
		||||
		bool HasParameter(const std::string &QueryParameter, uint64_t & Value);
 | 
			
		||||
 | 
			
		||||
		static bool AssignIfPresent(const Poco::JSON::Object::Ptr &O, const std::string &Field, std::string &Value);
 | 
			
		||||
		static bool AssignIfPresent(const Poco::JSON::Object::Ptr &O, const std::string &Field, uint64_t &Value);
 | 
			
		||||
 | 
			
		||||
		template<typename T> void ReturnObject(const char *Name, const std::vector<T> & Objects) {
 | 
			
		||||
		    Poco::JSON::Object  Answer;
 | 
			
		||||
		    RESTAPI_utils::field_to_json(Answer,Name,Objects);
 | 
			
		||||
            ReturnObject(Answer);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		Poco::Logger & Logger() { return Logger_; }
 | 
			
		||||
 | 
			
		||||
		void handleRequest(Poco::Net::HTTPServerRequest &request,
 | 
			
		||||
                           Poco::Net::HTTPServerResponse &response) final;
 | 
			
		||||
 | 
			
		||||
		virtual void DoGet() = 0 ;
 | 
			
		||||
		virtual void DoDelete() = 0 ;
 | 
			
		||||
		virtual void DoPost() = 0 ;
 | 
			
		||||
		virtual void DoPut() = 0 ;
 | 
			
		||||
 | 
			
		||||
		const Poco::JSON::Object::Ptr & ParseStream();
 | 
			
		||||
 | 
			
		||||
	  protected:
 | 
			
		||||
		BindingMap 					Bindings_;
 | 
			
		||||
		Poco::URI::QueryParameters 	Parameters_;
 | 
			
		||||
		Poco::Logger 				&Logger_;
 | 
			
		||||
		std::string 				SessionToken_;
 | 
			
		||||
		SecurityObjects::UserInfoAndPolicy 	UserInfo_;
 | 
			
		||||
		std::vector<std::string> 	Methods_;
 | 
			
		||||
		QueryBlock					QB_;
 | 
			
		||||
		bool                        Internal_=false;
 | 
			
		||||
		bool                        QueryBlockInitialized_=false;
 | 
			
		||||
		Poco::Net::HTTPServerRequest    *Request= nullptr;
 | 
			
		||||
		Poco::Net::HTTPServerResponse   *Response= nullptr;
 | 
			
		||||
		bool                        AlwaysAuthorize_=true;
 | 
			
		||||
		Poco::JSON::Parser          IncomingParser_;
 | 
			
		||||
		RESTAPI_GenericServer       & Server_;
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	class RESTAPI_UnknownRequestHandler : public RESTAPIHandler {
 | 
			
		||||
	  public:
 | 
			
		||||
		RESTAPI_UnknownRequestHandler(const RESTAPIHandler::BindingMap &bindings, Poco::Logger &L, RESTAPI_GenericServer & Server)
 | 
			
		||||
			: RESTAPIHandler(bindings, L, std::vector<std::string>{}, Server) {}
 | 
			
		||||
        inline void DoGet() override {};
 | 
			
		||||
		inline void DoPost() override {};
 | 
			
		||||
		inline void DoPut() override {};
 | 
			
		||||
		inline void DoDelete() override {};
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
	template<class T>
 | 
			
		||||
	constexpr auto test_has_PathName_method(T*)
 | 
			
		||||
	-> decltype(  T::PathName() , std::true_type{} )
 | 
			
		||||
	{
 | 
			
		||||
		return std::true_type{};
 | 
			
		||||
	}
 | 
			
		||||
	constexpr auto test_has_PathName_method(...) -> std::false_type
 | 
			
		||||
	{
 | 
			
		||||
		return std::false_type{};
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	template<typename T, typename... Args>
 | 
			
		||||
	RESTAPIHandler * RESTAPI_Router(const std::string & RequestedPath, RESTAPIHandler::BindingMap &Bindings, Poco::Logger & Logger, RESTAPI_GenericServer & Server) {
 | 
			
		||||
		static_assert(test_has_PathName_method((T*)nullptr), "Class must have a static PathName() method.");
 | 
			
		||||
		if(RESTAPIHandler::ParseBindings(RequestedPath,T::PathName(),Bindings)) {
 | 
			
		||||
			return new T(Bindings, Logger, Server, false);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if constexpr (sizeof...(Args) == 0) {
 | 
			
		||||
			return new RESTAPI_UnknownRequestHandler(Bindings,Logger, Server);
 | 
			
		||||
		} else {
 | 
			
		||||
			return RESTAPI_Router<Args...>(RequestedPath, Bindings, Logger, Server);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
    template<typename T, typename... Args>
 | 
			
		||||
    RESTAPIHandler * RESTAPI_Router_I(const std::string & RequestedPath, RESTAPIHandler::BindingMap &Bindings, Poco::Logger & Logger, RESTAPI_GenericServer & Server) {
 | 
			
		||||
        static_assert(test_has_PathName_method((T*)nullptr), "Class must have a static PathName() method.");
 | 
			
		||||
        if(RESTAPIHandler::ParseBindings(RequestedPath,T::PathName(),Bindings)) {
 | 
			
		||||
            return new T(Bindings, Logger, Server, true);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if constexpr (sizeof...(Args) == 0) {
 | 
			
		||||
            return new RESTAPI_UnknownRequestHandler(Bindings,Logger, Server);
 | 
			
		||||
        } else {
 | 
			
		||||
            return RESTAPI_Router_I<Args...>(RequestedPath, Bindings, Logger, Server);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#endif //UCENTRAL_RESTAPI_HANDLER_H
 | 
			
		||||
@@ -1,100 +0,0 @@
 | 
			
		||||
//
 | 
			
		||||
//	License type: BSD 3-Clause License
 | 
			
		||||
//	License copy: https://github.com/Telecominfraproject/wlan-cloud-ucentralgw/blob/master/LICENSE
 | 
			
		||||
//
 | 
			
		||||
//	Created by Stephane Bourque on 2021-03-04.
 | 
			
		||||
//	Arilia Wireless Inc.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#include "Poco/JSON/Parser.h"
 | 
			
		||||
 | 
			
		||||
#include "AuthService.h"
 | 
			
		||||
#include "RESTAPI_oauth2Handler.h"
 | 
			
		||||
#include "RESTAPI_protocol.h"
 | 
			
		||||
#include "RESTAPI_server.h"
 | 
			
		||||
 | 
			
		||||
#include "Utils.h"
 | 
			
		||||
 | 
			
		||||
namespace OpenWifi {
 | 
			
		||||
	void RESTAPI_oauth2Handler::DoGet() {
 | 
			
		||||
        if (!IsAuthorized()) {
 | 
			
		||||
            UnAuthorized("Not authorized.");
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        bool GetMe = GetBoolParameter(RESTAPI::Protocol::ME, false);
 | 
			
		||||
        if(GetMe) {
 | 
			
		||||
            Logger_.information(Poco::format("REQUEST-ME(%s): Request for %s", Request->clientAddress().toString(), UserInfo_.userinfo.email));
 | 
			
		||||
            Poco::JSON::Object Me;
 | 
			
		||||
            UserInfo_.userinfo.to_json(Me);
 | 
			
		||||
            ReturnObject(Me);
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        BadRequest("Ill-formed request. Please consult documentation.");
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
    void RESTAPI_oauth2Handler::DoDelete() {
 | 
			
		||||
        if (!IsAuthorized()) {
 | 
			
		||||
            UnAuthorized("Not authorized.");
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        auto Token = GetBinding(RESTAPI::Protocol::TOKEN, "...");
 | 
			
		||||
        if (Token == SessionToken_) {
 | 
			
		||||
            AuthService()->Logout(Token);
 | 
			
		||||
            ReturnStatus(Poco::Net::HTTPResponse::HTTP_NO_CONTENT, true);
 | 
			
		||||
        } else {
 | 
			
		||||
            Logger_.information(Poco::format("BAD-LOGOUT(%s): Request for %s", Request->clientAddress().toString(), UserInfo_.userinfo.email));
 | 
			
		||||
            NotFound();
 | 
			
		||||
        }
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	void RESTAPI_oauth2Handler::DoPost() {
 | 
			
		||||
        auto Obj = ParseStream();
 | 
			
		||||
        auto userId = GetS(RESTAPI::Protocol::USERID, Obj);
 | 
			
		||||
        auto password = GetS(RESTAPI::Protocol::PASSWORD, Obj);
 | 
			
		||||
        auto newPassword = GetS(RESTAPI::Protocol::NEWPASSWORD, Obj);
 | 
			
		||||
        Poco::toLowerInPlace(userId);
 | 
			
		||||
 | 
			
		||||
        if(GetBoolParameter(RESTAPI::Protocol::REQUIREMENTS, false)) {
 | 
			
		||||
            Logger_.information(Poco::format("POLICY-REQUEST(%s): Request.", Request->clientAddress().toString()));
 | 
			
		||||
            Poco::JSON::Object  Answer;
 | 
			
		||||
            Answer.set(RESTAPI::Protocol::PASSWORDPATTERN, AuthService()->PasswordValidationExpression());
 | 
			
		||||
            Answer.set(RESTAPI::Protocol::ACCESSPOLICY, RESTAPI_Server()->GetAccessPolicy());
 | 
			
		||||
            Answer.set(RESTAPI::Protocol::PASSWORDPOLICY, RESTAPI_Server()->GetPasswordPolicy());
 | 
			
		||||
            ReturnObject(Answer);
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if(GetBoolParameter(RESTAPI::Protocol::FORGOTPASSWORD,false)) {
 | 
			
		||||
            //  Send an email to the userId
 | 
			
		||||
            Logger_.information(Poco::format("FORGOTTEN-PASSWORD(%s): Request for %s", Request->clientAddress().toString(), userId));
 | 
			
		||||
            SecurityObjects::UserInfoAndPolicy UInfo;
 | 
			
		||||
            if(AuthService::SendEmailToUser(userId,AuthService::FORGOT_PASSWORD))
 | 
			
		||||
                Logger_.information(Poco::format("Send password reset link to %s",userId));
 | 
			
		||||
            UInfo.webtoken.userMustChangePassword=true;
 | 
			
		||||
            Poco::JSON::Object ReturnObj;
 | 
			
		||||
            UInfo.webtoken.to_json(ReturnObj);
 | 
			
		||||
            ReturnObject(ReturnObj);
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        SecurityObjects::UserInfoAndPolicy UInfo;
 | 
			
		||||
 | 
			
		||||
        auto Code=AuthService()->Authorize(userId, password, newPassword, UInfo);
 | 
			
		||||
        if (Code==AuthService::SUCCESS) {
 | 
			
		||||
            Poco::JSON::Object ReturnObj;
 | 
			
		||||
            UInfo.webtoken.to_json(ReturnObj);
 | 
			
		||||
            ReturnObject(ReturnObj);
 | 
			
		||||
            return;
 | 
			
		||||
        } else {
 | 
			
		||||
            switch(Code) {
 | 
			
		||||
                case AuthService::INVALID_CREDENTIALS: UnAuthorized("Unrecognized credentials (username/password)."); break;
 | 
			
		||||
                case AuthService::PASSWORD_INVALID: UnAuthorized("Invalid password."); break;
 | 
			
		||||
                case AuthService::PASSWORD_ALREADY_USED: UnAuthorized("Password already used previously."); break;
 | 
			
		||||
                case AuthService::USERNAME_PENDING_VERIFICATION: UnAuthorized("User access pending email verification."); break;
 | 
			
		||||
                case AuthService::PASSWORD_CHANGE_REQUIRED: UnAuthorized("Password change expected."); break;
 | 
			
		||||
                default: UnAuthorized("Unrecognized credentials (username/password)."); break;
 | 
			
		||||
            }
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -1,136 +0,0 @@
 | 
			
		||||
//
 | 
			
		||||
//	License type: BSD 3-Clause License
 | 
			
		||||
//	License copy: https://github.com/Telecominfraproject/wlan-cloud-ucentralgw/blob/master/LICENSE
 | 
			
		||||
//
 | 
			
		||||
//	Created by Stephane Bourque on 2021-03-04.
 | 
			
		||||
//	Arilia Wireless Inc.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#ifndef UCENTRALGW_RESTAPI_PROTOCOL_H
 | 
			
		||||
#define UCENTRALGW_RESTAPI_PROTOCOL_H
 | 
			
		||||
 | 
			
		||||
namespace OpenWifi::RESTAPI::Protocol {
 | 
			
		||||
	static const char * CAPABILITIES = "capabilities";
 | 
			
		||||
	static const char * LOGS = "logs";
 | 
			
		||||
	static const char * HEALTHCHECKS = "healthchecks";
 | 
			
		||||
	static const char * STATISTICS = "statistics";
 | 
			
		||||
	static const char * STATUS = "status";
 | 
			
		||||
	static const char * SERIALNUMBER = "serialNumber";
 | 
			
		||||
	static const char * PERFORM = "perform";
 | 
			
		||||
	static const char * CONFIGURE = "configure";
 | 
			
		||||
	static const char * UPGRADE = "upgrade";
 | 
			
		||||
	static const char * REBOOT = "reboot";
 | 
			
		||||
	static const char * FACTORY = "factory";
 | 
			
		||||
	static const char * LEDS = "leds";
 | 
			
		||||
	static const char * TRACE = "trace";
 | 
			
		||||
	static const char * REQUEST = "request";
 | 
			
		||||
	static const char * WIFISCAN = "wifiscan";
 | 
			
		||||
	static const char * EVENTQUEUE = "eventqueue";
 | 
			
		||||
	static const char * RTTY = "rtty";
 | 
			
		||||
	static const char * COMMAND = "command";
 | 
			
		||||
	static const char * STARTDATE = "startDate";
 | 
			
		||||
	static const char * ENDDATE = "endDate";
 | 
			
		||||
	static const char * OFFSET = "offset";
 | 
			
		||||
	static const char * LIMIT = "limit";
 | 
			
		||||
	static const char * LIFETIME = "lifetime";
 | 
			
		||||
	static const char * UUID = "UUID";
 | 
			
		||||
	static const char * DATA = "data";
 | 
			
		||||
	static const char * CONFIGURATION = "configuration";
 | 
			
		||||
	static const char * WHEN = "when";
 | 
			
		||||
	static const char * URI = "uri";
 | 
			
		||||
	static const char * LOGTYPE = "logType";
 | 
			
		||||
	static const char * VALUES = "values";
 | 
			
		||||
	static const char * TYPES = "types";
 | 
			
		||||
	static const char * PAYLOAD = "payload";
 | 
			
		||||
	static const char * KEEPREDIRECTOR = "keepRedirector";
 | 
			
		||||
	static const char * NETWORK = "network";
 | 
			
		||||
	static const char * INTERFACE = "interface";
 | 
			
		||||
	static const char * BANDS = "bands";
 | 
			
		||||
	static const char * CHANNELS = "channels";
 | 
			
		||||
	static const char * VERBOSE = "verbose";
 | 
			
		||||
	static const char * MESSAGE = "message";
 | 
			
		||||
	static const char * STATE = "state";
 | 
			
		||||
	static const char * HEALTHCHECK = "healthcheck";
 | 
			
		||||
	static const char * PCAP_FILE_TYPE = "pcap";
 | 
			
		||||
	static const char * DURATION = "duration";
 | 
			
		||||
	static const char * NUMBEROFPACKETS = "numberOfPackets";
 | 
			
		||||
	static const char * FILTER = "filter";
 | 
			
		||||
	static const char * SELECT = "select";
 | 
			
		||||
	static const char * SERIALONLY = "serialOnly";
 | 
			
		||||
	static const char * COUNTONLY = "countOnly";
 | 
			
		||||
	static const char * DEVICEWITHSTATUS = "deviceWithStatus";
 | 
			
		||||
	static const char * DEVICESWITHSTATUS = "devicesWithStatus";
 | 
			
		||||
	static const char * DEVICES = "devices";
 | 
			
		||||
	static const char * COUNT = "count";
 | 
			
		||||
	static const char * SERIALNUMBERS = "serialNumbers";
 | 
			
		||||
	static const char * CONFIGURATIONS = "configurations";
 | 
			
		||||
	static const char * NAME = "name";
 | 
			
		||||
	static const char * COMMANDS = "commands";
 | 
			
		||||
	static const char * COMMANDUUID = "commandUUID";
 | 
			
		||||
	static const char * FIRMWARES = "firmwares";
 | 
			
		||||
	static const char * TOPIC = "topic";
 | 
			
		||||
	static const char * HOST = "host";
 | 
			
		||||
	static const char * OS = "os";
 | 
			
		||||
	static const char * HOSTNAME = "hostname";
 | 
			
		||||
	static const char * PROCESSORS = "processors";
 | 
			
		||||
	static const char * REASON = "reason";
 | 
			
		||||
	static const char * RELOAD = "reload";
 | 
			
		||||
	static const char * SUBSYSTEMS = "subsystems";
 | 
			
		||||
	static const char * FILEUUID = "uuid";
 | 
			
		||||
	static const char * USERID = "userId";
 | 
			
		||||
	static const char * PASSWORD = "password";
 | 
			
		||||
	static const char * TOKEN = "token";
 | 
			
		||||
	static const char * SETLOGLEVEL = "setloglevel";
 | 
			
		||||
	static const char * GETLOGLEVELS = "getloglevels";
 | 
			
		||||
	static const char * GETSUBSYSTEMNAMES = "getsubsystemnames";
 | 
			
		||||
	static const char * GETLOGLEVELNAMES = "getloglevelnames";
 | 
			
		||||
	static const char * STATS = "stats";
 | 
			
		||||
	static const char * PARAMETERS = "parameters";
 | 
			
		||||
	static const char * VALUE = "value";
 | 
			
		||||
	static const char * LASTONLY = "lastOnly";
 | 
			
		||||
	static const char * NEWEST = "newest";
 | 
			
		||||
	static const char * ACTIVESCAN = "activeScan";
 | 
			
		||||
	static const char * LIST = "list";
 | 
			
		||||
	static const char * TAG = "tag";
 | 
			
		||||
	static const char * TAGLIST = "tagList";
 | 
			
		||||
    static const char * DESCRIPTION = "description";
 | 
			
		||||
    static const char * NOTES = "notes";
 | 
			
		||||
    static const char * DEVICETYPE = "deviceType";
 | 
			
		||||
    static const char * REVISION = "revision";
 | 
			
		||||
    static const char * AGES = "ages";
 | 
			
		||||
    static const char * REVISIONS = "revisions";
 | 
			
		||||
    static const char * DEVICETYPES = "deviceTypes";
 | 
			
		||||
    static const char * LATESTONLY = "latestOnly";
 | 
			
		||||
    static const char * IDONLY = "idOnly";
 | 
			
		||||
    static const char * REVISIONSET = "revisionSet";
 | 
			
		||||
    static const char * DEVICESET = "deviceSet";
 | 
			
		||||
    static const char * HISTORY = "history";
 | 
			
		||||
    static const char * ID = "id";
 | 
			
		||||
    static const char * VERSION = "version";
 | 
			
		||||
    static const char * TIMES = "times";
 | 
			
		||||
    static const char * UPTIME = "uptime";
 | 
			
		||||
    static const char * START = "start";
 | 
			
		||||
 | 
			
		||||
    static const char * NEWPASSWORD = "newPassword";
 | 
			
		||||
    static const char * USERS = "users";
 | 
			
		||||
 | 
			
		||||
    static const char * ERRORTEXT = "errorText";
 | 
			
		||||
    static const char * ERRORCODE = "errorCode";
 | 
			
		||||
    static const char * AVATARID = "avatarId";
 | 
			
		||||
    static const char * UNNAMED = "(unnamed)";
 | 
			
		||||
    static const char * UNSPECIFIED = "(unspecified)";
 | 
			
		||||
    static const char * CONTENTDISPOSITION = "Content-Disposition";
 | 
			
		||||
    static const char * CONTENTTYPE = "Content-Type";
 | 
			
		||||
 | 
			
		||||
    static const char * REQUIREMENTS = "requirements";
 | 
			
		||||
    static const char * PASSWORDPATTERN = "passwordPattern";
 | 
			
		||||
    static const char * ACCESSPOLICY = "accessPolicy";
 | 
			
		||||
    static const char * PASSWORDPOLICY = "passwordPolicy";
 | 
			
		||||
    static const char * FORGOTPASSWORD = "forgotPassword";
 | 
			
		||||
    static const char * ME = "me";
 | 
			
		||||
    static const char * TELEMETRY = "telemetry";
 | 
			
		||||
    static const char * INTERVAL = "interval";
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#endif // UCENTRALGW_RESTAPI_PROTOCOL_H
 | 
			
		||||
@@ -1,92 +0,0 @@
 | 
			
		||||
//
 | 
			
		||||
//	License type: BSD 3-Clause License
 | 
			
		||||
//	License copy: https://github.com/Telecominfraproject/wlan-cloud-ucentralgw/blob/master/LICENSE
 | 
			
		||||
//
 | 
			
		||||
//	Created by Stephane Bourque on 2021-03-04.
 | 
			
		||||
//	Arilia Wireless Inc.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#include <memory>
 | 
			
		||||
 | 
			
		||||
#include "Poco/URI.h"
 | 
			
		||||
 | 
			
		||||
#include "RESTAPI_server.h"
 | 
			
		||||
#include "RESTAPI_oauth2Handler.h"
 | 
			
		||||
#include "RESTAPI_system_command.h"
 | 
			
		||||
#include "RESTAPI_user_handler.h"
 | 
			
		||||
#include "RESTAPI_users_handler.h"
 | 
			
		||||
#include "RESTAPI_action_links.h"
 | 
			
		||||
#include "RESTAPI_systemEndpoints_handler.h"
 | 
			
		||||
#include "RESTAPI_AssetServer.h"
 | 
			
		||||
#include "RESTAPI_avatarHandler.h"
 | 
			
		||||
#include "RESTAPI_email_handler.h"
 | 
			
		||||
 | 
			
		||||
#include "Daemon.h"
 | 
			
		||||
#include "Utils.h"
 | 
			
		||||
 | 
			
		||||
namespace OpenWifi {
 | 
			
		||||
 | 
			
		||||
    class RESTAPI_Server *RESTAPI_Server::instance_ = nullptr;
 | 
			
		||||
 | 
			
		||||
    int RESTAPI_Server::Start() {
 | 
			
		||||
        Logger_.information("Starting.");
 | 
			
		||||
        Server_.InitLogging();
 | 
			
		||||
 | 
			
		||||
        AsserDir_ = Daemon()->ConfigPath("openwifi.restapi.wwwassets");
 | 
			
		||||
        AccessPolicy_ = Daemon()->ConfigGetString("openwifi.document.policy.access", "/wwwassets/access_policy.html");
 | 
			
		||||
        PasswordPolicy_ = Daemon()->ConfigGetString("openwifi.document.policy.password", "/wwwassets/possword_policy.html");
 | 
			
		||||
 | 
			
		||||
        for(const auto & Svr: ConfigServersList_) {
 | 
			
		||||
			Logger_.information(Poco::format("Starting: %s:%s Keyfile:%s CertFile: %s", Svr.Address(), std::to_string(Svr.Port()),
 | 
			
		||||
											 Svr.KeyFile(),Svr.CertFile()));
 | 
			
		||||
 | 
			
		||||
            auto Sock{Svr.CreateSecureSocket(Logger_)};
 | 
			
		||||
 | 
			
		||||
			Svr.LogCert(Logger_);
 | 
			
		||||
			if(!Svr.RootCA().empty())
 | 
			
		||||
				Svr.LogCas(Logger_);
 | 
			
		||||
 | 
			
		||||
            auto Params = new Poco::Net::HTTPServerParams;
 | 
			
		||||
            Params->setMaxThreads(50);
 | 
			
		||||
            Params->setMaxQueued(200);
 | 
			
		||||
			Params->setKeepAlive(true);
 | 
			
		||||
 | 
			
		||||
            auto NewServer = std::make_unique<Poco::Net::HTTPServer>(new RequestHandlerFactory(Server_), Pool_, Sock, Params);
 | 
			
		||||
            NewServer->start();
 | 
			
		||||
            RESTServers_.push_back(std::move(NewServer));
 | 
			
		||||
        }
 | 
			
		||||
        return 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Poco::Net::HTTPRequestHandler *RequestHandlerFactory::createRequestHandler(const Poco::Net::HTTPServerRequest & Request) {
 | 
			
		||||
        Poco::URI uri(Request.getURI());
 | 
			
		||||
        const auto & Path = uri.getPath();
 | 
			
		||||
        RESTAPIHandler::BindingMap Bindings;
 | 
			
		||||
        return RESTAPI_Router<
 | 
			
		||||
                RESTAPI_oauth2Handler,
 | 
			
		||||
                RESTAPI_users_handler,
 | 
			
		||||
                RESTAPI_user_handler,
 | 
			
		||||
                RESTAPI_system_command,
 | 
			
		||||
                RESTAPI_AssetServer,
 | 
			
		||||
                RESTAPI_systemEndpoints_handler,
 | 
			
		||||
                RESTAPI_action_links,
 | 
			
		||||
                RESTAPI_avatarHandler,
 | 
			
		||||
                RESTAPI_email_handler
 | 
			
		||||
                >(Path,Bindings,Logger_,Server_);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void RESTAPI_Server::Stop() {
 | 
			
		||||
        Logger_.information("Stopping ");
 | 
			
		||||
        for( const auto & svr : RESTServers_ )
 | 
			
		||||
            svr->stop();
 | 
			
		||||
        RESTServers_.clear();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void RESTAPI_Server::reinitialize(Poco::Util::Application &self) {
 | 
			
		||||
        Daemon()->LoadConfigurationFile();
 | 
			
		||||
        Logger_.information("Reinitializing.");
 | 
			
		||||
        Stop();
 | 
			
		||||
        Start();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}  // namespace
 | 
			
		||||
@@ -1,71 +0,0 @@
 | 
			
		||||
//
 | 
			
		||||
//	License type: BSD 3-Clause License
 | 
			
		||||
//	License copy: https://github.com/Telecominfraproject/wlan-cloud-ucentralgw/blob/master/LICENSE
 | 
			
		||||
//
 | 
			
		||||
//	Created by Stephane Bourque on 2021-03-04.
 | 
			
		||||
//	Arilia Wireless Inc.
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
#ifndef UCENTRAL_UCENTRALRESTAPISERVER_H
 | 
			
		||||
#define UCENTRAL_UCENTRALRESTAPISERVER_H
 | 
			
		||||
 | 
			
		||||
#include "SubSystemServer.h"
 | 
			
		||||
#include "Poco/Net/HTTPServer.h"
 | 
			
		||||
#include "Poco/Net/HTTPRequestHandler.h"
 | 
			
		||||
#include "Poco/Net/HTTPRequestHandlerFactory.h"
 | 
			
		||||
#include "Poco/Net/HTTPServerRequest.h"
 | 
			
		||||
#include "Poco/Net/NetException.h"
 | 
			
		||||
#include "RESTAPI_GenericServer.h"
 | 
			
		||||
 | 
			
		||||
namespace OpenWifi {
 | 
			
		||||
 | 
			
		||||
    class RESTAPI_Server : public SubSystemServer {
 | 
			
		||||
 | 
			
		||||
    public:
 | 
			
		||||
        static RESTAPI_Server *instance() {
 | 
			
		||||
            if (instance_ == nullptr) {
 | 
			
		||||
                instance_ = new RESTAPI_Server;
 | 
			
		||||
            }
 | 
			
		||||
            return instance_;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        int Start() override;
 | 
			
		||||
        void Stop() override;
 | 
			
		||||
        void reinitialize(Poco::Util::Application &self) override;
 | 
			
		||||
 | 
			
		||||
        inline const std::string & AssetDir() { return AsserDir_; }
 | 
			
		||||
        inline const std::string & GetPasswordPolicy() const { return PasswordPolicy_; }
 | 
			
		||||
        inline const std::string & GetAccessPolicy() const { return AccessPolicy_; }
 | 
			
		||||
    private:
 | 
			
		||||
		static RESTAPI_Server *instance_;
 | 
			
		||||
        std::vector<std::unique_ptr<Poco::Net::HTTPServer>>   RESTServers_;
 | 
			
		||||
		Poco::ThreadPool	Pool_;
 | 
			
		||||
		std::string         AsserDir_;
 | 
			
		||||
		std::string         PasswordPolicy_;
 | 
			
		||||
		std::string         AccessPolicy_;
 | 
			
		||||
		RESTAPI_GenericServer   Server_;
 | 
			
		||||
 | 
			
		||||
        RESTAPI_Server() noexcept:
 | 
			
		||||
            SubSystemServer("RESTAPIServer", "REST-SRV", "openwifi.restapi")
 | 
			
		||||
        {
 | 
			
		||||
        }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    inline RESTAPI_Server * RESTAPI_Server() { return RESTAPI_Server::instance(); };
 | 
			
		||||
 | 
			
		||||
    class RequestHandlerFactory : public Poco::Net::HTTPRequestHandlerFactory {
 | 
			
		||||
        public:
 | 
			
		||||
        RequestHandlerFactory(RESTAPI_GenericServer &Server) :
 | 
			
		||||
                Logger_(RESTAPI_Server()->Logger()),
 | 
			
		||||
                Server_(Server){}
 | 
			
		||||
 | 
			
		||||
            Poco::Net::HTTPRequestHandler *createRequestHandler(const Poco::Net::HTTPServerRequest &request) override;
 | 
			
		||||
        private:
 | 
			
		||||
            Poco::Logger    & Logger_;
 | 
			
		||||
            RESTAPI_GenericServer   &Server_;
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
} //   namespace
 | 
			
		||||
 | 
			
		||||
#endif //UCENTRAL_UCENTRALRESTAPISERVER_H
 | 
			
		||||
@@ -1,146 +0,0 @@
 | 
			
		||||
//
 | 
			
		||||
//	License type: BSD 3-Clause License
 | 
			
		||||
//	License copy: https://github.com/Telecominfraproject/wlan-cloud-ucentralgw/blob/master/LICENSE
 | 
			
		||||
//
 | 
			
		||||
//	Created by Stephane Bourque on 2021-03-04.
 | 
			
		||||
//	Arilia Wireless Inc.
 | 
			
		||||
//
 | 
			
		||||
#include "RESTAPI_system_command.h"
 | 
			
		||||
 | 
			
		||||
#include "Poco/Exception.h"
 | 
			
		||||
#include "Poco/JSON/Parser.h"
 | 
			
		||||
#include "Poco/DateTime.h"
 | 
			
		||||
#include "Poco/DateTimeFormat.h"
 | 
			
		||||
 | 
			
		||||
#include "Daemon.h"
 | 
			
		||||
#include "RESTAPI_protocol.h"
 | 
			
		||||
#include "RESTAPI_errors.h"
 | 
			
		||||
#include <thread>
 | 
			
		||||
#include <chrono>
 | 
			
		||||
 | 
			
		||||
using namespace std::chrono_literals;
 | 
			
		||||
 | 
			
		||||
namespace OpenWifi {
 | 
			
		||||
	void RESTAPI_system_command::DoPost() {
 | 
			
		||||
		auto Obj = ParseStream();
 | 
			
		||||
		if (Obj->has(RESTAPI::Protocol::COMMAND)) {
 | 
			
		||||
			auto Command = Poco::toLower(Obj->get(RESTAPI::Protocol::COMMAND).toString());
 | 
			
		||||
			if (Command == RESTAPI::Protocol::SETLOGLEVEL) {
 | 
			
		||||
				if (Obj->has(RESTAPI::Protocol::SUBSYSTEMS) &&
 | 
			
		||||
					Obj->isArray(RESTAPI::Protocol::SUBSYSTEMS)) {
 | 
			
		||||
					auto ParametersBlock = Obj->getArray(RESTAPI::Protocol::SUBSYSTEMS);
 | 
			
		||||
					for (const auto &i : *ParametersBlock) {
 | 
			
		||||
						Poco::JSON::Parser pp;
 | 
			
		||||
						auto InnerObj = pp.parse(i).extract<Poco::JSON::Object::Ptr>();
 | 
			
		||||
						if (InnerObj->has(RESTAPI::Protocol::TAG) &&
 | 
			
		||||
							InnerObj->has(RESTAPI::Protocol::VALUE)) {
 | 
			
		||||
							auto Name = GetS(RESTAPI::Protocol::TAG, InnerObj);
 | 
			
		||||
							auto Value = GetS(RESTAPI::Protocol::VALUE, InnerObj);
 | 
			
		||||
							Daemon()->SetSubsystemLogLevel(Name, Value);
 | 
			
		||||
							Logger_.information(
 | 
			
		||||
								Poco::format("Setting log level for %s at %s", Name, Value));
 | 
			
		||||
						}
 | 
			
		||||
					}
 | 
			
		||||
					OK();
 | 
			
		||||
					return;
 | 
			
		||||
				}
 | 
			
		||||
			} else if (Command == RESTAPI::Protocol::GETLOGLEVELS) {
 | 
			
		||||
				auto CurrentLogLevels = Daemon()->GetLogLevels();
 | 
			
		||||
				Poco::JSON::Object Result;
 | 
			
		||||
				Poco::JSON::Array Array;
 | 
			
		||||
				for (auto &[Name, Level] : CurrentLogLevels) {
 | 
			
		||||
					Poco::JSON::Object Pair;
 | 
			
		||||
					Pair.set(RESTAPI::Protocol::TAG, Name);
 | 
			
		||||
					Pair.set(RESTAPI::Protocol::VALUE, Level);
 | 
			
		||||
					Array.add(Pair);
 | 
			
		||||
				}
 | 
			
		||||
				Result.set(RESTAPI::Protocol::TAGLIST, Array);
 | 
			
		||||
				ReturnObject(Result);
 | 
			
		||||
				return;
 | 
			
		||||
			} else if (Command == RESTAPI::Protocol::GETLOGLEVELNAMES) {
 | 
			
		||||
				Poco::JSON::Object Result;
 | 
			
		||||
				Poco::JSON::Array LevelNamesArray;
 | 
			
		||||
				const Types::StringVec &LevelNames = Daemon()->GetLogLevelNames();
 | 
			
		||||
				for (const auto &i : LevelNames)
 | 
			
		||||
					LevelNamesArray.add(i);
 | 
			
		||||
				Result.set(RESTAPI::Protocol::LIST, LevelNamesArray);
 | 
			
		||||
				ReturnObject(Result);
 | 
			
		||||
				return;
 | 
			
		||||
			} else if (Command == RESTAPI::Protocol::GETSUBSYSTEMNAMES) {
 | 
			
		||||
				Poco::JSON::Object Result;
 | 
			
		||||
				Poco::JSON::Array LevelNamesArray;
 | 
			
		||||
				const Types::StringVec &SubSystemNames = Daemon()->GetSubSystems();
 | 
			
		||||
				for (const auto &i : SubSystemNames)
 | 
			
		||||
					LevelNamesArray.add(i);
 | 
			
		||||
				Result.set(RESTAPI::Protocol::LIST, LevelNamesArray);
 | 
			
		||||
				ReturnObject(Result);
 | 
			
		||||
				return;
 | 
			
		||||
			} else if (Command == RESTAPI::Protocol::STATS) {
 | 
			
		||||
 | 
			
		||||
			} else if (Command == RESTAPI::Protocol::RELOAD) {
 | 
			
		||||
				if (Obj->has(RESTAPI::Protocol::SUBSYSTEMS) &&
 | 
			
		||||
					Obj->isArray(RESTAPI::Protocol::SUBSYSTEMS)) {
 | 
			
		||||
					auto SubSystems = Obj->getArray(RESTAPI::Protocol::SUBSYSTEMS);
 | 
			
		||||
					std::vector<std::string> Names;
 | 
			
		||||
					for (const auto &i : *SubSystems)
 | 
			
		||||
						Names.push_back(i.toString());
 | 
			
		||||
						std::thread	ReloadThread([Names](){
 | 
			
		||||
						std::this_thread::sleep_for(10000ms);
 | 
			
		||||
						for(const auto &i:Names) {
 | 
			
		||||
						    if(i=="daemon")
 | 
			
		||||
						        Daemon()->Reload();
 | 
			
		||||
						    else
 | 
			
		||||
    							Daemon()->Reload(i);
 | 
			
		||||
						}
 | 
			
		||||
					 });
 | 
			
		||||
					ReloadThread.detach();
 | 
			
		||||
				}
 | 
			
		||||
				OK();
 | 
			
		||||
				return;
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			BadRequest(RESTAPI::Errors::InvalidCommand);
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
		BadRequest(RESTAPI::Errors::MissingOrInvalidParameters);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	void RESTAPI_system_command::DoGet() {
 | 
			
		||||
		std::string Arg;
 | 
			
		||||
		if(HasParameter("command",Arg) && Arg=="info") {
 | 
			
		||||
			Poco::JSON::Object Answer;
 | 
			
		||||
			Answer.set(RESTAPI::Protocol::VERSION, Daemon()->Version());
 | 
			
		||||
			Answer.set(RESTAPI::Protocol::UPTIME, Daemon()->uptime().totalSeconds());
 | 
			
		||||
			Answer.set(RESTAPI::Protocol::START, Daemon()->startTime().epochTime());
 | 
			
		||||
			Answer.set(RESTAPI::Protocol::OS, Poco::Environment::osName());
 | 
			
		||||
			Answer.set(RESTAPI::Protocol::PROCESSORS, Poco::Environment::processorCount());
 | 
			
		||||
			Answer.set(RESTAPI::Protocol::HOSTNAME, Poco::Environment::nodeName());
 | 
			
		||||
 | 
			
		||||
			Poco::JSON::Array   Certificates;
 | 
			
		||||
			auto SubSystems = Daemon()->GetFullSubSystems();
 | 
			
		||||
			std::set<std::string>   CertNames;
 | 
			
		||||
 | 
			
		||||
			for(const auto &i:SubSystems) {
 | 
			
		||||
			    auto Hosts=i->HostSize();
 | 
			
		||||
			    for(uint64_t j=0;j<Hosts;++j) {
 | 
			
		||||
			        auto CertFileName = i->Host(j).CertFile();
 | 
			
		||||
			        if(!CertFileName.empty()) {
 | 
			
		||||
			            auto InsertResult = CertNames.insert(CertFileName);
 | 
			
		||||
			            if(InsertResult.second) {
 | 
			
		||||
			                Poco::JSON::Object  Inner;
 | 
			
		||||
			                Inner.set("filename", CertFileName);
 | 
			
		||||
			                Poco::Crypto::X509Certificate   C(CertFileName);
 | 
			
		||||
			                auto ExpiresOn = C.expiresOn();
 | 
			
		||||
			                Inner.set("expiresOn",ExpiresOn.timestamp().epochTime());
 | 
			
		||||
			                Certificates.add(Inner);
 | 
			
		||||
			            }
 | 
			
		||||
			        }
 | 
			
		||||
			    }
 | 
			
		||||
			}
 | 
			
		||||
			Answer.set("certificates", Certificates);
 | 
			
		||||
			ReturnObject(Answer);
 | 
			
		||||
			return;
 | 
			
		||||
		}
 | 
			
		||||
		BadRequest(RESTAPI::Errors::InvalidCommand);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user