Merge branch 'spring_security'

This commit is contained in:
MathewFrancis 2025-05-16 11:15:53 +05:30
commit 8aa8e0f5fc
35 changed files with 1756 additions and 8 deletions

View File

@ -0,0 +1,913 @@
{
"info": {
"_postman_id": "721d5504-301f-488d-a25b-5e78769eac5a",
"name": "CezenPBX_API",
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json",
"_exporter_id": "29498098"
},
"item": [
{
"name": "create a new endpoint",
"request": {
"method": "POST",
"header": [],
"body": {
"mode": "raw",
"raw": "{\n \"id\": \"1005\",\n// \"transport\": \"transport-udp\",\n// \"context\": \"default\",\n// \"disallow\": \"all\",\n// \"allow\": \"ulaw,alaw\",\n// \"directMedia\": \"no\",\n \"connectedLineMethod\": null,\n \"callerid\": null,\n \"dtmfMode\": null,\n// \"mohsuggest\": \"default\",\n \"mailboxes\": null\n}\n\n// {\n// \"id\": \"1004\",\n// \"transport\": \"transport-udp\",\n// \"aors\": \"1004\",\n// \"auth\": \"1004\",\n// \"context\": \"default\",\n// \"disallow\": \"all\",\n// \"allow\": \"ulaw,alaw\",\n// \"directMedia\": \"no\",\n// \"connectedLineMethod\": null,\n// \"callerid\": \"User <1004>\",\n// \"dtmfMode\": null,\n// \"mohsuggest\": \"default\",\n// \"mailboxes\": \"1004@default\"\n// }",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "http://localhost:8081/cezen/add_user",
"protocol": "http",
"host": [
"localhost"
],
"port": "8081",
"path": [
"cezen",
"add_user"
]
}
},
"response": []
},
{
"name": "create a new extension",
"request": {
"method": "POST",
"header": [],
"body": {
"mode": "raw",
"raw": "{\n \"context\": \"default\",\n \"extension\": \"1005\",\n \"priority\": 1,\n \"app\": \"Dial\",\n \"appdata\": \"PJSIP/1005,20,m(default)\"\n}\n",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "http://localhost:8081/cezen/add_extension",
"protocol": "http",
"host": [
"localhost"
],
"port": "8081",
"path": [
"cezen",
"add_extension"
]
}
},
"response": [
{
"name": "1005",
"originalRequest": {
"method": "POST",
"header": [],
"body": {
"mode": "raw",
"raw": "{\n \"context\": \"default\",\n \"extension\": \"1005\",\n \"priority\": 1,\n \"app\": \"Dial\",\n \"appdata\": \"PJSIP/1005,20,m(default)\"\n}\n",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "http://localhost:8081/cezen/add_extension",
"protocol": "http",
"host": [
"localhost"
],
"port": "8081",
"path": [
"cezen",
"add_extension"
]
}
},
"status": "OK",
"code": 200,
"_postman_previewlanguage": "json",
"header": [
{
"key": "Vary",
"value": "Origin"
},
{
"key": "Vary",
"value": "Access-Control-Request-Method"
},
{
"key": "Vary",
"value": "Access-Control-Request-Headers"
},
{
"key": "X-Content-Type-Options",
"value": "nosniff"
},
{
"key": "X-XSS-Protection",
"value": "0"
},
{
"key": "Cache-Control",
"value": "no-cache, no-store, max-age=0, must-revalidate"
},
{
"key": "Pragma",
"value": "no-cache"
},
{
"key": "Expires",
"value": "0"
},
{
"key": "X-Frame-Options",
"value": "DENY"
},
{
"key": "Content-Type",
"value": "application/json"
},
{
"key": "Transfer-Encoding",
"value": "chunked"
},
{
"key": "Date",
"value": "Thu, 15 May 2025 10:20:09 GMT"
},
{
"key": "Keep-Alive",
"value": "timeout=60"
},
{
"key": "Connection",
"value": "keep-alive"
}
],
"cookie": [],
"body": "{\n \"status\": false,\n \"message\": \"Data likely already exists or DB issue\",\n \"exceptionMessage\": \"could not execute statement [(conn=482) Duplicate entry '1005-PJSIP/1005,20,m(default)-1' for key 'extension_table_unique_val'] [insert into extensions_table (app,appdata,context,exten,priority) values (?,?,?,?,?)]; SQL [insert into extensions_table (app,appdata,context,exten,priority) values (?,?,?,?,?)]; constraint [extension_table_unique_val]\"\n}"
},
{
"name": "test1",
"originalRequest": {
"method": "POST",
"header": [],
"body": {
"mode": "raw",
"raw": "{\n \"context\": \"default\",\n \"extension\": \"test1\",\n \"priority\": 1,\n \"app\": \"Dial\",\n \"appdata\": \"test123rf\"\n}\n",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "http://localhost:8081/cezen/add_extension",
"protocol": "http",
"host": [
"localhost"
],
"port": "8081",
"path": [
"cezen",
"add_extension"
]
}
},
"status": "OK",
"code": 200,
"_postman_previewlanguage": "json",
"header": [
{
"key": "Vary",
"value": "Origin"
},
{
"key": "Vary",
"value": "Access-Control-Request-Method"
},
{
"key": "Vary",
"value": "Access-Control-Request-Headers"
},
{
"key": "X-Content-Type-Options",
"value": "nosniff"
},
{
"key": "X-XSS-Protection",
"value": "0"
},
{
"key": "Cache-Control",
"value": "no-cache, no-store, max-age=0, must-revalidate"
},
{
"key": "Pragma",
"value": "no-cache"
},
{
"key": "Expires",
"value": "0"
},
{
"key": "X-Frame-Options",
"value": "DENY"
},
{
"key": "Content-Type",
"value": "application/json"
},
{
"key": "Transfer-Encoding",
"value": "chunked"
},
{
"key": "Date",
"value": "Thu, 15 May 2025 10:21:06 GMT"
},
{
"key": "Keep-Alive",
"value": "timeout=60"
},
{
"key": "Connection",
"value": "keep-alive"
}
],
"cookie": [],
"body": "{\n \"status\": true,\n \"message\": \"test1 Persisted \",\n \"exceptionMessage\": \"\"\n}"
}
]
},
{
"name": "set_password",
"request": {
"method": "POST",
"header": [],
"body": {
"mode": "raw",
"raw": "{\n \"id\": \"1005\",\n \"authType\": \"userpass\",\n \"userName\": \"1005\",\n \"password\": \"12345\",\n \"md5Cred\": null,\n \"realm\": null\n}\n",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "http://localhost:8081/cezen/set_password",
"protocol": "http",
"host": [
"localhost"
],
"port": "8081",
"path": [
"cezen",
"set_password"
]
}
},
"response": [
{
"name": "set_password",
"originalRequest": {
"method": "POST",
"header": [],
"body": {
"mode": "raw",
"raw": "{\n \"id\": \"1005\",\n \"authType\": \"userpass\",\n \"userName\": \"1005\",\n \"password\": \"12345\",\n \"md5Cred\": null,\n \"realm\": null\n}\n",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "http://localhost:8081/cezen/set_password",
"protocol": "http",
"host": [
"localhost"
],
"port": "8081",
"path": [
"cezen",
"set_password"
]
}
},
"status": "OK",
"code": 200,
"_postman_previewlanguage": "json",
"header": [
{
"key": "Vary",
"value": "Origin"
},
{
"key": "Vary",
"value": "Access-Control-Request-Method"
},
{
"key": "Vary",
"value": "Access-Control-Request-Headers"
},
{
"key": "X-Content-Type-Options",
"value": "nosniff"
},
{
"key": "X-XSS-Protection",
"value": "0"
},
{
"key": "Cache-Control",
"value": "no-cache, no-store, max-age=0, must-revalidate"
},
{
"key": "Pragma",
"value": "no-cache"
},
{
"key": "Expires",
"value": "0"
},
{
"key": "X-Frame-Options",
"value": "DENY"
},
{
"key": "Content-Type",
"value": "application/json"
},
{
"key": "Transfer-Encoding",
"value": "chunked"
},
{
"key": "Date",
"value": "Thu, 15 May 2025 10:21:54 GMT"
},
{
"key": "Keep-Alive",
"value": "timeout=60"
},
{
"key": "Connection",
"value": "keep-alive"
}
],
"cookie": [],
"body": "{\n \"status\": false,\n \"message\": \"Endpoint and password already set \",\n \"exceptionMessage\": \"could not execute statement [(conn=482) Duplicate entry '1005' for key 'PRIMARY'] [insert into ps_auths (auth_type,md5_cred,password,realm,username,id) values (?,?,?,?,?,?)]; SQL [insert into ps_auths (auth_type,md5_cred,password,realm,username,id) values (?,?,?,?,?,?)]; constraint [PRIMARY]\"\n}"
}
]
},
{
"name": "SetAORS",
"request": {
"method": "POST",
"header": [],
"body": {
"mode": "raw",
"raw": "{\n \"id\": \"1005\",\n \"maxContacts\": 1\n}",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "http://localhost:8081/cezen/set_aors",
"protocol": "http",
"host": [
"localhost"
],
"port": "8081",
"path": [
"cezen",
"set_aors"
]
}
},
"response": [
{
"name": "SetAORS",
"originalRequest": {
"method": "POST",
"header": [],
"body": {
"mode": "raw",
"raw": "{\n \"id\": \"1005\",\n \"maxContacts\": 1\n}",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "http://localhost:8081/cezen/set_aors",
"protocol": "http",
"host": [
"localhost"
],
"port": "8081",
"path": [
"cezen",
"set_aors"
]
}
},
"status": "OK",
"code": 200,
"_postman_previewlanguage": "json",
"header": [
{
"key": "Vary",
"value": "Origin"
},
{
"key": "Vary",
"value": "Access-Control-Request-Method"
},
{
"key": "Vary",
"value": "Access-Control-Request-Headers"
},
{
"key": "X-Content-Type-Options",
"value": "nosniff"
},
{
"key": "X-XSS-Protection",
"value": "0"
},
{
"key": "Cache-Control",
"value": "no-cache, no-store, max-age=0, must-revalidate"
},
{
"key": "Pragma",
"value": "no-cache"
},
{
"key": "Expires",
"value": "0"
},
{
"key": "X-Frame-Options",
"value": "DENY"
},
{
"key": "Content-Type",
"value": "application/json"
},
{
"key": "Transfer-Encoding",
"value": "chunked"
},
{
"key": "Date",
"value": "Thu, 15 May 2025 10:22:12 GMT"
},
{
"key": "Keep-Alive",
"value": "timeout=60"
},
{
"key": "Connection",
"value": "keep-alive"
}
],
"cookie": [],
"body": "{\n \"status\": true,\n \"message\": \"1005 Persisted \",\n \"exceptionMessage\": \"\"\n}"
}
]
},
{
"name": "DeleteExtension",
"request": {
"method": "DELETE",
"header": [],
"url": {
"raw": "http://localhost:8081/cezen/delete_extension?sipNumber=testEndPoint",
"protocol": "http",
"host": [
"localhost"
],
"port": "8081",
"path": [
"cezen",
"delete_extension"
],
"query": [
{
"key": "sipNumber",
"value": "testEndPoint"
}
]
}
},
"response": [
{
"name": "DeleteExtension",
"originalRequest": {
"method": "DELETE",
"header": [],
"url": {
"raw": "http://localhost:8081/cezen/delete_extension?sipNumber=1005",
"protocol": "http",
"host": [
"localhost"
],
"port": "8081",
"path": [
"cezen",
"delete_extension"
],
"query": [
{
"key": "sipNumber",
"value": "1005"
}
]
}
},
"status": "OK",
"code": 200,
"_postman_previewlanguage": "json",
"header": [
{
"key": "Vary",
"value": "Origin"
},
{
"key": "Vary",
"value": "Access-Control-Request-Method"
},
{
"key": "Vary",
"value": "Access-Control-Request-Headers"
},
{
"key": "X-Content-Type-Options",
"value": "nosniff"
},
{
"key": "X-XSS-Protection",
"value": "0"
},
{
"key": "Cache-Control",
"value": "no-cache, no-store, max-age=0, must-revalidate"
},
{
"key": "Pragma",
"value": "no-cache"
},
{
"key": "Expires",
"value": "0"
},
{
"key": "X-Frame-Options",
"value": "DENY"
},
{
"key": "Content-Type",
"value": "application/json"
},
{
"key": "Transfer-Encoding",
"value": "chunked"
},
{
"key": "Date",
"value": "Thu, 15 May 2025 10:22:35 GMT"
},
{
"key": "Keep-Alive",
"value": "timeout=60"
},
{
"key": "Connection",
"value": "keep-alive"
}
],
"cookie": [],
"body": "true"
}
]
},
{
"name": "Add_a_global_extension_feature",
"request": {
"method": "PUT",
"header": [],
"body": {
"mode": "raw",
"raw": "{\n \"context\": \"default\",\n \"extension\": \"w\",\n \"priority\": 5,\n \"app\": \"Dial\",\n \"appdata\": \"W conf\"\n}\n",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "http://localhost:8081/cezen/add_feature",
"protocol": "http",
"host": [
"localhost"
],
"port": "8081",
"path": [
"cezen",
"add_feature"
]
}
},
"response": [
{
"name": "Add_a_global_extension_feature",
"originalRequest": {
"method": "PUT",
"header": [],
"body": {
"mode": "raw",
"raw": "{\n \"context\": \"default\",\n \"extension\": \"w\",\n \"priority\": 5,\n \"app\": \"Dial\",\n \"appdata\": \"W conf\"\n}\n",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "http://localhost:8081/cezen/add_feature",
"protocol": "http",
"host": [
"localhost"
],
"port": "8081",
"path": [
"cezen",
"add_feature"
]
}
},
"status": "OK",
"code": 200,
"_postman_previewlanguage": "json",
"header": [
{
"key": "Vary",
"value": "Origin"
},
{
"key": "Vary",
"value": "Access-Control-Request-Method"
},
{
"key": "Vary",
"value": "Access-Control-Request-Headers"
},
{
"key": "X-Content-Type-Options",
"value": "nosniff"
},
{
"key": "X-XSS-Protection",
"value": "0"
},
{
"key": "Cache-Control",
"value": "no-cache, no-store, max-age=0, must-revalidate"
},
{
"key": "Pragma",
"value": "no-cache"
},
{
"key": "Expires",
"value": "0"
},
{
"key": "X-Frame-Options",
"value": "DENY"
},
{
"key": "Content-Type",
"value": "application/json"
},
{
"key": "Transfer-Encoding",
"value": "chunked"
},
{
"key": "Date",
"value": "Thu, 15 May 2025 10:53:48 GMT"
},
{
"key": "Keep-Alive",
"value": "timeout=60"
},
{
"key": "Connection",
"value": "keep-alive"
}
],
"cookie": [],
"body": "{\n \"status\": false,\n \"message\": \"w configured as default Already exists\",\n \"exceptionMessage\": \"jakarta.persistence.TransactionRequiredException: No EntityManager with actual transaction available for current thread - cannot reliably process 'persist' call\"\n}"
}
]
},
{
"name": "signup",
"request": {
"auth": {
"type": "noauth"
},
"method": "POST",
"header": [],
"body": {
"mode": "raw",
"raw": "{\n \"userName\": \"Mathew Francis\",\n \"email\":\"asda@gmail.com\",\n \"password\": \"1234567890\",\n \"confirmPassword\": \"1234567890\"\n}",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "http://localhost:8081/open/signup",
"protocol": "http",
"host": [
"localhost"
],
"port": "8081",
"path": [
"open",
"signup"
]
}
},
"response": []
},
{
"name": "login",
"protocolProfileBehavior": {
"disableBodyPruning": true
},
"request": {
"auth": {
"type": "basic",
"basic": [
{
"key": "password",
"value": "1234",
"type": "string"
},
{
"key": "username",
"value": "mathew",
"type": "string"
}
]
},
"method": "GET",
"header": [],
"body": {
"mode": "raw",
"raw": ""
},
"url": {
"raw": "http://localhost:8081/open/login",
"protocol": "http",
"host": [
"localhost"
],
"port": "8081",
"path": [
"open",
"login"
]
}
},
"response": [
{
"name": "login",
"originalRequest": {
"method": "GET",
"header": [],
"body": {
"mode": "raw",
"raw": ""
},
"url": {
"raw": "http://localhost:8081/open/login",
"protocol": "http",
"host": [
"localhost"
],
"port": "8081",
"path": [
"open",
"login"
]
}
},
"status": "OK",
"code": 200,
"_postman_previewlanguage": "json",
"header": [
{
"key": "Vary",
"value": "Origin"
},
{
"key": "Vary",
"value": "Access-Control-Request-Method"
},
{
"key": "Vary",
"value": "Access-Control-Request-Headers"
},
{
"key": "Set-Cookie",
"value": "Authorization=eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJNYXRoZXcgRnJhbmNpcyIsInN1YiI6IkpXVF9Ub2tlbiIsInVzZXJuYW1lIjoiY29tLmV4YW1wbGUuY2V6ZW5QQlguZW50aXR5LnVzZXIuVXNlckVudGl0eUAzMGI5ZjFlOCIsImF1dGhvcml0aWVzIjoiUk9MRV9hZG1pbiIsImlhdCI6MTc0NzI5MDAyMCwiZXhwIjoxNzQ3MzIwMDIwfQ.kjyArki3Cbc90Jjf68pl5iPeg61GWaxb6yT6ivTNXes; Path=/; Secure; HttpOnly"
},
{
"key": "X-Content-Type-Options",
"value": "nosniff"
},
{
"key": "X-XSS-Protection",
"value": "0"
},
{
"key": "Cache-Control",
"value": "no-cache, no-store, max-age=0, must-revalidate"
},
{
"key": "Pragma",
"value": "no-cache"
},
{
"key": "Expires",
"value": "0"
},
{
"key": "X-Frame-Options",
"value": "DENY"
},
{
"key": "Content-Type",
"value": "application/json"
},
{
"key": "Transfer-Encoding",
"value": "chunked"
},
{
"key": "Date",
"value": "Thu, 15 May 2025 06:20:20 GMT"
},
{
"key": "Keep-Alive",
"value": "timeout=60"
},
{
"key": "Connection",
"value": "keep-alive"
}
],
"cookie": [],
"body": "{\n \"status\": false,\n \"message\": \"Login not yet implemented\",\n \"exceptionMessage\": \"Login not yet implemented\"\n}"
}
]
},
{
"name": "logout",
"request": {
"method": "POST",
"header": [],
"url": {
"raw": "http://localhost:8081/logout",
"protocol": "http",
"host": [
"localhost"
],
"port": "8081",
"path": [
"logout"
]
}
},
"response": []
}
]
}

View File

@ -0,0 +1,102 @@
USE asterisk_db;
SHOW DATABASES;
SHOW TABLES;
SELECT * FROM `ps_endpoints`;
DELETE FROM `ps_endpoints` WHERE `id` = '1004';
SELECT * FROM `extensions_table`;
DESCRIBE `extensions_table`;
ALTER TABLE `extensions_table`
ADD CONSTRAINT `extension_table_unique_val_two_check` UNIQUE (`exten`, `appdata`);
ALTER TABLE `extensions_table`
DROP INDEX `extension_table_unique_val`;
ALTER TABLE `extensions_table`
ADD CONSTRAINT `extension_table_unique_val` UNIQUE (`exten`, `appdata`, `priority`);
DELETE FROM `extensions_table` WHERE priority = 4 and exten = "1005" ;
DELETE FROM `extensions_table` WHERE exten = "1005" OR exten = "1004";
DELETE FROM `ps_endpoints` WHERE id = "1004" OR id = "1005";
DELETE FROM `extensions_table` WHERE exten = "1004" OR exten = "1005";
DELETE FROM `ps_auths` WHERE id = "1004" OR id = "1005";
DELETE FROM `ps_aors` WHERE id = "1004" OR id = "1005";
SELECT * FROM `extensions_table` WHERE app = "Dial";
SELECT * FROM extensions_table WHERE context = 'default' AND exten = '1005';
SELECT * FROM extensions_table WHERE exten = '1004' AND context = 'default';
SELECT * FROM `ps_endpoints`;
SELECT * FROM `extensions_table`;
--
SELECT * FROM ps_auths;
SELECT * FROM ps_aors;
DESCRIBE `ps_auths`;
DESCRIBE `ps_aors`;
DESCRIBE `extensions_table`;
INSERT INTO `ps_aors`(`id`,`max_contacts`) VALUES ("1004", 1);
INSERT INTO `ps_auths`(`id`, `auth_type`, `username`, `password`, `md5_cred`, `realm`) VALUES("1004", "userpass", "1004", "12345", null, null);
-- USER ROLES ROLE GOES HERE
CREATE TABLE `roles`(
`role_id` INTEGER NOT NULL AUTO_INCREMENT,
`role_name` VARCHAR(20) UNIQUE NOT NULL,
CONSTRAINT `roles_pk` PRIMARY KEY (`role_id`)
)ENGINE = 'Innodb' AUTO_INCREMENT = 1, DEFAULT CHARSET 'latin1';
DESCRIBE `roles`;
CREATE TABLE `user_roles`(
`u_id` INTEGER NOT NULL,
`role_id` INTEGER NOT NULL,
CONSTRAINT `user_roles_pk` PRIMARY KEY(`u_id`,`role_id`)
)ENGINE = 'Innodb' AUTO_INCREMENT = 1, DEFAULT CHARSET 'latin1';
-- foreign key to be added to this table in alter table form
CREATE TABLE `user`(
`u_id` INTEGER NOT NULL AUTO_INCREMENT,
`user_name` VARCHAR(70) UNIQUE NOT NULL,
`password` VARCHAR(68) NOT NULL,
-- fk to uder_account
CONSTRAINT `user_table_pk` PRIMARY KEY(`u_id`)
)ENGINE = 'Innodb', AUTO_INCREMENT = 1, DEFAULT CHARSET 'latin1';
ALTER TABLE `user` ADD COLUMN `user_email_id` VARCHAR(50) UNIQUE NOT NULL;
ALTER TABLE `user_roles` ADD CONSTRAINT `user_lones_U_fk_to_user` FOREIGN KEY(`u_id`) REFERENCES `user`(`u_id`);
ALTER TABLE `user_roles` ADD CONSTRAINT `user_lones_R_fk_to_user` FOREIGN KEY(`role_id`) REFERENCES `roles`(`role_id`);
DESC `user_roles`;
INSERT `roles`(`role_name`) VALUES ('ROLE_admin');
SELECT * FROM `user`;
SELECT * FROM `user_roles`;
SELECT * FROM `roles`;
DELETE FROM `user` WHERE `user_name` = 'Mathew Francis';
DELETE FROM `user_roles` WHERE `u_id` = (SELECT `u_id` FROM `user_roles` LIMIT 1);
DELETE FROM `roles` WHERE `roles`.`role_name` = 'ROLE_admin'

Binary file not shown.

Binary file not shown.

View File

@ -70,6 +70,35 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- spring security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
<!-- JWT dependency -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.12.3</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.12.3</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.12.3</version>
<scope>runtime</scope>
</dependency>
</dependencies>
<build>

View File

@ -8,6 +8,7 @@ import jakarta.persistence.TypedQuery;
import jakarta.transaction.Transactional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.UnexpectedRollbackException;
@Repository
public class BasicAsteriskOpsDAO implements CezenPbxOpsDAO {
@ -114,21 +115,21 @@ public class BasicAsteriskOpsDAO implements CezenPbxOpsDAO {
}
@Override
@Transactional
public ReturnStatus saveAnExtensionByCharacters(ExtensionsTable extensionsTable) {
try{
this.entityManager.persist(extensionsTable);
try {
this.doPersist(extensionsTable); // calls @Transactional method
return new ReturnStatus(true,
extensionsTable.getExtension() +" configured as "+ extensionsTable.getContext() +" added",
"");
}catch (Exception e){
} catch (Exception e) {
return new ReturnStatus(false,
extensionsTable.getExtension() +" configured as "+ extensionsTable.getContext() +" Already exists",
e.toString());
}
}
@Transactional
public void doPersist(ExtensionsTable extensionsTable) {
entityManager.persist(extensionsTable);
}
}

View File

@ -0,0 +1,18 @@
package com.example.cezenPBX.DAO;
import com.example.cezenPBX.DTO.ReturnStatus;
import com.example.cezenPBX.entity.user.UserEntity;
// TODO only one admin allowed ... once the admin creates an
// account they should not be able to make the account again
// admin login, logout and signup DAO operations
public interface UserOpsDAO {
// check if user exists;
public boolean checkIfAdminExists(UserEntity userEntity) throws Exception;
// admin login
public ReturnStatus adminSetUsernameAndPassword(UserEntity userEntity);
public UserEntity getUserByUserName(String userName);
}

View File

@ -0,0 +1,75 @@
package com.example.cezenPBX.DAO;
import com.example.cezenPBX.DTO.ReturnStatus;
import com.example.cezenPBX.entity.user.Role;
import com.example.cezenPBX.entity.user.UserEntity;
import jakarta.persistence.EntityManager;
import jakarta.persistence.Query;
import jakarta.persistence.TypedQuery;
import jakarta.transaction.Transactional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
@Repository
public class UserOpsDAOImpl implements UserOpsDAO{
@Autowired
private EntityManager entityManager;
@Override
public boolean checkIfAdminExists(UserEntity userEntity){
// check if info exists in the database users table
// this enforces the fact that only one user can exist
Query query = entityManager.createQuery(
"SELECT COUNT(u) FROM UserEntity u JOIN u.roles r WHERE r.role = :roleName");
query.setParameter("roleName", "ROLE_Admin");
Long count = (Long) query.getSingleResult();
return count > 0;
}
// get roles from the database
// Admin sets a username and password for the first time
@Override
@Transactional
public ReturnStatus adminSetUsernameAndPassword(UserEntity userEntity) {
try {
if (checkIfAdminExists(userEntity)) {
return new ReturnStatus(false, "Admin already exists", "");
}
// Fetch existing ROLE_Admin from DB
TypedQuery<Role> query = entityManager.createQuery("FROM Role r WHERE r.role = :roleName", Role.class)
.setParameter("roleName", "ROLE_Admin");
Role role = query.getSingleResult();
userEntity.setARole(role);
// Persist the user
entityManager.persist(userEntity);
return new ReturnStatus(true, "Admin created", "");
} catch (Exception e) {
return new ReturnStatus(false, "Admin creation failed", e.getMessage());
}
}
// get user details by username
// throws an exception if the user doesn't exist
// exception is caught and returns null ... custom authentication provider must catch the exception
@Override
public UserEntity getUserByUserName(String userName) {
try{
TypedQuery<UserEntity> query = this.entityManager
.createQuery("SELECT u FROM UserEntity u JOIN FETCH u.roles AS r WHERE u.userName = :userName", UserEntity.class);
query.setParameter("userName", userName);
return query.getSingleResult();
}catch ( Exception e){
return null;
}
}
}

View File

@ -0,0 +1,20 @@
package com.example.cezenPBX.DTO.user;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
public record AdminSetPasswordDTO(
@NotBlank(message = "Username cannot be blank")
String userName,
@Email(message = "Email is not valid")
String email,
@NotBlank(message = "Password cannot be blank")
@Size(min = 3, message = "Password must be at least 8 characters long")
String password,
@NotBlank(message = "Confirm password cannot be blank")
String confirmPassword
) {}

View File

@ -0,0 +1,112 @@
package com.example.cezenPBX.config;
import com.example.cezenPBX.security.JWTTokenGeneratorFilter;
import com.example.cezenPBX.security.JWTTokenValidatorFilter;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import org.springframework.security.web.csrf.CookieCsrfTokenRepository;
import org.springframework.security.web.csrf.CsrfTokenRequestAttributeHandler;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import java.util.Collections;
import java.util.List;
// this class will handel the routs that are protected and
// allow spring security to accept login details from our custom login page
@Configuration
public class CezenLoginSecurityChain {
@Bean
SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
//the token is generated here
CsrfTokenRequestAttributeHandler requestHandler = new CsrfTokenRequestAttributeHandler();
requestHandler.setCsrfRequestAttributeName("_csrf");
//CSRF cookie
final CookieCsrfTokenRepository cookieCsrfTokenRepo = new CookieCsrfTokenRepository();
//make secure true when using only https
cookieCsrfTokenRepo.setCookieCustomizer(responseCookieBuilder -> responseCookieBuilder.secure(true));
// bellow line is used when you are using JWT tokens instead of jSession session keys but i put always because i guess CSRF token needs it
http.
logout((logout) -> logout.deleteCookies("Authorization", "JSESSIONID", "XSRF-TOKEN"))
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
//now because we aare sending the JWT token to The UI Application in a Header
//we need to manage it in the CORs config
.cors(corsCustomizer -> corsCustomizer.configurationSource(new CorsConfigurationSource() {
@Override
public CorsConfiguration getCorsConfiguration(HttpServletRequest request) {
//check CORs and CSRF in Previous commits
CorsConfiguration config = new CorsConfiguration();
// config.setAllowedOrigins(Collections.singletonList("http://localhost:4200"));
config.setAllowedOrigins(Collections.singletonList("*"));
config.setAllowedMethods(Collections.singletonList("*"));
config.setAllowCredentials(true);
config.setAllowedHeaders(Collections.singletonList("*"));
//the JWT will be sent to UI under Authorization header and XSR under X-XSRF-TOKEN
config.setExposedHeaders(List.of("Authorization", "X-XSRF-TOKEN"));
config.setMaxAge(3600L);
return config;
}
}))
//temporarily disabling cross sight resource forgery
.csrf(AbstractHttpConfigurer::disable)
// .csrf((csrf) ->
// csrf.csrfTokenRequestHandler(requestHandler).
// ignoringRequestMatchers("/open/signup","/open/login","/user/getXSRfToken")
// //.csrfTokenRepository(new CookieCsrfTokenRepository())
// .csrfTokenRepository(cookieCsrfTokenRepo)
// )
//.addFilterAfter(new CsrfCookieFilter(), BasicAuthenticationFilter.class)
//token generation after BasicAuthenticationFilter.class
.addFilterAfter(new JWTTokenGeneratorFilter(), BasicAuthenticationFilter.class)
//then position the verification filter
.addFilterBefore(new JWTTokenValidatorFilter(), BasicAuthenticationFilter.class)
.authorizeHttpRequests((requests) -> requests
//only admin can use this rout
//user roles :- ROLE_admin ROLE_employee ROLE_manager ROLE_user
.requestMatchers(
"/cezen/add_user",
"/cezen/add_feature",
"/cezen/delete_extension",
"/cezen/set_aors",
"/cezen/set_password",
"/cezen/add_extension"
).hasAnyRole("admin")
//any one who is authenticated can access /logout
.requestMatchers("/open/login", "/user/getXSRfToken", "/logout").authenticated()
//all the rest are open to public
.requestMatchers("/open/signup").permitAll()
//.requestMatchers(HttpMethod.POST, "/open/**").permitAll()
)
// redirect to /login if the user is not authenticated Customizer.withDefaults() enables a security feature using the defaults provided by Spring Security
.formLogin(Customizer.withDefaults())
.httpBasic(Customizer.withDefaults());
System.out.print("Security chain configured");
return http.build();
}
// to encode the password
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}

View File

@ -0,0 +1,81 @@
package com.example.cezenPBX.config;
import com.example.cezenPBX.DAO.UserOpsDAO;
import com.example.cezenPBX.entity.user.UserEntity;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
@Component
public class CustomAuthenticationProviderForCezen implements AuthenticationProvider {
@Autowired
private UserOpsDAO userOpsDAO;
@Autowired
private PasswordEncoder passwordEncoder;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
//get credentials from login form
String username = authentication.getName();
String pwd = authentication.getCredentials().toString();
//sanity check
if (username.isEmpty() || pwd.isEmpty()) return null;
//check for employee
UserEntity user = null;
try {
//check if employee exists if yes then fetch details
user = this.userOpsDAO.getUserByUserName(username);
} catch (Exception e) {
System.out.println(e.toString());
return null;
}
if (passwordEncoder.matches(pwd, user.getPassword())) {
//then it is a match a number of springs granted authorities
List<GrantedAuthority> authorities = new ArrayList<>();
//loop through the users authorities and add each of them to simple granted authority
try {
//check if user is part of permission set for admin signing in
boolean isAdmin = false;
for(var permission : user.getRoles()){
if(permission.getRole().equals("ROLE_admin")) isAdmin = true;
}
if(!isAdmin) throw new BadCredentialsException("no employee permission for given employee");
user.getRoles().forEach(a -> authorities.add(new SimpleGrantedAuthority(a.getRole())));
} catch (Exception e) {
//use/**/r doesn't have permissions or roles = null
System.out.println(e.toString());
return null;
}
return new UsernamePasswordAuthenticationToken(user.getUserName(), pwd, authorities);
} else {
throw new BadCredentialsException("Invalid password!");
}
}
@Override
public boolean supports(Class<?> authentication) {
//tells spring that i want to support username password style of auth
return (UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication));
}
}

View File

@ -0,0 +1,7 @@
package com.example.cezenPBX.constents;
public interface SecurityConstants {
public static final String JWT_KEY = ";sdmn3426FHB426RH62389;]['/.sdwswa";
public static final String JWT_HEADER = "Authorization";
}

View File

@ -0,0 +1,38 @@
package com.example.cezenPBX.controller;
import com.example.cezenPBX.DTO.ReturnStatus;
import com.example.cezenPBX.DTO.user.AdminSetPasswordDTO;
import com.example.cezenPBX.service.PbxUserService;
import jakarta.validation.Valid;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/open")
public class SignUpController {
@Autowired
private PbxUserService pbxUserService;
//sign up route
@PostMapping("/signup")
public ReturnStatus signUp(@RequestBody @Valid AdminSetPasswordDTO adminSetPasswordDTO){
return this.pbxUserService.adminSetUserNamePasswordSet(
adminSetPasswordDTO.userName(),
adminSetPasswordDTO.password(),
adminSetPasswordDTO.confirmPassword(),
adminSetPasswordDTO.email()
);
}
// and a login route
@GetMapping("/login")
public ReturnStatus login(){
return new ReturnStatus(true, "Welcome user authenticated successfully", "");
}
// forgot password
}

View File

@ -0,0 +1,65 @@
package com.example.cezenPBX.entity.user;
import jakarta.persistence.*;
import java.util.Collection;
@Entity
@Table(name = "roles")
final public class Role {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "role_id")
private int id;
//remember ROLE_
@Column(name = "role_name")
private String role;
//all employees under this role
// map by may be required
@ManyToMany(
fetch = FetchType.LAZY,
cascade = {
//The detach operation removes the entity from the persistent context. When we use CascadeType.DETACH, the child entity will also get removed from the persistent context.
CascadeType.DETACH,
CascadeType.MERGE,
CascadeType.PERSIST,
CascadeType.REFRESH,
}
//cascade = CascadeType.ALL
)
@JoinTable(
name = "user_roles",
joinColumns = @JoinColumn(name = "role_id"),
inverseJoinColumns = @JoinColumn(name = "u_id")
)
private Collection<UserEntity> employees;
public Role(){}
public Role(String role) {
this.role = role;
}
public int getId() {
return id;
}
public String getRole() {
return role;
}
public void setRole(String role) {
this.role = role;
}
public Collection<UserEntity> getEmployees() {
return employees;
}
public void setEmployees(Collection<UserEntity> employees) {
this.employees = employees;
}
}

View File

@ -0,0 +1,90 @@
package com.example.cezenPBX.entity.user;
import com.fasterxml.jackson.annotation.JsonIgnore;
import jakarta.persistence.*;
import java.util.Collection;
import java.util.HashSet;
@Entity
@Table(name = "user")
final public class UserEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "u_id")
private int id;
@Column(name = "user_name")
private String userName;
@JsonIgnore
@Column(name = "password")
private String password;
@Column(name = "user_email_id")
private String email;
//ROLE
@ManyToMany(
fetch = FetchType.LAZY,
cascade = {
//The detach operation removes the entity from the persistent context. When we use CascadeType.DETACH, the child entity will also get removed from the persistent context.
CascadeType.DETACH,
CascadeType.MERGE,
CascadeType.PERSIST,
CascadeType.REFRESH,
}
//cascade = CascadeType.ALL
)
@JoinTable(
name = "user_roles",
joinColumns = @JoinColumn(name = "u_id"),
inverseJoinColumns = @JoinColumn(name = "role_id")
)
private Collection<Role> roles;
public UserEntity(){}
public UserEntity(String userName, String password, String email) {
this.userName = userName;
this.password = password;
this.email = email;
}
public int getId() {
return id;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public Collection<Role> getRoles() {
return roles;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public void setARole(Role role){
if(this.roles == null){
this.roles = new HashSet<Role>();
}
this.roles.add(role);
}
}

View File

@ -0,0 +1,81 @@
package com.example.cezenPBX.security;
import com.example.cezenPBX.constents.SecurityConstants;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.crypto.SecretKey;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Collection;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;
public class JWTTokenGeneratorFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
//at this point the user is authenticated we just have to send the token back
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (null != authentication) {
//get the JWT key from the contents we defined
// Keys, Jwts class comes from pom.xml
SecretKey key = Keys.hmacShaKeyFor(SecurityConstants.JWT_KEY.getBytes(StandardCharsets.UTF_8));
//creating a JWT token
// issuer issues a jwt token
//subject can be any value
String jwt = Jwts.builder().issuer("Mathew Francis").subject("JWT_Token")
//building the token
.claim("username", authentication.getName())
.claim("authorities", populateAuthorities(authentication.getAuthorities()))
.issuedAt(new Date())
.expiration(new Date((new Date()).getTime() + 30000000))
//signing it with the key we set on line 35
.signWith(key).compact();
//SecurityConstants.JWT_HEADER, in the Constants SecurityConstants folder
//response.setHeader(SecurityConstants.JWT_HEADER, jwt);
//uncomment for cookie based saving
Cookie cookie = new Cookie(SecurityConstants.JWT_HEADER,jwt);
cookie.setHttpOnly(true);
cookie.setSecure(true);
cookie.setPath("/");
response.addCookie(cookie);
System.out.println("JWT Generated");
}
System.out.println("Intercepted");
System.out.println(response.getHeader("X-XSRF-TOKEN"));
filterChain.doFilter(request, response);
}
//only generate if the path is login
//other words this method will return false for /login
@Override
protected boolean shouldNotFilter(HttpServletRequest request) {
return !request.getServletPath().equals("/open/login");
//return !(request.getServletPath().equals("/open/signup") || request.getServletPath().equals("/open/login"));
}
// gets the authority's from granted authority which we set in the configuration CustomAuthenticationProvider class
// plug in user auth into jwt token
private String populateAuthorities(Collection<? extends GrantedAuthority> collection) {
Set<String> authoritiesSet = new HashSet<>();
for (GrantedAuthority authority : collection) {
authoritiesSet.add(authority.getAuthority());
}
return String.join(",", authoritiesSet);
}
}

View File

@ -0,0 +1,81 @@
package com.example.cezenPBX.security;
import com.example.cezenPBX.constents.SecurityConstants;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.crypto.SecretKey;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
public class JWTTokenValidatorFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
// SecurityConstants
// public static final String JWT_KEY = "jxgEQeXHuPq8VdbyYFNkANdudQ53YUn4";
// public static final String JWT_HEADER = "Authorization";
//String jwt = request.getHeader(SecurityConstants.JWT_HEADER);
//below is the COOKIE approach
String jwt = null;
for(var cookie : request.getCookies()){
if(cookie.getName().equals("Authorization")){
System.out.print("COOKIE");
System.out.println(cookie.getValue());
jwt = cookie.getValue();
}
}
if (null != jwt) {
try {
//generating the key
SecretKey key = Keys.hmacShaKeyFor(
SecurityConstants.JWT_KEY.getBytes(StandardCharsets.UTF_8));
//verification of legitimacy
Claims claims = Jwts.parser()
.verifyWith(key)
.build()
.parseSignedClaims(jwt)
.getPayload();
String username = String.valueOf(claims.get("username"));
String authorities = (String) claims.get("authorities");
// System.out.println("JWT name : "+ username);
// System.out.println("JWT auth "+ authorities);
//if successful the result will be stored in SecurityContextHolder
Authentication auth = new UsernamePasswordAuthenticationToken(username, null,
//this comes in a string of comas and values
AuthorityUtils.commaSeparatedStringToAuthorityList(authorities));
SecurityContextHolder.getContext().setAuthentication(auth);
} catch (Exception e) {
throw new BadCredentialsException("Invalid Token received!");
}
}
filterChain.doFilter(request, response);
}
//should be executed for all the api except the login api
@Override
protected boolean shouldNotFilter(HttpServletRequest request) {
return request.getServletPath().equals("/open/signup")
|| request.getServletPath().equals("/open/login");
// //bellow was done to archive this /exposed/**
// request.getServletPath().split("/")[1].equals("exposed");
}
}

View File

@ -0,0 +1,35 @@
package com.example.cezenPBX.service;
import com.example.cezenPBX.DAO.UserOpsDAO;
import com.example.cezenPBX.DTO.ReturnStatus;
import com.example.cezenPBX.entity.user.UserEntity;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class PbxUserService {
@Autowired
private UserOpsDAO userOpsDAO;
@Autowired
private PasswordEncoder passwordEncoder;
// must perform the sanity checks before being set to the database
// method will return a faulty return status if the damin exists
public ReturnStatus adminSetUserNamePasswordSet(String userName, String password, String confirmPassword, String email){
// password will be checked here
if(!password.equals(confirmPassword)){
return new ReturnStatus(false, "Passwords do not match", "Passwords do not match");
}
// password encryption
UserEntity userEntity = new UserEntity(userName, passwordEncoder.encode(password), email);
// commit the username and password to the database
return userOpsDAO.adminSetUsernameAndPassword(userEntity);
}
}