├── .gitignore ├── LICENSE.txt ├── README.md ├── api ├── Procfile ├── gsenhaapi │ ├── __init__.py │ ├── controller │ │ ├── __init__.py │ │ ├── authController.py │ │ ├── databaseController.py │ │ ├── ldapController.py │ │ └── userController.py │ ├── exceptions.py │ ├── model │ │ ├── __init__.py │ │ ├── auth.py │ │ ├── crypto.py │ │ ├── database.py │ │ ├── ldapsearch.py │ │ ├── log.py │ │ ├── schemas │ │ │ ├── addfolder.json │ │ │ ├── addpasswd.json │ │ │ ├── addpasswdexternal.json │ │ │ ├── addpasswdshared.json │ │ │ ├── addpasswdsharedexternal.json │ │ │ ├── adduser.json │ │ │ ├── delfolder.json │ │ │ ├── delpasswd.json │ │ │ ├── generic.json │ │ │ ├── search.json │ │ │ ├── unlock.json │ │ │ ├── unlocking.json │ │ │ ├── update.json │ │ │ └── updatepub.json │ │ ├── send_email.py │ │ └── user.py │ ├── schema_wrapper.py │ └── view │ │ ├── __init__.py │ │ └── routes.py ├── requirements.apt ├── requirements.txt ├── runserver.py └── script │ └── mysqlcreate.sql └── ui ├── Procfile ├── forms.py ├── main.py ├── requirements.txt ├── send_json.py ├── settings.py ├── static ├── css │ ├── 32px.png │ ├── 40px.png │ ├── bootstrap-theme.css │ ├── bootstrap-theme.css.map │ ├── bootstrap-theme.min.css │ ├── bootstrap-treeview.min.css │ ├── bootstrap.css │ ├── bootstrap.css.map │ ├── bootstrap.min.css │ ├── font-awesome.min.css │ ├── navbar.css │ ├── style.css │ ├── style.min.css │ └── throbber.gif ├── fonts │ ├── FontAwesome.otf │ ├── fontawesome-webfont.eot │ ├── fontawesome-webfont.svg │ ├── fontawesome-webfont.ttf │ └── fontawesome-webfont.woff ├── gsenha-chrome.crx └── js │ ├── adduser.js │ ├── bootstrap.min.js │ ├── cleanSessionStorage.js │ ├── clipBoard.js │ ├── deletePass.js │ ├── exportPasswd.js │ ├── forge.min.js │ ├── generatePasswd.js │ ├── generatePasswdModal.js │ ├── getPk.js │ ├── import.js │ ├── jquery.min.js │ ├── jstree.js │ ├── jstree.min.js │ ├── papaparse.min.js │ ├── purify.min.js │ ├── showPass.js │ ├── unlock.js │ └── updatePass.js └── templates ├── addfolder.html ├── addpassword.html ├── adduser.html ├── base.html ├── base2.html ├── base3.html ├── base4.html ├── deletepasswd.html ├── delfolder.html ├── docs.html ├── error.html ├── import.html ├── index.html ├── login.html ├── passwords.html ├── plugin_chrome.html ├── privkey.html ├── unlock.html ├── unlocking.html ├── updatepass.html └── updatepk.html /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.pem 3 | *.env 4 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright (c) <2016> 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | 6 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 7 | 8 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 9 | 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GSenha 2 | 3 | GSenha is a password manager, but not a usual one. Its architecture was designed to avoid information leakage in the case of a compromise (this name derives from Brazilian translation of password). 4 | 5 | It is possible to store a password and share it among a group of users in a secure way, and also store a personal password, just for yourself. 6 | 7 | Storing a personal password is just like using another well-known password manager like KeePass, PasswordSafe, Password Gorilla and others. The goal in GSenha is to be able to store a password and allow other users to have access to it securely, without backdoors and no shared secret keys. This is done with asymmetric cryptography (private and public keys). 8 | 9 | GSenha has as a REST API with JWT. There is a front-end (look folder `/ui`), but anyone can write a custom one or use it as a command-line tool. 10 | 11 | ## Authentication and user management 12 | 13 | There is one dependency, you must have an LDAP base. GSenha does not perform user management, it uses the information provided in the LDAP base. 14 | 15 | Authentication and authorization are all handled by the LDAP. A new user must add herself/himself into the system informing his/her LDAP's credentials and an RSA public key. Gsenha will perform a query on the LDAP server and, once authentication is granted, all user information will be retrieved, like given name, surname, email, groups and it will be stored in a database with the public key. After that, the user will perform login using his/her LDAP's credentials. In all requests of the API, it will be performed a query into LDAP to see if there is any inconsistency with the user and his/her groups. The GSenha's database group table will mirror LDAP's base. This is how authorization is handled. 16 | 17 | ## Presentations 18 | 19 | * [Slides for presentation at the Nic.br Security Working Group (GTS25) - Only in Portuguese](ftp://ftp.registro.br/pub/gts/gts25/07-GerenciamentoSenhas.pdf) 20 | * [Video for presentation at the Nic.br Security Working Group (GTS25) - Only in Portuguese](https://www.youtube.com/watch?v=WNtcEJK80TU) 21 | 22 | ## Private key fallback 23 | 24 | The system is not able to retrieve your private key in case of loss. It is the user's obligation to keep his/her private key safe. **DO NOT LOSE YOUR PRIVATE KEY!** 25 | 26 | ## Want to know more? 27 | 28 | Take a look at our excellent [documentation](https://github.com/globocom/gsenha/wiki)! 29 | -------------------------------------------------------------------------------- /api/Procfile: -------------------------------------------------------------------------------- 1 | gsenha: gunicorn -b 0.0.0.0:$PORT -t 90 gsenhaapi:app -------------------------------------------------------------------------------- /api/gsenhaapi/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from flask import Flask , jsonify, request 3 | from flask_jwt import JWT 4 | from datetime import datetime, timedelta 5 | import os 6 | 7 | app = Flask(__name__) 8 | app.config["SECRET_KEY"] = os.environ.get('SECRET_KEY') 9 | app.config["JWT_AUTH_URL_RULE"] = "/login" 10 | app.config['JWT_EXPIRATION_DELTA'] = timedelta(minutes=int(600)) 11 | 12 | jwt = JWT(app) 13 | 14 | import gsenhaapi.view.routes -------------------------------------------------------------------------------- /api/gsenhaapi/controller/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/globocom/gsenha/ba057d03fa68cdc608a0ea31000de817f5d88098/api/gsenhaapi/controller/__init__.py -------------------------------------------------------------------------------- /api/gsenhaapi/controller/authController.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from gsenhaapi.model.auth import Auth 3 | from gsenhaapi.exceptions import InvalidUsage 4 | 5 | class AuthController(object): 6 | 7 | def __init__(self,m_auth): 8 | self.m_auth = m_auth 9 | 10 | def auth(self,user,passwd): 11 | response = self.m_auth.authenticate(user,passwd) 12 | if response == -1: 13 | message = "Failed to connect to ldap" 14 | raise InvalidUsage(message,500) 15 | if response == 0: 16 | message = "Failed to authenticate user %s"%user 17 | raise InvalidUsage(message,401) 18 | return int(response) -------------------------------------------------------------------------------- /api/gsenhaapi/controller/databaseController.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from gsenhaapi.model.database import DataBase 3 | from gsenhaapi.exceptions import DatabaseError 4 | from gsenhaapi.exceptions import InvalidUsage 5 | from gsenhaapi.model.log import Log 6 | 7 | class DatabaseController: 8 | 9 | def __init__(self,database): 10 | self.db = database 11 | self.log = Log() 12 | 13 | def healthcheck(self): 14 | response = self.db.healthcheck() 15 | if not response: 16 | raise DatabaseError("Connection failed",500) 17 | 18 | def search_user(self,username): 19 | response , err = self.db.search_user_db(username) 20 | if not err: 21 | log_message = """action=|database search| method=|search user| desc=|%s| result=|error|"""%response 22 | self.log.log_error(log_message) 23 | raise DatabaseError("Failed to search user",500) 24 | if response == None: 25 | raise InvalidUsage("user does not exist, please add at /add/user",401) 26 | return list(response) 27 | 28 | def get_user_group(self,userdb): 29 | response , err = self.db.get_groups_from_user_db(userdb) 30 | if not err: 31 | log_message = """action=|database search| method=|get_user_group| desc=|%s| result=|error|"""%response 32 | self.log.log_error(log_message) 33 | raise DatabaseError("Failed to get groups from user",500) 34 | return list(response) 35 | 36 | def get_groups_name(self,idGrupo): 37 | response , err = self.db.get_groups_by_id_db(idGrupo) 38 | if not err: 39 | log_message = """action=|database search| method=|get_groups_name| desc=|%s| result=|error|"""%response 40 | self.log.log_error(log_message) 41 | raise DatabaseError("Failed to get groups name",500) 42 | return list(response) 43 | 44 | def exclude_user_group(self,idGrupo,username): 45 | response , err = self.db.exclude_user_group_db(idGrupo,username) 46 | if not response: 47 | log_message = """action=|database search| method=|exclude_user_group| desc=|%s| result=|error|"""%response 48 | self.log.log_error(log_message) 49 | raise DatabaseError(response,500) 50 | 51 | def search_group(self,group): 52 | response , err = self.db.search_group_db(group) 53 | if not err: 54 | log_message = """action=|database search| method=|search_group| desc=|%s| result=|error|"""%response 55 | self.log.log_error(log_message) 56 | raise DatabaseError(response,500) 57 | if response == None: 58 | return [] 59 | return list(response) 60 | 61 | def add_group(self,NomeGrupo): 62 | groupdb , err = self.db.search_group_db(NomeGrupo) 63 | if not err: 64 | log_message = """action=|database search| method=|add_group| desc=|%s| result=|error|"""%groupdb 65 | self.log.log_error(log_message) 66 | raise DatabaseError("Failed to search group",500) 67 | 68 | if groupdb == None: 69 | 70 | response , err = self.db.add_group_db(NomeGrupo) 71 | if not err: 72 | log_message = """action=|database search| method=|add_group| desc=|%s| result=|error|"""%response 73 | self.log.log_error(log_message) 74 | raise DatabaseError("Failed to add group",500) 75 | 76 | groupdb , err = self.db.search_group_db(NomeGrupo) 77 | if not err: 78 | log_message = """action=|database search| method=|add_group| desc=|%s| result=|error|"""%groupdb 79 | self.log.log_error(log_message) 80 | raise DatabaseError("Failed to search group",500) 81 | 82 | folderName = "/Shared/"+NomeGrupo 83 | 84 | response , err = self.db.add_groupfolder_db(folderName,"/Shared",groupdb) 85 | if not err: 86 | log_message = """action=|database search| method=|add_group| desc=|%s| result=|error|"""%response 87 | self.log.log_error(log_message) 88 | raise DatabaseError("Failed to add group folder",500) 89 | 90 | externalFolderName = "/Shared/"+NomeGrupo+"/External" 91 | external_path = "/Shared/"+NomeGrupo 92 | 93 | response , err = self.db.add_groupfolder_db(externalFolderName,external_path,groupdb) 94 | if not err: 95 | log_message = """action=|database search| method=|add_group_db| desc=|%s| result=|error|"""%response 96 | self.log.log_error(log_message) 97 | raise DatabaseError("Failed to add group folder",500) 98 | 99 | return list(groupdb) 100 | 101 | def add_user_group(self,userdb,groupdb): 102 | response , err = self.db.add_user_group_db(userdb,groupdb) 103 | if not err: 104 | log_message = """action=|database search| method=|add_user_group| desc=|%s| result=|error|"""%response 105 | self.log.log_error(log_message) 106 | raise DatabaseError("Failed to add user group",500) 107 | 108 | def get_user_folder(self,userdb): 109 | response , err = self.db.get_userfolder(userdb) 110 | if not err: 111 | log_message = """action=|database search| method=|get_user_folder| desc=|%s| result=|error|"""%response 112 | self.log.log_error(log_message) 113 | raise DatabaseError("Failed to get user folder",500) 114 | return list(response) 115 | 116 | def get_personal_passwords_by_folder(self,idFolder,username): 117 | response , err = self.db.get_personal_passwords_byfolder_db(idFolder,username) 118 | if not err: 119 | log_message = """action=|database search| method=|get_personal_passwords_by_folder| desc=|%s| result=|error|"""%response 120 | self.log.log_error(log_message) 121 | raise DatabaseError("Failed to get passwords by folder",500) 122 | return list(response) 123 | 124 | def get_group_folder(self,GroupName): 125 | response , err = self.db.get_groupfolder(GroupName) 126 | if not err: 127 | log_message = """action=|database search| method=|get_group_folder| desc=|%s| result=|error|"""%response 128 | self.log.log_error(log_message) 129 | raise DatabaseError("Failed to get group folder",500) 130 | return list(response) 131 | 132 | def get_group_passwords_by_folder(self,idFolder,username): 133 | response , err = self.db.get_group_passwords_byfolder_db(idFolder,username) 134 | if not err: 135 | log_message = """action=|database search| method=|get_group_passwords_by_folder| desc=|%s| result=|error|"""%response 136 | self.log.log_error(log_message) 137 | raise DatabaseError("Failed to get shared passwords",500) 138 | return list(response) 139 | 140 | def get_groups(self): 141 | response , err = self.db.get_groups_db() 142 | if not err: 143 | log_message = """action=|database search| method=|get_groups| desc=|%s| result=|error|"""%response 144 | self.log.log_error(log_message) 145 | raise DatabaseError("Failed to get groups from database",500) 146 | return list(response) 147 | 148 | def get_pubkey(self,userdb): 149 | response , err = self.db.get_publickey_db(userdb) 150 | if not err: 151 | log_message = """action=|database search| method=|get_pubkey| desc=|%s| result=|error|"""%response 152 | self.log.log_error(log_message) 153 | raise DatabaseError("Failed to get pubkey", 500) 154 | return list(response) 155 | 156 | def add_user(self,name,email,username,pubkey): 157 | response , err = self.db.add_user_db(name,email,username,pubkey) 158 | if not err: 159 | log_message = """action=|database search| method=|add_user| desc=|%s| result=|error|"""%response 160 | self.log.log_error(log_message) 161 | raise DatabaseError("Failed to add user", 500) 162 | 163 | def add_user_folder(self,name,path,userdb): 164 | response , err = self.db.add_userfolder_db(name,path,userdb) 165 | if not err: 166 | log_message = """action=|database search| method=|add_user_folder| desc=|%s| result=|error|"""%response 167 | self.log.log_error(log_message) 168 | raise DatabaseError("Failed to add user folder", 500) 169 | 170 | def get_folder(self,nameFolder): 171 | response , err = self.db.get_folder_db(nameFolder) 172 | if not err: 173 | log_message = """action=|database search| method=|get_folder| desc=|%s| result=|error|"""%response 174 | self.log.log_error(log_message) 175 | raise DatabaseError("Failed to get folder",500) 176 | if response == None: 177 | return [] 178 | return list(response) 179 | 180 | def add_personal_password(self,username,folderName,passwd,name,login,url,description): 181 | response , err = self.db.add_personalpassword_db("Personal",username,folderName,passwd,name,login,url,description) 182 | if not err: 183 | log_message = """action=|database search| method=|add_personal_password| desc=|%s| result=|error|"""%response 184 | self.log.log_error(log_message) 185 | raise DatabaseError("Failed to add personal password. You can not have two passwords with the same name in the same folder",400) 186 | 187 | def add_shared_password(self,group,userdb,folderName,sharedId,passwd,name,login,url,description): 188 | response , err = self.db.add_sharedpassword_db(group,userdb,folderName,sharedId,passwd,name,login,url,description) 189 | if not err: 190 | log_message = """action=|database search| method=|add_shared_password| desc=|%s| result=|error|"""%response 191 | self.log.log_error(log_message) 192 | raise DatabaseError("Failed to add shared password. You can not have two passwords with the same name in the same folder",500) 193 | 194 | def get_sharedId(self): 195 | response , err = self.db.get_idcompartilhados_db() 196 | if not err: 197 | log_message = """action=|database search| method=|get_sharedId| desc=|%s| result=|error|"""%response 198 | self.log.log_error(log_message) 199 | raise DatabaseError("Failed to get shared id",500) 200 | if response == None: 201 | return [] 202 | return list(response) 203 | 204 | def get_userId_by_group(self,group): 205 | response , err = self.db.get_usersidbygroup_db(group) 206 | if not err: 207 | log_message = """action=|database search| method=|get_userId_by_group| desc=|%s| result=|error|"""%response 208 | self.log.log_error(log_message) 209 | raise DatabaseError("Failed to get users id bu group") 210 | if response == None: 211 | return [] 212 | return list(response) 213 | 214 | def get_folder_by_name(self,folderName): 215 | response , err = self.db.get_folderbyname_db(folderName) 216 | if not err: 217 | log_message = """action=|database search| method=|get_folder_by_name| desc=|%s| result=|error|"""%response 218 | self.log.log_error(log_message) 219 | raise DatabaseError("Failed to get folder",500) 220 | if response == None: 221 | return [] 222 | return list(response) 223 | 224 | def get_group_id(self,group): 225 | response , err = self.db.get_group_id_db(group) 226 | if not err: 227 | log_message = """action=|database search| method=|get_group_id| desc=|%s| result=|error|"""%response 228 | self.log.log_error(log_message) 229 | raise DatabaseError("Failed to get group",500) 230 | if response == None: 231 | return [] 232 | return list(response) 233 | 234 | def add_group_folder(self,folderName,path,group): 235 | response , err = self.db.add_groupfolder_db(folderName,path,group) 236 | if not err: 237 | log_message = """action=|database search| method=|add_group_folder| desc=|%s| result=|error|"""%response 238 | self.log.log_error(log_message) 239 | raise DatabaseError("Failed to add group folder",500) 240 | 241 | def get_passwd_byid(self,_id): 242 | response , err = self.db.get_passwd_byid_db(_id) 243 | if not err: 244 | log_message = """action=|database search| method=|get_passwd_by_id| desc=|%s| result=|error|"""%response 245 | self.log.log_error(log_message) 246 | raise DatabaseError("Failed to get passwd by id",500) 247 | if response == None: 248 | return [] 249 | return list(response) 250 | 251 | def update_personal_password(self,_id,name,passwd,description,url,login): 252 | response , err = self.db.update_personal_password_db(_id,name,passwd,description,url,login) 253 | if not err: 254 | log_message = """action=|database search| method=|update_personal_password| desc=|%s| result=|error|"""%response 255 | self.log.log_error(log_message) 256 | raise DatabaseError("Failed to update personal password",500) 257 | 258 | def update_shared_password(self,_id,name,passwd,description,url,login): 259 | response , err = self.db.update_shared_password_db(_id,name,passwd,description,url,login) 260 | if not err: 261 | log_message = """action=|database search| method=|update_shared_password| desc=|%s| result=|error|"""%response 262 | self.log.log_error(log_message) 263 | raise DatabaseError("Failed to update shared password",500) 264 | 265 | def get_passwd_by_sharedId(self,sharedId): 266 | response , err = self.db.get_passwd_byidCompartilhado(sharedId) 267 | if not err: 268 | log_message = """action=|database search| method=|get_passwd_by_sharedId| desc=|%s| result=|error|"""%response 269 | self.log.log_error(log_message) 270 | raise DatabaseError("Failed to get password by sharedId",500) 271 | if response == None: 272 | return [] 273 | return list(response) 274 | 275 | def get_personal_passwords(self,username): 276 | response , err = self.db.get_personal_passwords_db(username) 277 | if not err: 278 | log_message = """action=|database search| method=|get_personal_passwords| desc=|%s| result=|error|"""%response 279 | self.log.log_error(log_message) 280 | raise DatabaseError("Failed to get password by sharedId",500) 281 | if response == None: 282 | return [] 283 | return list(response) 284 | 285 | def get_group_passwords(self,username): 286 | response , err = self.db.get_group_passwords_db(username) 287 | if not err: 288 | log_message = """action=|database search| method=|get_group_passwords| desc=|%s| result=|error|"""%response 289 | self.log.log_error(log_message) 290 | raise DatabaseError("Failed to get password by sharedId",500) 291 | if response == None: 292 | return [] 293 | return list(response) 294 | 295 | def update_password_newpubkey(self,idPassword,passwd,nome,login,url,descricao): 296 | response , err = self.db.update_password_newpubkey(idPassword,passwd,nome,login,url,descricao) 297 | if not err: 298 | log_message = """action=|database search| method=|update_password_newpubkey| desc=|%s| result=|error|"""%response 299 | self.log.log_error(log_message) 300 | raise DatabaseError("Failed to update password with new pubkey",500) 301 | 302 | def update_pubkey(self,userdb,pubkey): 303 | response , err = self.db.update_publickey(userdb,pubkey) 304 | if not err: 305 | log_message = """action=|database search| method=|update_pubkey| desc=|%s| result=|error|"""%response 306 | self.log.log_error(log_message) 307 | raise DatabaseError("Failed to update pubkey",500) 308 | 309 | def delete_personal_password(self,_id): 310 | response , err = self.db.exclude_password_personal_db(_id) 311 | if not err: 312 | log_message = """action=|database search| method=|delete_personal_password| desc=|%s| result=|error|"""%response 313 | self.log.log_error(log_message) 314 | raise DatabaseError("Failed delete personal password",500) 315 | 316 | def delete_shared_password(self,_id): 317 | response , err = self.db.exclude_password_shared_db(_id) 318 | if not err: 319 | log_message = """action=|database search| method=|delete_shared_password| desc=|%s| result=|error|"""%response 320 | self.log.log_error(log_message) 321 | raise DatabaseError("Failed to delete shared password",500) 322 | 323 | def get_personal_tree(self,userdb): 324 | response , err = self.db.get_personalfolder_tree(userdb) 325 | if not err: 326 | log_message = """action=|database search| method=|get_tree| desc=|%s| result=|error|"""%response 327 | self.log.log_error(log_message) 328 | raise DatabaseError("Failed to get personal tree",500) 329 | if response == None: 330 | return [] 331 | return list(response) 332 | 333 | def get_group_tree(self,group): 334 | response , err = self.db.get_groupfolder_tree(group) 335 | if not err: 336 | log_message = """action=|database search| method=|get_tree| desc=|%s| result=|error|"""%response 337 | self.log.log_error(log_message) 338 | raise DatabaseError("Failed to get group tree",500) 339 | if response == None: 340 | return [] 341 | return list(response) 342 | 343 | def get_user_name(self,userdb): 344 | response , err = self.db.get_user_name_db(userdb) 345 | if not err: 346 | log_message = """action=|database search| method=|get_user_name| desc=|%s| result=|error|"""%response 347 | self.log.log_error(log_message) 348 | raise DatabaseError("Failed to get user name",500) 349 | if response == None: 350 | return [] 351 | return list(response) 352 | 353 | def add_password_root(self,group,userdb,folder,passwd,name,login,url,description): 354 | response , err = self.db.add_sharedpassword_special_db(group,userdb,folder,passwd,name,login,url,description) 355 | if not err: 356 | log_message = """action=|database search| method=|add_password_root| desc=|%s| result=|error|"""%response 357 | self.log.log_error(log_message) 358 | raise DatabaseError("Failed to add password",500) 359 | 360 | def delete_group_folder(self,folder,group): 361 | response , err = self.db.delete_groupfolder_db(folder,group) 362 | if not err: 363 | log_message = """action=|database search| method=|delete_group_folder| desc=|%s| result=|error|"""%response 364 | self.log.log_error(log_message) 365 | raise DatabaseError("Failed to delete folder. It must be empty",500) 366 | 367 | def delete_user_folder(self,folder,userdb): 368 | response , err = self.db.delete_userfolder_db(folder,userdb) 369 | if not err: 370 | log_message = """action=|database search| method=|delete_user_folder| desc=|%s| result=|error|"""%response 371 | self.log.log_error(log_message) 372 | raise DatabaseError("Failed to delete folder. It must be empty",500) 373 | 374 | def get_pass_by_group_unlock(self,group,userdb,usertounlockdb): 375 | response , err = self.db.get_pass_by_group_unlock_db(group,userdb,usertounlockdb) 376 | if not err: 377 | log_message = """action=|database search| method=|get_pass_by_group_unlock| desc=|%s| result=|error|"""%response 378 | self.log.log_error(log_message) 379 | raise DatabaseError("Failed to get passwords to unlock",500) 380 | if response == None: 381 | return [] 382 | return list(response) 383 | 384 | def set_hash(self,userdb,t_hash): 385 | response , err = self.db.set_hash(userdb,t_hash) 386 | if not err: 387 | log_message = """action=|database search| method=|set_hash| desc=|%s| result=|error|"""%response 388 | self.log.log_error(log_message) 389 | raise DatabaseError("Failed to set hash",500) 390 | 391 | def set_token(self,userdb,token): 392 | response , err = self.db.set_token(userdb,token) 393 | if not err: 394 | log_message = """action=|database search| method=|set_token| desc=|%s| result=|error|"""%response 395 | self.log.log_error(log_message) 396 | raise DatabaseError("Failed to set token",500) 397 | 398 | def get_token(self,userdb): 399 | response , err = self.db.get_token(userdb) 400 | if not err: 401 | log_message = """action=|database search| method=|get_token| desc=|%s| result=|error|"""%response 402 | self.log.log_error(log_message) 403 | raise DatabaseError("Failed to get token",500) 404 | if response == None: 405 | return [] 406 | return list(response) 407 | 408 | def get_hash(self,userdb): 409 | response , err = self.db.get_hash(userdb) 410 | if not err: 411 | log_message = """action=|database search| method=|get_hash| desc=|%s| result=|error|"""%response 412 | self.log.log_error(log_message) 413 | raise DatabaseError("Failed to get hash",500) 414 | if response == None: 415 | return [] 416 | return list(response) 417 | 418 | def add_unlocked_shared_password(self,group,usertounlock,folder,sharedId,passwd,name,login,url,description): 419 | response , err = self.db.add_unlocked_sharedpassword_db(group,usertounlock,folder,sharedId,passwd,name,login,url,description) 420 | if not err: 421 | log_message = """action=|database search| method=|add_unlocked_shared_password| desc=|%s| result=|error|"""%response 422 | self.log.log_error(log_message) 423 | raise DatabaseError("Failed to add unlocked password",500) 424 | 425 | def get_user_info(self,userdb): 426 | response , err = self.db.get_user_info_db(userdb) 427 | if not err: 428 | log_message = """action=|database search| method=|get_user_info| desc=|%s| result=|error|"""%response 429 | self.log.log_error(log_message) 430 | raise DatabaseError("Failed to get user info password",500) 431 | return list(response) 432 | 433 | def search_passwd(self,idUsuario,folder,name): 434 | response , err = self.db.search_password(idUsuario,folder,name) 435 | if not err: 436 | log_message = """action=|database search| method=|search_passwd| desc=|%s| result=|error|"""%response 437 | self.log.log_error(log_message) 438 | raise DatabaseError("Failed to get user info password",500) 439 | return list(response) 440 | 441 | 442 | -------------------------------------------------------------------------------- /api/gsenhaapi/controller/ldapController.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from gsenhaapi.model.ldapsearch import LdapSearch 4 | from gsenhaapi.exceptions import InvalidUsage 5 | 6 | class LdapController(object): 7 | 8 | def __init__(self, ldap): 9 | self.ldap = ldap 10 | 11 | def healthcheck(self): 12 | response = self.ldap.healthcheck() 13 | if not response: 14 | raise InvalidUsage("Failed to connect to ldap",500) 15 | 16 | def get_groups(self,user): 17 | response , err = self.ldap.get_groups(user) 18 | if not err: 19 | raise InvalidUsage("Failed to get groups from ldap",500) 20 | return response 21 | 22 | def search_info(self,user): 23 | response , err = self.ldap.info_search(user) 24 | if not err: 25 | raise InvalidUsage("Failed to get email from user",500) 26 | return response 27 | -------------------------------------------------------------------------------- /api/gsenhaapi/controller/userController.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from gsenhaapi.model.user import User 3 | from gsenhaapi.controller.databaseController import DatabaseController 4 | from gsenhaapi.exceptions import InvalidUsage 5 | from gsenhaapi.exceptions import DatabaseError 6 | 7 | class UserController(object): 8 | 9 | def __init__(self,database): 10 | self.db = database 11 | self.user = None 12 | 13 | def set_user(self,user): 14 | self.user = user 15 | 16 | def check_group(self,ldapgroups,dbgroups): 17 | 18 | groups_db_name= [] 19 | for idGrupo in dbgroups: 20 | try: 21 | name = self.db.get_groups_name(idGrupo[0]) 22 | groups_db_name.append(name[0][0]) 23 | except DatabaseError,e: 24 | raise InvalidUsage(e.message,500) 25 | 26 | groups_ldap = set(ldapgroups) 27 | groups_db = set(groups_db_name) 28 | 29 | if groups_db != groups_ldap: 30 | if len(groups_ldap) > len(groups_db): 31 | groups_to_add = list(groups_ldap - groups_db) 32 | if groups_to_add == groups_db_name: 33 | for group in groups_db_name: 34 | try: 35 | self.db.exclude_user_group(group,self.user.username) 36 | except DatabaseError,e: 37 | raise InvalidUsage(e.message,500) 38 | 39 | for group in ldapgroups: 40 | try: 41 | groupdb = self.db.add_group(group) 42 | except DatabaseError,e: 43 | raise InvalidUsage(e.message,500) 44 | try: 45 | self.db.add_user_group(self.user.userdb,groupdb) 46 | except DatabaseError,e: 47 | raise InvalidUsage(e.message,500) 48 | else: 49 | for group in groups_to_add: 50 | try: 51 | groupdb = self.db.add_group(group) 52 | except DatabaseError,e: 53 | raise InvalidUsage(e.message,500) 54 | try: 55 | self.db.add_user_group(self.user.userdb,groupdb) 56 | except DatabaseError,e: 57 | raise InvalidUsage(e.message,500) 58 | else: 59 | groups_to_remove = list(groups_db - groups_ldap) 60 | for group in groups_to_remove: 61 | try: 62 | self.db.exclude_user_group(group,self.user.username) 63 | except DatabaseError,e: 64 | raise InvalidUsage(e.message,500) 65 | 66 | groups_to_add = list(groups_ldap - groups_db) 67 | 68 | for group in groups_to_add: 69 | try: 70 | groupdb = self.db.add_group(group) 71 | except DatabaseError,e: 72 | raise InvalidUsage(e.message,500) 73 | try: 74 | self.db.add_user_group(self.user.userdb,groupdb) 75 | except DatabaseError,e: 76 | raise InvalidUsage(e.message,500) 77 | 78 | -------------------------------------------------------------------------------- /api/gsenhaapi/exceptions.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from flask import jsonify, current_app 3 | import traceback 4 | 5 | 6 | class InvalidUsage(Exception): 7 | status_code = 400 8 | 9 | def __init__(self, message, status_code=None, payload=None): 10 | Exception.__init__(self) 11 | self.message = message 12 | if status_code is not None: 13 | self.status_code = status_code 14 | self.payload = payload 15 | 16 | def to_dict(self): 17 | rv = dict(self.payload or ()) 18 | rv['message'] = self.message 19 | return rv 20 | 21 | class DatabaseError(Exception): 22 | status_code = 500 23 | 24 | def __init__(self, message, status_code=None, payload=None): 25 | Exception.__init__(self) 26 | self.message = message 27 | if status_code is not None: 28 | self.status_code = status_code 29 | self.payload = payload 30 | 31 | def to_dict(self): 32 | rv = dict(self.payload or ()) 33 | rv['message'] = self.message 34 | return rv 35 | 36 | class CryptoError(Exception): 37 | status_code = 500 38 | 39 | def __init__(self, message, status_code=None, payload=None): 40 | Exception.__init__(self) 41 | self.message = message 42 | if status_code is not None: 43 | self.status_code = status_code 44 | self.payload = payload 45 | 46 | def to_dict(self): 47 | rv = dict(self.payload or ()) 48 | rv['message'] = self.message 49 | return rv -------------------------------------------------------------------------------- /api/gsenhaapi/model/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/globocom/gsenha/ba057d03fa68cdc608a0ea31000de817f5d88098/api/gsenhaapi/model/__init__.py -------------------------------------------------------------------------------- /api/gsenhaapi/model/auth.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import requests, json 3 | import ldap 4 | import os 5 | 6 | class Auth: 7 | 8 | def __init__(self,server): 9 | self.s = server 10 | self.who = os.environ.get('LDAP_USER') 11 | self.cred = os.environ.get('LDAP_PASSWORD') 12 | self.baseDN = os.environ.get('BASE_USER_DN') 13 | 14 | def authenticate(self,user,password): 15 | try: 16 | self.l = ldap.initialize(self.s) 17 | 18 | self.l.protocol_version=ldap.VERSION3 19 | 20 | self.l.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_NEVER) 21 | 22 | if os.environ.get('CERT') != None and os.environ.get('CLIENT_CERT') != None and os.environ.get('CLIENT_KEY') != None: 23 | self.l.set_option(ldap.OPT_X_TLS_CACERTFILE, os.environ.get('CERT')) 24 | self.l.set_option(ldap.OPT_X_TLS_CERTFILE, os.environ.get('CLIENT_CERT')) 25 | self.l.set_option(ldap.OPT_X_TLS_KEYFILE, os.environ.get('CLIENT_KEY')) 26 | 27 | self.l.set_option(ldap.OPT_X_TLS_NEWCTX,0) 28 | except ldap.LDAPError: 29 | return -1 30 | try: 31 | cn = "cn=%s,"%user 32 | self.l.simple_bind_s(cn+str(self.baseDN),password) 33 | except ldap.INVALID_CREDENTIALS: 34 | return 0 35 | 36 | scope = ldap.SCOPE_SUBTREE 37 | search = '(cn=%s)' %user 38 | searchAttribute = ["uidNumber"] 39 | 40 | # We try recover user data using previous authenticated connection. If user don't have permissions, we fallback to gsenha's user 41 | try: 42 | result = self.l.search_s(self.baseDN,scope,search,searchAttribute) 43 | except ldap.NO_SUCH_OBJECT: 44 | self.l.simple_bind_s(self.who, self.cred) 45 | result = self.l.search_s(self.baseDN,scope,search,searchAttribute) 46 | 47 | self.l.unbind() 48 | return result[0][1]['uidNumber'][0] -------------------------------------------------------------------------------- /api/gsenhaapi/model/crypto.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import cryptography 3 | from cryptography.hazmat.backends import default_backend 4 | from cryptography.hazmat.primitives.asymmetric import rsa 5 | from cryptography.hazmat.primitives.serialization import load_pem_private_key 6 | from cryptography.hazmat.primitives.serialization import load_pem_public_key 7 | from cryptography.hazmat.primitives.asymmetric import padding 8 | from cryptography.hazmat.primitives import serialization 9 | 10 | from gsenhaapi.controller.databaseController import DatabaseController 11 | from gsenhaapi.exceptions import CryptoError 12 | 13 | class Crypto: 14 | 15 | def __init__(self,database): 16 | self.db = database 17 | 18 | def get_pubkey(self,userdb): 19 | tmp = self.db.get_pubkey(userdb) 20 | 21 | pk_s = str(tmp[0]) 22 | tmp = pk_s.strip("''(),") 23 | tmp2 = tmp.split(",") 24 | e = int(tmp2[1]) 25 | n = int(tmp2[0]) 26 | numbers = rsa.RSAPublicNumbers(e, n) 27 | pubkey = numbers.public_key(default_backend()) 28 | 29 | return pubkey 30 | 31 | def load_pubkey(self,keydata): 32 | try: 33 | pubkey = load_pem_public_key(keydata, backend=default_backend()) 34 | except ValueError: 35 | raise CryptoError("failed to read public key",500) 36 | except Exception: 37 | raise CryptoError("failed to get public key",500) 38 | 39 | if pubkey.key_size != 4096: 40 | raise CryptoError("public key must be 4096 bits",400) 41 | 42 | return pubkey 43 | 44 | def load_privkey(self,privk_s): 45 | try: 46 | privkey = load_pem_private_key(privk_s, password=None, backend=default_backend()) 47 | except ValueError: 48 | raise CryptoError("Failed to load private key. Only PEM format is supported.") 49 | except cryptography.exceptions.UnsupportedAlgorithm: 50 | raise CryptoError("Unsupported Algorithm",500) 51 | return privkey 52 | 53 | def generate_privkey(self): 54 | private_key = rsa.generate_private_key(public_exponent=65537,key_size=4096,backend=default_backend()) 55 | return private_key 56 | 57 | def generate_pem_public(self,public_key): 58 | return public_key.public_bytes(encoding=serialization.Encoding.PEM,format=serialization.PublicFormat.SubjectPublicKeyInfo) 59 | 60 | def generate_pem_private(self,private_key): 61 | return private_key.private_bytes(encoding=serialization.Encoding.PEM,format=serialization.PrivateFormat.TraditionalOpenSSL,encryption_algorithm=serialization.NoEncryption()) 62 | -------------------------------------------------------------------------------- /api/gsenhaapi/model/ldapsearch.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import ldap, ldif, sys 3 | import os 4 | 5 | class LdapSearch: 6 | 7 | def __init__(self,server): 8 | self.s = server 9 | self.who = os.environ.get('LDAP_USER') 10 | self.cred = os.environ.get('LDAP_PASSWORD') 11 | self.baseUserDN = os.environ.get('BASE_USER_DN') 12 | self.baseGroupDN = os.environ.get('BASE_GROUP_DN') 13 | 14 | def healthcheck(self): 15 | try: 16 | self.l = ldap.initialize(self.s) 17 | 18 | self.l.protocol_version=ldap.VERSION3 19 | 20 | self.l.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_NEVER) 21 | 22 | if os.environ.get('CERT') != None and os.environ.get('CLIENT_CERT') != None and os.environ.get('CLIENT_KEY') != None: 23 | self.l.set_option(ldap.OPT_X_TLS_CACERTFILE, os.environ.get('CERT')) 24 | self.l.set_option(ldap.OPT_X_TLS_CERTFILE, os.environ.get('CLIENT_CERT')) 25 | self.l.set_option(ldap.OPT_X_TLS_KEYFILE, os.environ.get('CLIENT_KEY')) 26 | 27 | self.l.set_option(ldap.OPT_X_TLS_NEWCTX,0) 28 | except ldap.LDAPError: 29 | return 0 30 | try: 31 | self.l.simple_bind_s(self.who,self.cred) 32 | except ldap.LDAPError: 33 | return 0 34 | 35 | self.l.unbind() 36 | return 1 37 | 38 | def get_groups(self,user): 39 | 40 | try: 41 | 42 | self.l = ldap.initialize(self.s) 43 | 44 | self.l.protocol_version=ldap.VERSION3 45 | 46 | self.l.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_NEVER) 47 | 48 | if os.environ.get('CERT') != None and os.environ.get('CLIENT_CERT') != None and os.environ.get('CLIENT_KEY') != None: 49 | self.l.set_option(ldap.OPT_X_TLS_CACERTFILE, os.environ.get('CERT')) 50 | self.l.set_option(ldap.OPT_X_TLS_CERTFILE, os.environ.get('CLIENT_CERT')) 51 | self.l.set_option(ldap.OPT_X_TLS_KEYFILE, os.environ.get('CLIENT_KEY')) 52 | 53 | self.l.set_option(ldap.OPT_X_TLS_NEWCTX,0) 54 | except ldap.LDAPError,e: 55 | return "Erro ao iniciar conexao com o LDAP",0 56 | 57 | try: 58 | self.l.simple_bind_s(self.who,self.cred) 59 | except ldap.LDAPError,e: 60 | return "Erro ao se conectar com o LDAP" , 0 61 | 62 | base_dn = str(self.baseGroupDN) 63 | scope = ldap.SCOPE_SUBTREE 64 | search = '(memberUid=%s)' %user 65 | 66 | result = self.l.search_s(base_dn,scope,search) 67 | 68 | groups = [] 69 | 70 | for i in range(len(result)): 71 | tmp = result[i][1]['cn'][0] 72 | if tmp != user: 73 | groups.append(tmp) 74 | 75 | self.l.unbind() 76 | 77 | return groups , 1 78 | 79 | def info_search(self,user): 80 | try: 81 | 82 | self.l = ldap.initialize(self.s) 83 | 84 | self.l.protocol_version=ldap.VERSION3 85 | 86 | self.l.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_NEVER) 87 | 88 | if os.environ.get('CERT') != None and os.environ.get('CLIENT_CERT') != None and os.environ.get('CLIENT_KEY') != None: 89 | self.l.set_option(ldap.OPT_X_TLS_CACERTFILE, os.environ.get('CERT')) 90 | self.l.set_option(ldap.OPT_X_TLS_CERTFILE, os.environ.get('CLIENT_CERT')) 91 | self.l.set_option(ldap.OPT_X_TLS_KEYFILE, os.environ.get('CLIENT_KEY')) 92 | 93 | self.l.set_option(ldap.OPT_X_TLS_NEWCTX,0) 94 | except ldap.LDAPError,e: 95 | return "Erro ao iniciar conexao com o LDAP",0 96 | 97 | try: 98 | self.l.simple_bind_s(self.who,self.cred) 99 | except ldap.LDAPError,e: 100 | return "Erro ao se conectar com o LDAP" , 0 101 | 102 | base_dn = str(self.baseUserDN) 103 | scope = ldap.SCOPE_SUBTREE 104 | search = '(cn=%s)' %user 105 | searchAttribute = ["mail","sn","givenname"] 106 | 107 | result = self.l.search_s(base_dn,scope,search,searchAttribute) 108 | 109 | self.l.unbind() 110 | 111 | return {"mail":result[0][1]['mail'][0],"name":result[0][1]['givenName'][0] + " " + result[0][1]['sn'][0]} , 1 -------------------------------------------------------------------------------- /api/gsenhaapi/model/log.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import logging, sys 3 | from logging.handlers import SysLogHandler 4 | 5 | 6 | class Log(): 7 | 8 | def __init__(self): 9 | self.stdout_logger = logging.getLogger('gsenha-api') 10 | if not self.stdout_logger.handlers: 11 | self.out_hdlr = logging.StreamHandler(sys.stdout) 12 | self.out_hdlr.setFormatter(logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')) 13 | self.out_hdlr.setLevel(logging.INFO) 14 | self.stdout_logger.addHandler(self.out_hdlr) 15 | self.stdout_logger.setLevel(logging.INFO) 16 | 17 | def log_error(self,message): 18 | self.stdout_logger.error(message) 19 | 20 | def log_info(self,message): 21 | self.stdout_logger.info(message) -------------------------------------------------------------------------------- /api/gsenhaapi/model/schemas/addfolder.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "type": "object", 4 | "properties": { 5 | "name": { 6 | "type": "string" 7 | }, 8 | "path": { 9 | "type": "string" 10 | } 11 | }, 12 | "required": ["name","path"] 13 | } -------------------------------------------------------------------------------- /api/gsenhaapi/model/schemas/addpasswd.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "type": "object", 4 | "properties": { 5 | "passwd": { 6 | "type": "string" 7 | }, 8 | "folder": { 9 | "type": "string" 10 | }, 11 | "name": { 12 | "type": "string" 13 | }, 14 | "login": { 15 | "type": "string" 16 | }, 17 | "url": { 18 | "type": "string" 19 | }, 20 | "description": { 21 | "type": "string" 22 | } 23 | }, 24 | "required": ["passwd","folder","name"] 25 | } -------------------------------------------------------------------------------- /api/gsenhaapi/model/schemas/addpasswdexternal.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "type": "object", 4 | "properties": { 5 | "passwd": { 6 | "type": "string" 7 | }, 8 | "username": { 9 | "type": "string" 10 | }, 11 | "name": { 12 | "type": "string" 13 | }, 14 | "login": { 15 | "type": "string" 16 | }, 17 | "url": { 18 | "type": "string" 19 | }, 20 | "description": { 21 | "type": "string" 22 | } 23 | }, 24 | "required": ["passwd","username","name"] 25 | } -------------------------------------------------------------------------------- /api/gsenhaapi/model/schemas/addpasswdshared.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "type": "object", 4 | "properties": { 5 | "passwd": { 6 | "type": "string" 7 | }, 8 | "folder": { 9 | "type": "string" 10 | }, 11 | "name": { 12 | "type": "string" 13 | }, 14 | "group": { 15 | "type": "string" 16 | }, 17 | "login": { 18 | "type": "string" 19 | }, 20 | "url": { 21 | "type": "string" 22 | }, 23 | "description": { 24 | "type": "string" 25 | } 26 | }, 27 | "required": ["passwd","folder","name","group"] 28 | } -------------------------------------------------------------------------------- /api/gsenhaapi/model/schemas/addpasswdsharedexternal.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "type": "object", 4 | "properties": { 5 | "passwd": { 6 | "type": "string" 7 | }, 8 | "group": { 9 | "type": "string" 10 | }, 11 | "name": { 12 | "type": "string" 13 | }, 14 | "login": { 15 | "type": "string" 16 | }, 17 | "url": { 18 | "type": "string" 19 | }, 20 | "description": { 21 | "type": "string" 22 | } 23 | }, 24 | "required": ["passwd","group","name"] 25 | } -------------------------------------------------------------------------------- /api/gsenhaapi/model/schemas/adduser.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "type": "object", 4 | "properties": { 5 | "user": { 6 | "type": "string" 7 | }, 8 | "password": { 9 | "type": "string" 10 | }, 11 | "pubkey": { 12 | "type": "string" 13 | } 14 | }, 15 | "required": ["user","password","pubkey"] 16 | } -------------------------------------------------------------------------------- /api/gsenhaapi/model/schemas/delfolder.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "type": "object", 4 | "properties": { 5 | "folder": { 6 | "type": "string" 7 | } 8 | }, 9 | "required": ["folder"] 10 | } -------------------------------------------------------------------------------- /api/gsenhaapi/model/schemas/delpasswd.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "type": "object", 4 | "properties": { 5 | "id": { 6 | "type": "string" 7 | } 8 | }, 9 | "required": ["id"] 10 | } -------------------------------------------------------------------------------- /api/gsenhaapi/model/schemas/generic.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "type": "object", 4 | "properties": { 5 | "user": { 6 | "type": "string" 7 | }, 8 | "password": { 9 | "type": "string" 10 | } 11 | }, 12 | "required": ["user","password"] 13 | } -------------------------------------------------------------------------------- /api/gsenhaapi/model/schemas/search.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "type": "object", 4 | "properties": { 5 | "folder": { 6 | "type": "string" 7 | }, 8 | "name":{ 9 | "type":"string" 10 | } 11 | }, 12 | "required": ["folder","name"] 13 | } -------------------------------------------------------------------------------- /api/gsenhaapi/model/schemas/unlock.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "type": "object", 4 | "properties": { 5 | "group": { 6 | "type": "string" 7 | }, 8 | "usertounlock": { 9 | "type": "string" 10 | } 11 | }, 12 | "required": ["group","usertounlock"] 13 | } -------------------------------------------------------------------------------- /api/gsenhaapi/model/schemas/unlocking.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "type": "object", 4 | "properties": { 5 | "token": { 6 | "type": "string" 7 | }, 8 | "group": { 9 | "type": "string" 10 | }, 11 | "usertounlock": { 12 | "type": "string" 13 | }, 14 | "passwords": { 15 | "type": "array" 16 | } 17 | }, 18 | "required": ["token","group","usertounlock","passwords"] 19 | } -------------------------------------------------------------------------------- /api/gsenhaapi/model/schemas/update.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "type": "object", 4 | "properties": { 5 | "name": { 6 | "type": "string" 7 | }, 8 | "id": { 9 | "type": "string" 10 | }, 11 | "description": { 12 | "type": "string" 13 | }, 14 | "login": { 15 | "type": "string" 16 | }, 17 | "url": { 18 | "type": "string" 19 | }, 20 | "passwd": { 21 | "type": "string" 22 | } 23 | }, 24 | "required": ["id"] 25 | } -------------------------------------------------------------------------------- /api/gsenhaapi/model/schemas/updatepub.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "type": "object", 4 | "properties": { 5 | "pubkey": { 6 | "type":"string" 7 | }, 8 | "privkey": { 9 | "type":"string" 10 | } 11 | }, 12 | "required": ["pubkey","privkey"] 13 | } -------------------------------------------------------------------------------- /api/gsenhaapi/model/send_email.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import smtplib 3 | from email import utils 4 | from email.mime.text import MIMEText 5 | import sys 6 | from gsenhaapi.exceptions import InvalidUsage 7 | from gsenhaapi.model.log import Log 8 | log = Log() 9 | 10 | class Email: 11 | 12 | def __init__ (self, host, sender,name_sender): 13 | self.host = host 14 | self.sender = sender 15 | self.name_sender = name_sender 16 | 17 | 18 | def create_welcome_msg(self,name,group): 19 | 20 | if len(group) == 0: 21 | msg = """ 22 | %s, 23 | Welcome to GSenha. 24 | 25 | Before starting, please, read the docs. Your private key will never be stored or sent to server, keep it safe!""" %(name) 26 | else: 27 | users = "" 28 | for i in range(0,len(group)): 29 | if group[i] != name: 30 | if i == 0: 31 | users = users+group[i] 32 | else: 33 | users = users+", "+group[i] 34 | msg = """ 35 | %s, 36 | Welcome to GSenha. 37 | 38 | Before starting, please, read the docs. 39 | 40 | You are not the first member of your group, in this case it is necessary that an older member unlock you to see all passwords. 41 | 42 | Here there are some users that can do this: %s. 43 | 44 | Your private key will never be stored or sent to server, keep it safe!""" %(name,users) 45 | return msg 46 | 47 | 48 | # def create_passwd_added(self,name,name_from): 49 | # msg = """ 50 | # Olá %s, 51 | 52 | # o usuário %s acabou de adicionar uma senha para você. Confira na sua pasta "Externas". 53 | # """ %(name,name_from) 54 | # return msg 55 | 56 | 57 | def send_welcome_email(self,to,name,group): 58 | group = list(group) 59 | text = self.create_welcome_msg(name,group) 60 | msg = MIMEText(text.decode('utf-8'),'plain','UTF-8') 61 | 62 | recipient = to 63 | 64 | msg['To'] = utils.formataddr((name, recipient)) 65 | msg['From'] = utils.formataddr((self.name_sender, self.sender)) 66 | msg['Subject'] = 'Welcome' 67 | 68 | server = smtplib.SMTP(self.host,25) 69 | 70 | try: 71 | server.sendmail(self.sender, [recipient], msg.as_string()) 72 | except smtplib.SMTPException: 73 | log_message = """action=|send_welcome_email| user=|%s| desc=|Failed to send welcome mail| result=|error|""" % (to) 74 | log.log_info(log_message) 75 | finally: 76 | server.quit() 77 | 78 | # def send_passwd_added_email(self,to,name,name_from): 79 | 80 | # text = self.create_passwd_added(name,name_from) 81 | 82 | # msg = MIMEText(text.decode('utf-8'),'plain','UTF-8') 83 | 84 | # recipient = to 85 | 86 | # msg['To'] = utils.formataddr((name, recipient)) 87 | # msg['From'] = utils.formataddr((self.name_sender, self.sender)) 88 | # msg['Subject'] = 'Senha adicionada' 89 | 90 | # server = smtplib.SMTP(self.host,25) 91 | 92 | # try: 93 | # server.sendmail(self.sender, [recipient], msg.as_string()) 94 | # except smtplib.SMTPException: 95 | # raise InvalidUsage("Failed to send added password email",500) 96 | # finally: 97 | # server.quit() 98 | -------------------------------------------------------------------------------- /api/gsenhaapi/model/user.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | class User(object): 4 | 5 | def __init__(self,username,userid): 6 | self.username = username 7 | self.userdb = None 8 | self.uid = userid 9 | 10 | def set_userdb(self,userdb): 11 | self.userdb = userdb -------------------------------------------------------------------------------- /api/gsenhaapi/schema_wrapper.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from functools import wraps 3 | from flask import current_app, jsonify, request 4 | from jsonschema import Draft4Validator, validate, ValidationError 5 | from gsenhaapi.exceptions import * 6 | from werkzeug.exceptions import BadRequest 7 | import os 8 | import json 9 | import traceback 10 | 11 | def validate_json(f): 12 | @wraps(f) 13 | def wrapper(*args, **kw): 14 | try: 15 | request.json 16 | except BadRequest, e: 17 | msg = "payload must be a valid json" 18 | return jsonify({"status":"error","message":msg}), 400 19 | return f(*args, **kw) 20 | return wrapper 21 | 22 | def validate_schema(schema_name="generic"): 23 | def decorator(f): 24 | @wraps(f) 25 | def wrapper(*args, **kw): 26 | if request.method == 'POST': 27 | with open(os.path.join('gsenhaapi/model/schemas/', schema_name+'.json')) as schema_file: 28 | schema_js = json.load(schema_file) 29 | try: 30 | validate(request.get_json(force=True), schema_js) 31 | except ValidationError, e: 32 | return jsonify({"status":"error","message":e.message}) , 400 33 | return f(*args, **kw) 34 | return wrapper 35 | return decorator -------------------------------------------------------------------------------- /api/gsenhaapi/view/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/globocom/gsenha/ba057d03fa68cdc608a0ea31000de817f5d88098/api/gsenhaapi/view/__init__.py -------------------------------------------------------------------------------- /api/requirements.apt: -------------------------------------------------------------------------------- 1 | libmysqlclient-dev 2 | python-dev 3 | libldap2-dev 4 | libsasl2-dev 5 | libffi-dev 6 | libssl-dev -------------------------------------------------------------------------------- /api/requirements.txt: -------------------------------------------------------------------------------- 1 | Flask==1.0.2 2 | Flask-JWT==0.2.0 3 | itsdangerous==1.1.0 4 | Jinja2==2.10.1 5 | MarkupSafe==1.1.0 6 | MySQL-python==1.2.5 7 | mysqlclient==1.4.1 8 | pyasn1==0.4.5 9 | python-ldap==3.1.0 10 | requests==2.21.0 11 | Werkzeug==0.15.3 12 | wsgiref==0.1.2 13 | gunicorn==19.9.0 14 | cryptography==2.4.2 15 | Unidecode==1.0.23 16 | bcrypt==3.1.5 17 | jsonschema==2.6.0 18 | -------------------------------------------------------------------------------- /api/runserver.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from gsenhaapi import app 3 | 4 | if __name__ == '__main__': 5 | port = 8080 6 | print 'Starting server on port:{0}'.format(port) 7 | app.run(host='127.0.0.1', port=port, debug=True) -------------------------------------------------------------------------------- /api/script/mysqlcreate.sql: -------------------------------------------------------------------------------- 1 | SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0; 2 | SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0; 3 | SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='TRADITIONAL,ALLOW_INVALID_DATES'; 4 | 5 | -- ----------------------------------------------------- 6 | -- Schema gsenha 7 | -- ----------------------------------------------------- 8 | DROP SCHEMA IF EXISTS `gsenha` ; 9 | 10 | -- ----------------------------------------------------- 11 | -- Schema gsenha 12 | -- ----------------------------------------------------- 13 | CREATE SCHEMA IF NOT EXISTS `gsenha` ; 14 | USE `gsenha` ; 15 | 16 | -- 17 | -- Table structure for table `Usuarios` 18 | -- 19 | 20 | DROP TABLE IF EXISTS `Usuarios`; 21 | CREATE TABLE `Usuarios` ( 22 | `idUsuario` int(11) NOT NULL AUTO_INCREMENT, 23 | `NomeUsuario` varchar(45) NOT NULL, 24 | `Email` varchar(45) NOT NULL, 25 | `username` varchar(45) NOT NULL, 26 | `PublicKey` varchar(1500) NOT NULL, 27 | `token` varchar(65) NOT NULL DEFAULT '0', 28 | `hash` varchar(65) NOT NULL DEFAULT '0', 29 | PRIMARY KEY (`idUsuario`), 30 | UNIQUE KEY `username_UNIQUE` (`username`) 31 | ) ENGINE=InnoDB; 32 | 33 | -- 34 | -- Table structure for table `Grupos` 35 | -- 36 | 37 | DROP TABLE IF EXISTS `Grupos`; 38 | CREATE TABLE `Grupos` ( 39 | `idGrupo` int(11) NOT NULL AUTO_INCREMENT, 40 | `NomeGrupo` varchar(45) NOT NULL, 41 | PRIMARY KEY (`idGrupo`), 42 | UNIQUE KEY `NomeGrupo_UNIQUE` (`NomeGrupo`) 43 | ) ENGINE=InnoDB; 44 | 45 | -- 46 | -- Table structure for table `Pastas` 47 | -- 48 | 49 | DROP TABLE IF EXISTS `Pastas`; 50 | CREATE TABLE `Pastas` ( 51 | `idPasta` int(11) NOT NULL AUTO_INCREMENT, 52 | `lft` int(11) NOT NULL, 53 | `rgt` int(11) NOT NULL, 54 | `NomePasta` varchar(200) NOT NULL, 55 | `Usuarios_idUsuario` int(11) DEFAULT NULL, 56 | `Grupos_idGrupo` int(11) DEFAULT NULL, 57 | PRIMARY KEY (`idPasta`), 58 | UNIQUE KEY `NomePasta` (`NomePasta`), 59 | KEY `fk_Pastas_Usuarios1_idx` (`Usuarios_idUsuario`), 60 | KEY `fk_Pastas_Grupos1_idx` (`Grupos_idGrupo`), 61 | CONSTRAINT `fk_Pastas_Grupos1` FOREIGN KEY (`Grupos_idGrupo`) REFERENCES `Grupos` (`idGrupo`) ON DELETE NO ACTION ON UPDATE NO ACTION, 62 | CONSTRAINT `fk_Pastas_Usuarios1` FOREIGN KEY (`Usuarios_idUsuario`) REFERENCES `Usuarios` (`idUsuario`) ON DELETE NO ACTION ON UPDATE NO ACTION 63 | ) ENGINE=InnoDB; 64 | 65 | -- 66 | -- Table structure for table `Passwords` 67 | -- 68 | 69 | DROP TABLE IF EXISTS `Passwords`; 70 | CREATE TABLE `Passwords` ( 71 | `idPassword` int(11) NOT NULL AUTO_INCREMENT, 72 | `Usuario_idUsuario` int(11) NOT NULL, 73 | `Grupo_idGrupo` int(11) NOT NULL, 74 | `Pastas_idPastas` int(11) NOT NULL, 75 | `idCompartilhado` int(11) DEFAULT NULL, 76 | `senha` varchar(1000) NOT NULL, 77 | `url` varchar(1000) DEFAULT NULL, 78 | `login` varchar(1000) DEFAULT NULL, 79 | `descricao` varchar(1000) DEFAULT NULL, 80 | `nome` varchar(100) NOT NULL, 81 | PRIMARY KEY (`idPassword`), 82 | UNIQUE KEY `idPassword` (`idPassword`), 83 | UNIQUE KEY `nome_unico` (`nome`,`Usuario_idUsuario`,`Grupo_idGrupo`,`Pastas_idPastas`), 84 | KEY `fk_Password_Usuario_idx` (`Usuario_idUsuario`), 85 | KEY `fk_Password_Grupo1_idx` (`Grupo_idGrupo`), 86 | KEY `fk_Passwords_Pastas1_idx` (`Pastas_idPastas`), 87 | CONSTRAINT `fk_Passwords_Pastas1` FOREIGN KEY (`Pastas_idPastas`) REFERENCES `Pastas` (`idPasta`) ON DELETE NO ACTION ON UPDATE NO ACTION, 88 | CONSTRAINT `fk_Password_Grupo1` FOREIGN KEY (`Grupo_idGrupo`) REFERENCES `Grupos` (`idGrupo`) ON DELETE NO ACTION ON UPDATE NO ACTION, 89 | CONSTRAINT `fk_Password_Usuario` FOREIGN KEY (`Usuario_idUsuario`) REFERENCES `Usuarios` (`idUsuario`) ON DELETE NO ACTION ON UPDATE NO ACTION 90 | ) ENGINE=InnoDB; 91 | 92 | -- 93 | -- Table structure for table `Usuario_Grupo` 94 | -- 95 | 96 | DROP TABLE IF EXISTS `Usuario_Grupo`; 97 | CREATE TABLE `Usuario_Grupo` ( 98 | `idUsuario_Grupo` int(11) NOT NULL AUTO_INCREMENT, 99 | `Usuarios_idUsuario` int(11) NOT NULL, 100 | `Grupos_idGrupo` int(11) NOT NULL, 101 | PRIMARY KEY (`idUsuario_Grupo`), 102 | KEY `fk_Usuario_Grupo_Usuarios1_idx` (`Usuarios_idUsuario`), 103 | KEY `fk_Usuario_Grupo_Grupos1_idx` (`Grupos_idGrupo`), 104 | CONSTRAINT `fk_Usuario_Grupo_Grupos1` FOREIGN KEY (`Grupos_idGrupo`) REFERENCES `Grupos` (`idGrupo`) ON DELETE NO ACTION ON UPDATE NO ACTION, 105 | CONSTRAINT `fk_Usuario_Grupo_Usuarios1` FOREIGN KEY (`Usuarios_idUsuario`) REFERENCES `Usuarios` (`idUsuario`) ON DELETE NO ACTION ON UPDATE NO ACTION 106 | ) ENGINE=InnoDB; 107 | 108 | 109 | INSERT INTO `gsenha`.`Grupos` (`NomeGrupo`) VALUES ("Personal"); 110 | 111 | INSERT INTO `gsenha`.`Pastas`(`lft`,`rgt`,`NomePasta`,`Usuarios_idUsuario`,`Grupos_idGrupo`) 112 | VALUES ("0","0","/",NULL,NULL); 113 | 114 | LOCK TABLES `gsenha`.`Pastas` WRITE; 115 | SELECT @myLeft:= lft FROM `gsenha`.`Pastas` WHERE NomePasta = "/"; 116 | UPDATE `gsenha`.`Pastas` SET rgt = (rgt + 2) WHERE rgt > @myLeft; 117 | UPDATE `gsenha`.`Pastas` SET lft = (lft + 2) WHERE lft > @myLeft; 118 | INSERT INTO `gsenha`.`Pastas` (`NomePasta`,`lft`,`rgt`,`Grupos_idGrupo`,`Usuarios_idUsuario`) VALUES ("/Personal",@myLeft +1,@myLeft +2,NULL,NULL); 119 | UNLOCK TABLES; 120 | 121 | LOCK TABLES `gsenha`.`Pastas` WRITE; 122 | SELECT @myLeft:= lft FROM `gsenha`.`Pastas` WHERE NomePasta = "/"; 123 | UPDATE `gsenha`.`Pastas` SET rgt = (rgt + 2) WHERE rgt > @myLeft; 124 | UPDATE `gsenha`.`Pastas` SET lft = (lft + 2) WHERE lft > @myLeft; 125 | INSERT INTO `gsenha`.`Pastas` (`NomePasta`,`lft`,`rgt`,`Grupos_idGrupo`,`Usuarios_idUsuario`) VALUES ("/Shared",@myLeft +1,@myLeft +2,NULL,NULL); 126 | UNLOCK TABLES; 127 | -------------------------------------------------------------------------------- /ui/Procfile: -------------------------------------------------------------------------------- 1 | gsenha: gunicorn -b 0.0.0.0:$PORT -t 90 main:app -------------------------------------------------------------------------------- /ui/forms.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from flask_wtf import FlaskForm 3 | from flask_appbuilder.forms import DynamicForm 4 | from wtforms import StringField, SubmitField, PasswordField, RadioField, HiddenField, SelectMultipleField, SelectField, IntegerField, TextAreaField 5 | from wtforms.validators import Required, DataRequired, Length, EqualTo 6 | 7 | class LoginForm(FlaskForm): 8 | user = StringField('Username', validators=[Required(), Length(1,256)]) 9 | password = PasswordField('Password', validators=[Required(),Length(1,256)]) 10 | submit = SubmitField('Submit') 11 | 12 | class AddPasswordPersonalForm(FlaskForm): 13 | name = StringField('Name',validators=[Required(),Length(1,256)]) 14 | passwd = PasswordField('Password',validators=[Required(),Length(1,256),EqualTo('passwd2', message=u'Passwords are not the same.')]) 15 | passwd2 = PasswordField('Confirm Password',validators=[Required(),Length(1,256)]) 16 | folder = SelectField('Folder',validators=[Required()],coerce=str) 17 | login = StringField('Login',validators=[Required(),Length(1,256)]) 18 | url = StringField('Url',validators=[Length(0,256)]) 19 | description = TextAreaField(u'Description',validators=[Required(),Length(1,256)]) 20 | submit = SubmitField('Submit') 21 | 22 | class AddPasswordGroupForm(FlaskForm): 23 | name = StringField('Name',validators=[Required(),Length(1,256)]) 24 | passwd = PasswordField('Password',validators=[Required(),Length(1,256),EqualTo('passwd2', message=u'Passwords are not the same.')]) 25 | passwd2 = PasswordField('Confirm Password',validators=[Required(),Length(1,256)]) 26 | folder = SelectField('Folder',validators=[Required()],coerce=str) 27 | group = SelectField('Group',coerce=str) 28 | login = StringField('Login',validators=[Required(),Length(1,256)]) 29 | url = StringField('Url',validators=[Length(0,256)]) 30 | description = TextAreaField(u'Description',validators=[Required(),Length(1,256)]) 31 | submit = SubmitField('Submit') 32 | 33 | class AddPasswordExtUserForm(FlaskForm): 34 | name = StringField('Name',validators=[Required(),Length(1,256)]) 35 | passwd = PasswordField('Password',validators=[Required(),Length(1,256),EqualTo('passwd2', message=u'Passwords are not the same.')]) 36 | passwd2 = PasswordField('Confirm Password',validators=[Required(),Length(1,256)]) 37 | username = StringField('Username',validators=[Required(),Length(1,256)]) 38 | login = StringField('Login',validators=[Required(),Length(1,256)]) 39 | url = StringField('Url',validators=[Length(0,256)]) 40 | description = TextAreaField(u'Description',validators=[Required(),Length(1,256)]) 41 | submit = SubmitField('Submit') 42 | 43 | class AddPasswordExtGroupForm(FlaskForm): 44 | name = StringField('Name',validators=[Required(),Length(1,256)]) 45 | passwd = PasswordField('Password',validators=[Required(),Length(1,256),EqualTo('passwd2', message=u'Passwords are not the same.')]) 46 | passwd2 = PasswordField('Confirm Password',validators=[Required(),Length(1,256)]) 47 | group = SelectField('Group',coerce=str) 48 | login = StringField('Login',validators=[Required(),Length(1,256)]) 49 | url = StringField('Url',validators=[Length(0,256)]) 50 | description = TextAreaField(u'Description',validators=[Required(),Length(1,256)]) 51 | submit = SubmitField('Submit') 52 | 53 | class AddUserForm(FlaskForm): 54 | user = StringField('Username',validators=[Required(),Length(1,256)]) 55 | password = PasswordField('Password',validators=[Required(),Length(1,256)]) 56 | pk = TextAreaField('Public Key',validators=[Required(),Length(1,1500)]) 57 | submit = SubmitField('Submit') 58 | 59 | class AddFolderForm(FlaskForm): 60 | path = SelectField('Path',coerce=str,validators=[Required()]) 61 | name = StringField('Name',validators=[Required(),Length(1,256)]) 62 | submit = SubmitField('Submit') 63 | 64 | class DeleteFolderForm(FlaskForm): 65 | folder = SelectField('Folder',coerce=str,validators=[Required()]) 66 | submit = SubmitField('Submit') 67 | 68 | class UnlockForm(FlaskForm): 69 | usertounlock = StringField(u'User to unlock',validators=[Required(),Length(1,256)]) 70 | group = SelectField('Group',validators=[Required()],coerce=str) 71 | submit = SubmitField('Submit') 72 | 73 | class UpdatePasswdForm(FlaskForm): 74 | id_passwd = HiddenField('ID',validators=[Required()]) 75 | passwd = PasswordField('Password',validators=[Length(0,256),EqualTo('passwd2', message=u'Passwords are not tha same')]) 76 | passwd2 = PasswordField('Confirm Password',validators=[Length(0,256)]) 77 | url = StringField('Url',validators=[Length(0,256)]) 78 | login = StringField('Login',validators=[Length(0,256)]) 79 | name = StringField('Name',validators=[Length(0,256)]) 80 | description = StringField(u'Description',validators=[Length(0,256)]) 81 | submit = SubmitField('Submit') 82 | 83 | class UpdatePubKeyForm(FlaskForm): 84 | pubkey = TextAreaField('New Public Key',validators=[Required(),Length(1,1500)]) 85 | privkey = TextAreaField('Current Private Key',validators=[Required(),Length(1,4000)]) 86 | submit = SubmitField('Submit') 87 | 88 | class DeletePasswordForm(FlaskForm): 89 | id_passwd = IntegerField('Password ID',validators=[Required()]) 90 | submit = SubmitField('Submit') -------------------------------------------------------------------------------- /ui/requirements.txt: -------------------------------------------------------------------------------- 1 | Flask==1.0.2 2 | Babel==2.6.0 3 | Flask-AppBuilder==1.12.2 4 | Flask-BabelPkg==0.9.6 5 | Flask-Bootstrap==3.3.7.1 6 | Flask-Login==0.4.1 7 | Flask-OpenID==1.2.5 8 | Flask-SQLAlchemy==2.3.2 9 | Flask-WTF==0.14.2 10 | itsdangerous==1.1.0 11 | Jinja2==2.10.1 12 | MarkupSafe==1.1.0 13 | pytz==2018.9 14 | requests==2.21.0 15 | speaklater==1.3 16 | SQLAlchemy==1.3.0 17 | Werkzeug==0.15.3 18 | wsgiref==0.1.2 19 | WTForms==2.2.1 20 | gunicorn==19.9.0 21 | -------------------------------------------------------------------------------- /ui/send_json.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import json, requests, sys, settings 3 | 4 | class SendJson: 5 | 6 | def send_get_passwords(self,token): 7 | url_gsenha = settings.URL_GSENHA_PASSWORDS 8 | bearer = "Bearer "+str(token) 9 | headers = {'Authorization':bearer} 10 | req = requests.get(url_gsenha, headers=headers, verify=True) 11 | return req 12 | 13 | def send_login(self,user,passwd): 14 | url_gsenha = settings.URL_GSENHA_LOGIN 15 | data = {} 16 | data["username"] = user 17 | data["password"] = passwd 18 | headers = {'Content-type': 'application/json', 'Accept': 'text/plain'} 19 | req = requests.post(url_gsenha, data=json.dumps(data), headers=headers, verify=True) 20 | return req 21 | 22 | def send_add_user(self,user,passwd,pk): 23 | url_gsenha = settings.URL_GSENHA_USER 24 | data = {} 25 | data["user"] = user 26 | data["password"] = passwd 27 | data["pubkey"] = pk 28 | headers = {'Content-type': 'application/json', 'Accept': 'text/plain'} 29 | req = requests.post(url_gsenha, data=json.dumps(data), headers=headers, verify=True) 30 | return req 31 | 32 | def send_add_password_personal(self,token,name,passwd,folder,login,url,description): 33 | url_gsenha = settings.URL_GSENHA_ADDPERSONAL 34 | data = {} 35 | data["name"] = name 36 | data["passwd"] = passwd 37 | data["folder"] = folder 38 | data["login"] = login 39 | data["url"] = url 40 | data["description"] = description 41 | bearer = "Bearer "+str(token) 42 | headers = {'Authorization':bearer,'Content-type': 'application/json', 'Accept': 'text/plain'} 43 | req = requests.post(url_gsenha, data=json.dumps(data), headers=headers, verify=True) 44 | return req 45 | 46 | def send_add_password_personal_url(self,token,name,passwd,folder,login,description): 47 | url_gsenha = settings.URL_GSENHA_ADDPERSONAL 48 | data = {} 49 | data["name"] = name 50 | data["passwd"] = passwd 51 | data["folder"] = folder 52 | data["login"] = login 53 | data["description"] = description 54 | bearer = "Bearer "+str(token) 55 | headers = {'Authorization':bearer,'Content-type': 'application/json', 'Accept': 'text/plain'} 56 | req = requests.post(url_gsenha, data=json.dumps(data), headers=headers, verify=True) 57 | return req 58 | 59 | def send_add_password_group(self,token,name,passwd,folder,group,login,url,description): 60 | url_gsenha = settings.URL_GSENHA_ADDSHARED 61 | data = {} 62 | data["name"] = name 63 | data["passwd"] = passwd 64 | data["group"] = group 65 | data["folder"] = folder 66 | data["login"] = login 67 | data["url"] = url 68 | data["description"] = description 69 | bearer = "Bearer "+str(token) 70 | headers = {'Authorization':bearer,'Content-type': 'application/json', 'Accept': 'text/plain'} 71 | req = requests.post(url_gsenha, data=json.dumps(data), headers=headers, verify=True) 72 | return req 73 | 74 | def send_add_password_group_url(self,token,name,passwd,folder,group,login,description): 75 | url_gsenha = settings.URL_GSENHA_ADDSHARED 76 | data = {} 77 | data["name"] = name 78 | data["passwd"] = passwd 79 | data["group"] = group 80 | data["folder"] = folder 81 | data["login"] = login 82 | data["description"] = description 83 | bearer = "Bearer "+str(token) 84 | headers = {'Authorization':bearer,'Content-type': 'application/json', 'Accept': 'text/plain'} 85 | req = requests.post(url_gsenha, data=json.dumps(data), headers=headers, verify=True) 86 | return req 87 | 88 | def send_add_password_personal_ext(self,token,name,passwd,username,login,url,description): 89 | url_gsenha = settings.URL_GSENHA_ADDPERSONALEXTERNAL 90 | data = {} 91 | data["name"] = name 92 | data["passwd"] = passwd 93 | data["username"] = username 94 | data["login"] = login 95 | data["url"] = url 96 | data["description"] = description 97 | bearer = "Bearer "+str(token) 98 | headers = {'Authorization':bearer,'Content-type': 'application/json', 'Accept': 'text/plain'} 99 | req = requests.post(url_gsenha, data=json.dumps(data), headers=headers, verify=True) 100 | return req 101 | 102 | def send_add_password_personal_ext_url(self,token,name,passwd,username,login,description): 103 | url_gsenha = settings.URL_GSENHA_ADDPERSONALEXTERNAL 104 | data = {} 105 | data["name"] = name 106 | data["passwd"] = passwd 107 | data["username"] = username 108 | data["login"] = login 109 | data["description"] = description 110 | bearer = "Bearer "+str(token) 111 | headers = {'Authorization':bearer,'Content-type': 'application/json', 'Accept': 'text/plain'} 112 | req = requests.post(url_gsenha, data=json.dumps(data), headers=headers, verify=True) 113 | return req 114 | 115 | def send_add_password_group_ext(self,token,name,passwd,group,login,url,description): 116 | url_gsenha = settings.URL_GSENHA_ADDSHAREDEXTERNAL 117 | data = {} 118 | data["name"] = name 119 | data["passwd"] = passwd 120 | data["group"] = group 121 | data["login"] = login 122 | data["url"] = url 123 | data["description"] = description 124 | bearer = "Bearer "+str(token) 125 | headers = {'Authorization':bearer,'Content-type': 'application/json', 'Accept': 'text/plain'} 126 | req = requests.post(url_gsenha, data=json.dumps(data), headers=headers, verify=True) 127 | return req 128 | 129 | def send_add_password_group_ext_url(self,token,name,passwd,group,login,description): 130 | url_gsenha = settings.URL_GSENHA_ADDSHAREDEXTERNAL 131 | data = {} 132 | data["name"] = name 133 | data["passwd"] = passwd 134 | data["group"] = group 135 | data["login"] = login 136 | data["description"] = description 137 | bearer = "Bearer "+str(token) 138 | headers = {'Authorization':bearer,'Content-type': 'application/json', 'Accept': 'text/plain'} 139 | req = requests.post(url_gsenha, data=json.dumps(data), headers=headers, verify=True) 140 | return req 141 | 142 | def send_add_folder(self,token,path,name): 143 | url_gsenha = settings.URL_GSENHA_ADDFODLER 144 | data = {} 145 | data["path"] = path 146 | data["name"] = name 147 | bearer = "Bearer "+str(token) 148 | headers = {'Authorization':bearer,'Content-type': 'application/json', 'Accept': 'text/plain'} 149 | req = requests.post(url_gsenha, data=json.dumps(data), headers=headers, verify=True) 150 | return req 151 | 152 | def send_del_folder(self,token,folder): 153 | url_gsenha = settings.URL_GSENHA_DELFOLDER 154 | data = {} 155 | data["folder"] = folder 156 | bearer = "Bearer "+str(token) 157 | headers = {'Authorization':bearer,'Content-type': 'application/json', 'Accept': 'text/plain'} 158 | req = requests.post(url_gsenha, data=json.dumps(data), headers=headers, verify=True) 159 | return req 160 | 161 | def send_get_folders(self,token): 162 | url_gsenha = settings.URL_GSENHA_GETFOLDERS 163 | bearer = "Bearer "+str(token) 164 | headers = {'Authorization':bearer} 165 | req = requests.get(url_gsenha, headers=headers, verify=True) 166 | return req 167 | 168 | def send_get_groups(self,token): 169 | url_gsenha = settings.URL_GSENHA_GETGROUPS 170 | bearer = "Bearer "+str(token) 171 | headers = {'Authorization':bearer} 172 | req = requests.get(url_gsenha, headers=headers, verify=True) 173 | return req 174 | 175 | def send_get_mygroups(self,token): 176 | url_gsenha = settings.URL_GSENHA_GETMYGROUPS 177 | bearer = "Bearer "+str(token) 178 | headers = {'Authorization':bearer} 179 | req = requests.get(url_gsenha, headers=headers, verify=True) 180 | return req 181 | 182 | def send_get_tree(self,token): 183 | url_gsenha = settings.URL_GSENHA_GETTREE 184 | bearer = "Bearer "+str(token) 185 | headers = {'Authorization':bearer} 186 | req = requests.get(url_gsenha, headers=headers, verify=True) 187 | return req 188 | 189 | def send_unlock(self,token,group,usertounlock): 190 | url_gsenha = settings.URL_GSENHA_UNLOCK 191 | data = {} 192 | data["group"] = group 193 | data["usertounlock"] = usertounlock 194 | bearer = "Bearer "+str(token) 195 | headers = {'Authorization':bearer,'Content-type': 'application/json', 'Accept': 'text/plain'} 196 | req = requests.post(url_gsenha, data=json.dumps(data), headers=headers, verify=True) 197 | return req 198 | 199 | def send_unlock2(self,token,data): 200 | url_gsenha = settings.URL_GSENHA_UNLOCK2 201 | bearer = "Bearer "+str(token) 202 | headers = {'Authorization':bearer,'Content-type': 'application/json', 'Accept': 'text/plain'} 203 | req = requests.post(url_gsenha, data=json.dumps(data), headers=headers, verify=True) 204 | return req 205 | 206 | def send_update(self,token,id_passwd,passwd,url,login,name,description): 207 | url_gsenha = settings.URL_GSENHA_UPDATEPASSWD 208 | data = {} 209 | data["id"] = id_passwd 210 | if passwd != None: 211 | data["passwd"] = passwd 212 | if url != None: 213 | data["url"] = url 214 | if login != None: 215 | data["login"] = login 216 | if name != None: 217 | data["name"] = name 218 | if description != None: 219 | data["description"] = description 220 | bearer = "Bearer "+str(token) 221 | headers = {'Authorization':bearer,'Content-type': 'application/json', 'Accept': 'text/plain'} 222 | req = requests.post(url_gsenha, data=json.dumps(data), headers=headers, verify=True) 223 | return req 224 | 225 | def send_update_url(self,token,id_passwd,passwd,login,name,description): 226 | url_gsenha = settings.URL_GSENHA_UPDATEPASSWD 227 | data = {} 228 | data["id"] = id_passwd 229 | data["passwd"] = passwd 230 | data["login"] = login 231 | data["name"] = name 232 | data["description"] = description 233 | bearer = "Bearer "+str(token) 234 | headers = {'Authorization':bearer,'Content-type': 'application/json', 'Accept': 'text/plain'} 235 | req = requests.post(url_gsenha, data=json.dumps(data), headers=headers, verify=True) 236 | return req 237 | 238 | def send_update_pubkey(self,token,pubkey,privkey): 239 | url_gsenha = settings.URL_GSENHA_UPDATEPUBKEY 240 | data = {} 241 | data["pubkey"] = pubkey 242 | data["privkey"] = privkey 243 | bearer = "Bearer "+str(token) 244 | headers = {'Authorization':bearer,'Content-type': 'application/json', 'Accept': 'text/plain'} 245 | req = requests.post(url_gsenha, data=json.dumps(data), headers=headers, verify=True) 246 | return req 247 | 248 | def send_delete_password(self,token,idPassword): 249 | url_gsenha = settings.URL_GSENHA_DELPASSWORD 250 | bearer = "Bearer "+str(token) 251 | headers = {'Authorization':bearer,'Content-type': 'application/json', 'Accept': 'text/plain'} 252 | req = requests.delete(url_gsenha+"/"+idPassword, headers=headers, verify=True) 253 | return req 254 | 255 | def send_import(self,token,data): 256 | url_gsenha = settings.URL_GSENHA_ADDPERSONAL 257 | bearer = "Bearer "+str(token) 258 | headers = {'Authorization':bearer,'Content-type': 'application/json', 'Accept': 'text/plain'} 259 | req = requests.post(url_gsenha, data=json.dumps(data), headers=headers, verify=True) 260 | return req -------------------------------------------------------------------------------- /ui/settings.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | SECRET_KEY = os.environ.get('SECRET_KEY') 4 | URL_GSENHA_LOGIN = os.environ.get('URL_GSENHA_LOGIN') 5 | URL_GSENHA_PASSWORDS = os.environ.get('URL_GSENHA_PASSWORDS') 6 | URL_GSENHA_USER = os.environ.get('URL_GSENHA_USER') 7 | URL_GSENHA_ADDPERSONAL = os.environ.get('URL_GSENHA_ADDPERSONAL') 8 | URL_GSENHA_ADDSHARED = os.environ.get('URL_GSENHA_ADDSHARED') 9 | URL_GSENHA_ADDFODLER = os.environ.get('URL_GSENHA_ADDFOLDER') 10 | URL_GSENHA_DELFOLDER = os.environ.get('URL_GSENHA_DELFOLDER') 11 | URL_GSENHA_GETFOLDERS = os.environ.get('URL_GSENHA_GETFOLDERS') 12 | URL_GSENHA_GETTREE = os.environ.get('URL_GSENHA_GETTREE') 13 | URL_GSENHA_GETGROUPS = os.environ.get('URL_GSENHA_GETGROUPS') 14 | URL_GSENHA_GETMYGROUPS = os.environ.get('URL_GSENHA_GETMYGROUPS') 15 | URL_GSENHA_UNLOCK = os.environ.get('URL_GSENHA_UNLOCK') 16 | URL_GSENHA_UNLOCK2 = os.environ.get('URL_GSENHA_UNLOCK2') 17 | URL_GSENHA_UPDATEPASSWD = os.environ.get('URL_GSENHA_UPDATEPASSWD') 18 | URL_GSENHA_UPDATEPUBKEY = os.environ.get('URL_GSENHA_UPDATEPUBKEY') 19 | URL_GSENHA_DELPASSWORD = os.environ.get('URL_GSENHA_DELPASSWORD') 20 | URL_GSENHA_ADDPERSONALEXTERNAL = os.environ.get('URL_GSENHA_ADDPERSONALEXTERNAL') 21 | URL_GSENHA_ADDSHAREDEXTERNAL = os.environ.get('URL_GSENHA_ADDSHAREDEXTERNAL') 22 | BASE_URL = os.environ.get('BASE_URL') -------------------------------------------------------------------------------- /ui/static/css/32px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/globocom/gsenha/ba057d03fa68cdc608a0ea31000de817f5d88098/ui/static/css/32px.png -------------------------------------------------------------------------------- /ui/static/css/40px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/globocom/gsenha/ba057d03fa68cdc608a0ea31000de817f5d88098/ui/static/css/40px.png -------------------------------------------------------------------------------- /ui/static/css/bootstrap-theme.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap v3.3.1 (http://getbootstrap.com) 3 | * Copyright 2011-2014 Twitter, Inc. 4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 5 | */.btn-default,.btn-primary,.btn-success,.btn-info,.btn-warning,.btn-danger{text-shadow:0 -1px 0 rgba(0,0,0,.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 1px rgba(0,0,0,.075)}.btn-default:active,.btn-primary:active,.btn-success:active,.btn-info:active,.btn-warning:active,.btn-danger:active,.btn-default.active,.btn-primary.active,.btn-success.active,.btn-info.active,.btn-warning.active,.btn-danger.active{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn-default .badge,.btn-primary .badge,.btn-success .badge,.btn-info .badge,.btn-warning .badge,.btn-danger .badge{text-shadow:none}.btn:active,.btn.active{background-image:none}.btn-default{text-shadow:0 1px 0 #fff;background-image:-webkit-linear-gradient(top,#fff 0,#e0e0e0 100%);background-image:-o-linear-gradient(top,#fff 0,#e0e0e0 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#e0e0e0));background-image:linear-gradient(to bottom,#fff 0,#e0e0e0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#dbdbdb;border-color:#ccc}.btn-default:hover,.btn-default:focus{background-color:#e0e0e0;background-position:0 -15px}.btn-default:active,.btn-default.active{background-color:#e0e0e0;border-color:#dbdbdb}.btn-default:disabled,.btn-default[disabled]{background-color:#e0e0e0;background-image:none}.btn-primary{background-image:-webkit-linear-gradient(top,#337ab7 0,#265a88 100%);background-image:-o-linear-gradient(top,#337ab7 0,#265a88 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#265a88));background-image:linear-gradient(to bottom,#337ab7 0,#265a88 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff265a88', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#245580}.btn-primary:hover,.btn-primary:focus{background-color:#265a88;background-position:0 -15px}.btn-primary:active,.btn-primary.active{background-color:#265a88;border-color:#245580}.btn-primary:disabled,.btn-primary[disabled]{background-color:#265a88;background-image:none}.btn-success{background-image:-webkit-linear-gradient(top,#5cb85c 0,#419641 100%);background-image:-o-linear-gradient(top,#5cb85c 0,#419641 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5cb85c),to(#419641));background-image:linear-gradient(to bottom,#5cb85c 0,#419641 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff419641', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#3e8f3e}.btn-success:hover,.btn-success:focus{background-color:#419641;background-position:0 -15px}.btn-success:active,.btn-success.active{background-color:#419641;border-color:#3e8f3e}.btn-success:disabled,.btn-success[disabled]{background-color:#419641;background-image:none}.btn-info{background-image:-webkit-linear-gradient(top,#5bc0de 0,#2aabd2 100%);background-image:-o-linear-gradient(top,#5bc0de 0,#2aabd2 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5bc0de),to(#2aabd2));background-image:linear-gradient(to bottom,#5bc0de 0,#2aabd2 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2aabd2', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#28a4c9}.btn-info:hover,.btn-info:focus{background-color:#2aabd2;background-position:0 -15px}.btn-info:active,.btn-info.active{background-color:#2aabd2;border-color:#28a4c9}.btn-info:disabled,.btn-info[disabled]{background-color:#2aabd2;background-image:none}.btn-warning{background-image:-webkit-linear-gradient(top,#f0ad4e 0,#eb9316 100%);background-image:-o-linear-gradient(top,#f0ad4e 0,#eb9316 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f0ad4e),to(#eb9316));background-image:linear-gradient(to bottom,#f0ad4e 0,#eb9316 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffeb9316', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#e38d13}.btn-warning:hover,.btn-warning:focus{background-color:#eb9316;background-position:0 -15px}.btn-warning:active,.btn-warning.active{background-color:#eb9316;border-color:#e38d13}.btn-warning:disabled,.btn-warning[disabled]{background-color:#eb9316;background-image:none}.btn-danger{background-image:-webkit-linear-gradient(top,#d9534f 0,#c12e2a 100%);background-image:-o-linear-gradient(top,#d9534f 0,#c12e2a 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9534f),to(#c12e2a));background-image:linear-gradient(to bottom,#d9534f 0,#c12e2a 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc12e2a', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#b92c28}.btn-danger:hover,.btn-danger:focus{background-color:#c12e2a;background-position:0 -15px}.btn-danger:active,.btn-danger.active{background-color:#c12e2a;border-color:#b92c28}.btn-danger:disabled,.btn-danger[disabled]{background-color:#c12e2a;background-image:none}.thumbnail,.img-thumbnail{-webkit-box-shadow:0 1px 2px rgba(0,0,0,.075);box-shadow:0 1px 2px rgba(0,0,0,.075)}.dropdown-menu>li>a:hover,.dropdown-menu>li>a:focus{background-color:#e8e8e8;background-image:-webkit-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-o-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#e8e8e8));background-image:linear-gradient(to bottom,#f5f5f5 0,#e8e8e8 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);background-repeat:repeat-x}.dropdown-menu>.active>a,.dropdown-menu>.active>a:hover,.dropdown-menu>.active>a:focus{background-color:#2e6da4;background-image:-webkit-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2e6da4));background-image:linear-gradient(to bottom,#337ab7 0,#2e6da4 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);background-repeat:repeat-x}.navbar-default{background-image:-webkit-linear-gradient(top,#fff 0,#f8f8f8 100%);background-image:-o-linear-gradient(top,#fff 0,#f8f8f8 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#f8f8f8));background-image:linear-gradient(to bottom,#fff 0,#f8f8f8 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-radius:4px;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 5px rgba(0,0,0,.075);box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 5px rgba(0,0,0,.075)}.navbar-default .navbar-nav>.open>a,.navbar-default .navbar-nav>.active>a{background-image:-webkit-linear-gradient(top,#dbdbdb 0,#e2e2e2 100%);background-image:-o-linear-gradient(top,#dbdbdb 0,#e2e2e2 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#dbdbdb),to(#e2e2e2));background-image:linear-gradient(to bottom,#dbdbdb 0,#e2e2e2 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdbdbdb', endColorstr='#ffe2e2e2', GradientType=0);background-repeat:repeat-x;-webkit-box-shadow:inset 0 3px 9px rgba(0,0,0,.075);box-shadow:inset 0 3px 9px rgba(0,0,0,.075)}.navbar-brand,.navbar-nav>li>a{text-shadow:0 1px 0 rgba(255,255,255,.25)}.navbar-inverse{background-image:-webkit-linear-gradient(top,#3c3c3c 0,#222 100%);background-image:-o-linear-gradient(top,#3c3c3c 0,#222 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#3c3c3c),to(#222));background-image:linear-gradient(to bottom,#3c3c3c 0,#222 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x}.navbar-inverse .navbar-nav>.open>a,.navbar-inverse .navbar-nav>.active>a{background-image:-webkit-linear-gradient(top,#080808 0,#0f0f0f 100%);background-image:-o-linear-gradient(top,#080808 0,#0f0f0f 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#080808),to(#0f0f0f));background-image:linear-gradient(to bottom,#080808 0,#0f0f0f 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff080808', endColorstr='#ff0f0f0f', GradientType=0);background-repeat:repeat-x;-webkit-box-shadow:inset 0 3px 9px rgba(0,0,0,.25);box-shadow:inset 0 3px 9px rgba(0,0,0,.25)}.navbar-inverse .navbar-brand,.navbar-inverse .navbar-nav>li>a{text-shadow:0 -1px 0 rgba(0,0,0,.25)}.navbar-static-top,.navbar-fixed-top,.navbar-fixed-bottom{border-radius:0}@media (max-width:767px){.navbar .navbar-nav .open .dropdown-menu>.active>a,.navbar .navbar-nav .open .dropdown-menu>.active>a:hover,.navbar .navbar-nav .open .dropdown-menu>.active>a:focus{color:#fff;background-image:-webkit-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2e6da4));background-image:linear-gradient(to bottom,#337ab7 0,#2e6da4 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);background-repeat:repeat-x}}.alert{text-shadow:0 1px 0 rgba(255,255,255,.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.25),0 1px 2px rgba(0,0,0,.05);box-shadow:inset 0 1px 0 rgba(255,255,255,.25),0 1px 2px rgba(0,0,0,.05)}.alert-success{background-image:-webkit-linear-gradient(top,#dff0d8 0,#c8e5bc 100%);background-image:-o-linear-gradient(top,#dff0d8 0,#c8e5bc 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#dff0d8),to(#c8e5bc));background-image:linear-gradient(to bottom,#dff0d8 0,#c8e5bc 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0);background-repeat:repeat-x;border-color:#b2dba1}.alert-info{background-image:-webkit-linear-gradient(top,#d9edf7 0,#b9def0 100%);background-image:-o-linear-gradient(top,#d9edf7 0,#b9def0 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9edf7),to(#b9def0));background-image:linear-gradient(to bottom,#d9edf7 0,#b9def0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0);background-repeat:repeat-x;border-color:#9acfea}.alert-warning{background-image:-webkit-linear-gradient(top,#fcf8e3 0,#f8efc0 100%);background-image:-o-linear-gradient(top,#fcf8e3 0,#f8efc0 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fcf8e3),to(#f8efc0));background-image:linear-gradient(to bottom,#fcf8e3 0,#f8efc0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0);background-repeat:repeat-x;border-color:#f5e79e}.alert-danger{background-image:-webkit-linear-gradient(top,#f2dede 0,#e7c3c3 100%);background-image:-o-linear-gradient(top,#f2dede 0,#e7c3c3 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f2dede),to(#e7c3c3));background-image:linear-gradient(to bottom,#f2dede 0,#e7c3c3 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0);background-repeat:repeat-x;border-color:#dca7a7}.progress{background-image:-webkit-linear-gradient(top,#ebebeb 0,#f5f5f5 100%);background-image:-o-linear-gradient(top,#ebebeb 0,#f5f5f5 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#ebebeb),to(#f5f5f5));background-image:linear-gradient(to bottom,#ebebeb 0,#f5f5f5 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0);background-repeat:repeat-x}.progress-bar{background-image:-webkit-linear-gradient(top,#337ab7 0,#286090 100%);background-image:-o-linear-gradient(top,#337ab7 0,#286090 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#286090));background-image:linear-gradient(to bottom,#337ab7 0,#286090 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff286090', GradientType=0);background-repeat:repeat-x}.progress-bar-success{background-image:-webkit-linear-gradient(top,#5cb85c 0,#449d44 100%);background-image:-o-linear-gradient(top,#5cb85c 0,#449d44 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5cb85c),to(#449d44));background-image:linear-gradient(to bottom,#5cb85c 0,#449d44 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0);background-repeat:repeat-x}.progress-bar-info{background-image:-webkit-linear-gradient(top,#5bc0de 0,#31b0d5 100%);background-image:-o-linear-gradient(top,#5bc0de 0,#31b0d5 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5bc0de),to(#31b0d5));background-image:linear-gradient(to bottom,#5bc0de 0,#31b0d5 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0);background-repeat:repeat-x}.progress-bar-warning{background-image:-webkit-linear-gradient(top,#f0ad4e 0,#ec971f 100%);background-image:-o-linear-gradient(top,#f0ad4e 0,#ec971f 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f0ad4e),to(#ec971f));background-image:linear-gradient(to bottom,#f0ad4e 0,#ec971f 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0);background-repeat:repeat-x}.progress-bar-danger{background-image:-webkit-linear-gradient(top,#d9534f 0,#c9302c 100%);background-image:-o-linear-gradient(top,#d9534f 0,#c9302c 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9534f),to(#c9302c));background-image:linear-gradient(to bottom,#d9534f 0,#c9302c 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0);background-repeat:repeat-x}.progress-bar-striped{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.list-group{border-radius:4px;-webkit-box-shadow:0 1px 2px rgba(0,0,0,.075);box-shadow:0 1px 2px rgba(0,0,0,.075)}.list-group-item.active,.list-group-item.active:hover,.list-group-item.active:focus{text-shadow:0 -1px 0 #286090;background-image:-webkit-linear-gradient(top,#337ab7 0,#2b669a 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2b669a 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2b669a));background-image:linear-gradient(to bottom,#337ab7 0,#2b669a 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2b669a', GradientType=0);background-repeat:repeat-x;border-color:#2b669a}.list-group-item.active .badge,.list-group-item.active:hover .badge,.list-group-item.active:focus .badge{text-shadow:none}.panel{-webkit-box-shadow:0 1px 2px rgba(0,0,0,.05);box-shadow:0 1px 2px rgba(0,0,0,.05)}.panel-default>.panel-heading{background-image:-webkit-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-o-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#e8e8e8));background-image:linear-gradient(to bottom,#f5f5f5 0,#e8e8e8 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);background-repeat:repeat-x}.panel-primary>.panel-heading{background-image:-webkit-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2e6da4));background-image:linear-gradient(to bottom,#337ab7 0,#2e6da4 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);background-repeat:repeat-x}.panel-success>.panel-heading{background-image:-webkit-linear-gradient(top,#dff0d8 0,#d0e9c6 100%);background-image:-o-linear-gradient(top,#dff0d8 0,#d0e9c6 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#dff0d8),to(#d0e9c6));background-image:linear-gradient(to bottom,#dff0d8 0,#d0e9c6 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0);background-repeat:repeat-x}.panel-info>.panel-heading{background-image:-webkit-linear-gradient(top,#d9edf7 0,#c4e3f3 100%);background-image:-o-linear-gradient(top,#d9edf7 0,#c4e3f3 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9edf7),to(#c4e3f3));background-image:linear-gradient(to bottom,#d9edf7 0,#c4e3f3 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0);background-repeat:repeat-x}.panel-warning>.panel-heading{background-image:-webkit-linear-gradient(top,#fcf8e3 0,#faf2cc 100%);background-image:-o-linear-gradient(top,#fcf8e3 0,#faf2cc 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fcf8e3),to(#faf2cc));background-image:linear-gradient(to bottom,#fcf8e3 0,#faf2cc 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0);background-repeat:repeat-x}.panel-danger>.panel-heading{background-image:-webkit-linear-gradient(top,#f2dede 0,#ebcccc 100%);background-image:-o-linear-gradient(top,#f2dede 0,#ebcccc 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f2dede),to(#ebcccc));background-image:linear-gradient(to bottom,#f2dede 0,#ebcccc 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0);background-repeat:repeat-x}.well{background-image:-webkit-linear-gradient(top,#e8e8e8 0,#f5f5f5 100%);background-image:-o-linear-gradient(top,#e8e8e8 0,#f5f5f5 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#e8e8e8),to(#f5f5f5));background-image:linear-gradient(to bottom,#e8e8e8 0,#f5f5f5 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0);background-repeat:repeat-x;border-color:#dcdcdc;-webkit-box-shadow:inset 0 1px 3px rgba(0,0,0,.05),0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 3px rgba(0,0,0,.05),0 1px 0 rgba(255,255,255,.1)} -------------------------------------------------------------------------------- /ui/static/css/bootstrap-treeview.min.css: -------------------------------------------------------------------------------- 1 | .list-group-item{cursor:pointer}span.indent{margin-left:10px;margin-right:10px}span.icon{margin-right:5px} -------------------------------------------------------------------------------- /ui/static/css/font-awesome.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome 4.1.0 by @davegandy - http://fontawesome.io - @fontawesome 3 | * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) 4 | */@font-face{font-family:'FontAwesome';src:url('../fonts/fontawesome-webfont.eot?v=4.1.0');src:url('../fonts/fontawesome-webfont.eot?#iefix&v=4.1.0') format('embedded-opentype'),url('../fonts/fontawesome-webfont.woff?v=4.1.0') format('woff'),url('../fonts/fontawesome-webfont.ttf?v=4.1.0') format('truetype'),url('../fonts/fontawesome-webfont.svg?v=4.1.0#fontawesomeregular') format('svg');font-weight:normal;font-style:normal}.fa{display:inline-block;font-family:FontAwesome;font-style:normal;font-weight:normal;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571429em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14285714em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14285714em;width:2.14285714em;top:.14285714em;text-align:center}.fa-li.fa-lg{left:-1.85714286em}.fa-border{padding:.2em .25em .15em;border:solid .08em #eee;border-radius:.1em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left{margin-right:.3em}.fa.pull-right{margin-left:.3em}.fa-spin{-webkit-animation:spin 2s infinite linear;-moz-animation:spin 2s infinite linear;-o-animation:spin 2s infinite linear;animation:spin 2s infinite linear}@-moz-keyframes spin{0%{-moz-transform:rotate(0deg)}100%{-moz-transform:rotate(359deg)}}@-webkit-keyframes spin{0%{-webkit-transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg)}}@-o-keyframes spin{0%{-o-transform:rotate(0deg)}100%{-o-transform:rotate(359deg)}}@keyframes spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=1);-webkit-transform:rotate(90deg);-moz-transform:rotate(90deg);-ms-transform:rotate(90deg);-o-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2);-webkit-transform:rotate(180deg);-moz-transform:rotate(180deg);-ms-transform:rotate(180deg);-o-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=3);-webkit-transform:rotate(270deg);-moz-transform:rotate(270deg);-ms-transform:rotate(270deg);-o-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1);-webkit-transform:scale(-1, 1);-moz-transform:scale(-1, 1);-ms-transform:scale(-1, 1);-o-transform:scale(-1, 1);transform:scale(-1, 1)}.fa-flip-vertical{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1);-webkit-transform:scale(1, -1);-moz-transform:scale(1, -1);-ms-transform:scale(1, -1);-o-transform:scale(1, -1);transform:scale(1, -1)}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:"\f000"}.fa-music:before{content:"\f001"}.fa-search:before{content:"\f002"}.fa-envelope-o:before{content:"\f003"}.fa-heart:before{content:"\f004"}.fa-star:before{content:"\f005"}.fa-star-o:before{content:"\f006"}.fa-user:before{content:"\f007"}.fa-film:before{content:"\f008"}.fa-th-large:before{content:"\f009"}.fa-th:before{content:"\f00a"}.fa-th-list:before{content:"\f00b"}.fa-check:before{content:"\f00c"}.fa-times:before{content:"\f00d"}.fa-search-plus:before{content:"\f00e"}.fa-search-minus:before{content:"\f010"}.fa-power-off:before{content:"\f011"}.fa-signal:before{content:"\f012"}.fa-gear:before,.fa-cog:before{content:"\f013"}.fa-trash-o:before{content:"\f014"}.fa-home:before{content:"\f015"}.fa-file-o:before{content:"\f016"}.fa-clock-o:before{content:"\f017"}.fa-road:before{content:"\f018"}.fa-download:before{content:"\f019"}.fa-arrow-circle-o-down:before{content:"\f01a"}.fa-arrow-circle-o-up:before{content:"\f01b"}.fa-inbox:before{content:"\f01c"}.fa-play-circle-o:before{content:"\f01d"}.fa-rotate-right:before,.fa-repeat:before{content:"\f01e"}.fa-refresh:before{content:"\f021"}.fa-list-alt:before{content:"\f022"}.fa-lock:before{content:"\f023"}.fa-flag:before{content:"\f024"}.fa-headphones:before{content:"\f025"}.fa-volume-off:before{content:"\f026"}.fa-volume-down:before{content:"\f027"}.fa-volume-up:before{content:"\f028"}.fa-qrcode:before{content:"\f029"}.fa-barcode:before{content:"\f02a"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-book:before{content:"\f02d"}.fa-bookmark:before{content:"\f02e"}.fa-print:before{content:"\f02f"}.fa-camera:before{content:"\f030"}.fa-font:before{content:"\f031"}.fa-bold:before{content:"\f032"}.fa-italic:before{content:"\f033"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-align-left:before{content:"\f036"}.fa-align-center:before{content:"\f037"}.fa-align-right:before{content:"\f038"}.fa-align-justify:before{content:"\f039"}.fa-list:before{content:"\f03a"}.fa-dedent:before,.fa-outdent:before{content:"\f03b"}.fa-indent:before{content:"\f03c"}.fa-video-camera:before{content:"\f03d"}.fa-photo:before,.fa-image:before,.fa-picture-o:before{content:"\f03e"}.fa-pencil:before{content:"\f040"}.fa-map-marker:before{content:"\f041"}.fa-adjust:before{content:"\f042"}.fa-tint:before{content:"\f043"}.fa-edit:before,.fa-pencil-square-o:before{content:"\f044"}.fa-share-square-o:before{content:"\f045"}.fa-check-square-o:before{content:"\f046"}.fa-arrows:before{content:"\f047"}.fa-step-backward:before{content:"\f048"}.fa-fast-backward:before{content:"\f049"}.fa-backward:before{content:"\f04a"}.fa-play:before{content:"\f04b"}.fa-pause:before{content:"\f04c"}.fa-stop:before{content:"\f04d"}.fa-forward:before{content:"\f04e"}.fa-fast-forward:before{content:"\f050"}.fa-step-forward:before{content:"\f051"}.fa-eject:before{content:"\f052"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-plus-circle:before{content:"\f055"}.fa-minus-circle:before{content:"\f056"}.fa-times-circle:before{content:"\f057"}.fa-check-circle:before{content:"\f058"}.fa-question-circle:before{content:"\f059"}.fa-info-circle:before{content:"\f05a"}.fa-crosshairs:before{content:"\f05b"}.fa-times-circle-o:before{content:"\f05c"}.fa-check-circle-o:before{content:"\f05d"}.fa-ban:before{content:"\f05e"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrow-down:before{content:"\f063"}.fa-mail-forward:before,.fa-share:before{content:"\f064"}.fa-expand:before{content:"\f065"}.fa-compress:before{content:"\f066"}.fa-plus:before{content:"\f067"}.fa-minus:before{content:"\f068"}.fa-asterisk:before{content:"\f069"}.fa-exclamation-circle:before{content:"\f06a"}.fa-gift:before{content:"\f06b"}.fa-leaf:before{content:"\f06c"}.fa-fire:before{content:"\f06d"}.fa-eye:before{content:"\f06e"}.fa-eye-slash:before{content:"\f070"}.fa-warning:before,.fa-exclamation-triangle:before{content:"\f071"}.fa-plane:before{content:"\f072"}.fa-calendar:before{content:"\f073"}.fa-random:before{content:"\f074"}.fa-comment:before{content:"\f075"}.fa-magnet:before{content:"\f076"}.fa-chevron-up:before{content:"\f077"}.fa-chevron-down:before{content:"\f078"}.fa-retweet:before{content:"\f079"}.fa-shopping-cart:before{content:"\f07a"}.fa-folder:before{content:"\f07b"}.fa-folder-open:before{content:"\f07c"}.fa-arrows-v:before{content:"\f07d"}.fa-arrows-h:before{content:"\f07e"}.fa-bar-chart-o:before{content:"\f080"}.fa-twitter-square:before{content:"\f081"}.fa-facebook-square:before{content:"\f082"}.fa-camera-retro:before{content:"\f083"}.fa-key:before{content:"\f084"}.fa-gears:before,.fa-cogs:before{content:"\f085"}.fa-comments:before{content:"\f086"}.fa-thumbs-o-up:before{content:"\f087"}.fa-thumbs-o-down:before{content:"\f088"}.fa-star-half:before{content:"\f089"}.fa-heart-o:before{content:"\f08a"}.fa-sign-out:before{content:"\f08b"}.fa-linkedin-square:before{content:"\f08c"}.fa-thumb-tack:before{content:"\f08d"}.fa-external-link:before{content:"\f08e"}.fa-sign-in:before{content:"\f090"}.fa-trophy:before{content:"\f091"}.fa-github-square:before{content:"\f092"}.fa-upload:before{content:"\f093"}.fa-lemon-o:before{content:"\f094"}.fa-phone:before{content:"\f095"}.fa-square-o:before{content:"\f096"}.fa-bookmark-o:before{content:"\f097"}.fa-phone-square:before{content:"\f098"}.fa-twitter:before{content:"\f099"}.fa-facebook:before{content:"\f09a"}.fa-github:before{content:"\f09b"}.fa-unlock:before{content:"\f09c"}.fa-credit-card:before{content:"\f09d"}.fa-rss:before{content:"\f09e"}.fa-hdd-o:before{content:"\f0a0"}.fa-bullhorn:before{content:"\f0a1"}.fa-bell:before{content:"\f0f3"}.fa-certificate:before{content:"\f0a3"}.fa-hand-o-right:before{content:"\f0a4"}.fa-hand-o-left:before{content:"\f0a5"}.fa-hand-o-up:before{content:"\f0a6"}.fa-hand-o-down:before{content:"\f0a7"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-globe:before{content:"\f0ac"}.fa-wrench:before{content:"\f0ad"}.fa-tasks:before{content:"\f0ae"}.fa-filter:before{content:"\f0b0"}.fa-briefcase:before{content:"\f0b1"}.fa-arrows-alt:before{content:"\f0b2"}.fa-group:before,.fa-users:before{content:"\f0c0"}.fa-chain:before,.fa-link:before{content:"\f0c1"}.fa-cloud:before{content:"\f0c2"}.fa-flask:before{content:"\f0c3"}.fa-cut:before,.fa-scissors:before{content:"\f0c4"}.fa-copy:before,.fa-files-o:before{content:"\f0c5"}.fa-paperclip:before{content:"\f0c6"}.fa-save:before,.fa-floppy-o:before{content:"\f0c7"}.fa-square:before{content:"\f0c8"}.fa-navicon:before,.fa-reorder:before,.fa-bars:before{content:"\f0c9"}.fa-list-ul:before{content:"\f0ca"}.fa-list-ol:before{content:"\f0cb"}.fa-strikethrough:before{content:"\f0cc"}.fa-underline:before{content:"\f0cd"}.fa-table:before{content:"\f0ce"}.fa-magic:before{content:"\f0d0"}.fa-truck:before{content:"\f0d1"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-square:before{content:"\f0d3"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-plus:before{content:"\f0d5"}.fa-money:before{content:"\f0d6"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-up:before{content:"\f0d8"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-columns:before{content:"\f0db"}.fa-unsorted:before,.fa-sort:before{content:"\f0dc"}.fa-sort-down:before,.fa-sort-desc:before{content:"\f0dd"}.fa-sort-up:before,.fa-sort-asc:before{content:"\f0de"}.fa-envelope:before{content:"\f0e0"}.fa-linkedin:before{content:"\f0e1"}.fa-rotate-left:before,.fa-undo:before{content:"\f0e2"}.fa-legal:before,.fa-gavel:before{content:"\f0e3"}.fa-dashboard:before,.fa-tachometer:before{content:"\f0e4"}.fa-comment-o:before{content:"\f0e5"}.fa-comments-o:before{content:"\f0e6"}.fa-flash:before,.fa-bolt:before{content:"\f0e7"}.fa-sitemap:before{content:"\f0e8"}.fa-umbrella:before{content:"\f0e9"}.fa-paste:before,.fa-clipboard:before{content:"\f0ea"}.fa-lightbulb-o:before{content:"\f0eb"}.fa-exchange:before{content:"\f0ec"}.fa-cloud-download:before{content:"\f0ed"}.fa-cloud-upload:before{content:"\f0ee"}.fa-user-md:before{content:"\f0f0"}.fa-stethoscope:before{content:"\f0f1"}.fa-suitcase:before{content:"\f0f2"}.fa-bell-o:before{content:"\f0a2"}.fa-coffee:before{content:"\f0f4"}.fa-cutlery:before{content:"\f0f5"}.fa-file-text-o:before{content:"\f0f6"}.fa-building-o:before{content:"\f0f7"}.fa-hospital-o:before{content:"\f0f8"}.fa-ambulance:before{content:"\f0f9"}.fa-medkit:before{content:"\f0fa"}.fa-fighter-jet:before{content:"\f0fb"}.fa-beer:before{content:"\f0fc"}.fa-h-square:before{content:"\f0fd"}.fa-plus-square:before{content:"\f0fe"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angle-down:before{content:"\f107"}.fa-desktop:before{content:"\f108"}.fa-laptop:before{content:"\f109"}.fa-tablet:before{content:"\f10a"}.fa-mobile-phone:before,.fa-mobile:before{content:"\f10b"}.fa-circle-o:before{content:"\f10c"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-spinner:before{content:"\f110"}.fa-circle:before{content:"\f111"}.fa-mail-reply:before,.fa-reply:before{content:"\f112"}.fa-github-alt:before{content:"\f113"}.fa-folder-o:before{content:"\f114"}.fa-folder-open-o:before{content:"\f115"}.fa-smile-o:before{content:"\f118"}.fa-frown-o:before{content:"\f119"}.fa-meh-o:before{content:"\f11a"}.fa-gamepad:before{content:"\f11b"}.fa-keyboard-o:before{content:"\f11c"}.fa-flag-o:before{content:"\f11d"}.fa-flag-checkered:before{content:"\f11e"}.fa-terminal:before{content:"\f120"}.fa-code:before{content:"\f121"}.fa-mail-reply-all:before,.fa-reply-all:before{content:"\f122"}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:"\f123"}.fa-location-arrow:before{content:"\f124"}.fa-crop:before{content:"\f125"}.fa-code-fork:before{content:"\f126"}.fa-unlink:before,.fa-chain-broken:before{content:"\f127"}.fa-question:before{content:"\f128"}.fa-info:before{content:"\f129"}.fa-exclamation:before{content:"\f12a"}.fa-superscript:before{content:"\f12b"}.fa-subscript:before{content:"\f12c"}.fa-eraser:before{content:"\f12d"}.fa-puzzle-piece:before{content:"\f12e"}.fa-microphone:before{content:"\f130"}.fa-microphone-slash:before{content:"\f131"}.fa-shield:before{content:"\f132"}.fa-calendar-o:before{content:"\f133"}.fa-fire-extinguisher:before{content:"\f134"}.fa-rocket:before{content:"\f135"}.fa-maxcdn:before{content:"\f136"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-html5:before{content:"\f13b"}.fa-css3:before{content:"\f13c"}.fa-anchor:before{content:"\f13d"}.fa-unlock-alt:before{content:"\f13e"}.fa-bullseye:before{content:"\f140"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-rss-square:before{content:"\f143"}.fa-play-circle:before{content:"\f144"}.fa-ticket:before{content:"\f145"}.fa-minus-square:before{content:"\f146"}.fa-minus-square-o:before{content:"\f147"}.fa-level-up:before{content:"\f148"}.fa-level-down:before{content:"\f149"}.fa-check-square:before{content:"\f14a"}.fa-pencil-square:before{content:"\f14b"}.fa-external-link-square:before{content:"\f14c"}.fa-share-square:before{content:"\f14d"}.fa-compass:before{content:"\f14e"}.fa-toggle-down:before,.fa-caret-square-o-down:before{content:"\f150"}.fa-toggle-up:before,.fa-caret-square-o-up:before{content:"\f151"}.fa-toggle-right:before,.fa-caret-square-o-right:before{content:"\f152"}.fa-euro:before,.fa-eur:before{content:"\f153"}.fa-gbp:before{content:"\f154"}.fa-dollar:before,.fa-usd:before{content:"\f155"}.fa-rupee:before,.fa-inr:before{content:"\f156"}.fa-cny:before,.fa-rmb:before,.fa-yen:before,.fa-jpy:before{content:"\f157"}.fa-ruble:before,.fa-rouble:before,.fa-rub:before{content:"\f158"}.fa-won:before,.fa-krw:before{content:"\f159"}.fa-bitcoin:before,.fa-btc:before{content:"\f15a"}.fa-file:before{content:"\f15b"}.fa-file-text:before{content:"\f15c"}.fa-sort-alpha-asc:before{content:"\f15d"}.fa-sort-alpha-desc:before{content:"\f15e"}.fa-sort-amount-asc:before{content:"\f160"}.fa-sort-amount-desc:before{content:"\f161"}.fa-sort-numeric-asc:before{content:"\f162"}.fa-sort-numeric-desc:before{content:"\f163"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbs-down:before{content:"\f165"}.fa-youtube-square:before{content:"\f166"}.fa-youtube:before{content:"\f167"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-youtube-play:before{content:"\f16a"}.fa-dropbox:before{content:"\f16b"}.fa-stack-overflow:before{content:"\f16c"}.fa-instagram:before{content:"\f16d"}.fa-flickr:before{content:"\f16e"}.fa-adn:before{content:"\f170"}.fa-bitbucket:before{content:"\f171"}.fa-bitbucket-square:before{content:"\f172"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-long-arrow-down:before{content:"\f175"}.fa-long-arrow-up:before{content:"\f176"}.fa-long-arrow-left:before{content:"\f177"}.fa-long-arrow-right:before{content:"\f178"}.fa-apple:before{content:"\f179"}.fa-windows:before{content:"\f17a"}.fa-android:before{content:"\f17b"}.fa-linux:before{content:"\f17c"}.fa-dribbble:before{content:"\f17d"}.fa-skype:before{content:"\f17e"}.fa-foursquare:before{content:"\f180"}.fa-trello:before{content:"\f181"}.fa-female:before{content:"\f182"}.fa-male:before{content:"\f183"}.fa-gittip:before{content:"\f184"}.fa-sun-o:before{content:"\f185"}.fa-moon-o:before{content:"\f186"}.fa-archive:before{content:"\f187"}.fa-bug:before{content:"\f188"}.fa-vk:before{content:"\f189"}.fa-weibo:before{content:"\f18a"}.fa-renren:before{content:"\f18b"}.fa-pagelines:before{content:"\f18c"}.fa-stack-exchange:before{content:"\f18d"}.fa-arrow-circle-o-right:before{content:"\f18e"}.fa-arrow-circle-o-left:before{content:"\f190"}.fa-toggle-left:before,.fa-caret-square-o-left:before{content:"\f191"}.fa-dot-circle-o:before{content:"\f192"}.fa-wheelchair:before{content:"\f193"}.fa-vimeo-square:before{content:"\f194"}.fa-turkish-lira:before,.fa-try:before{content:"\f195"}.fa-plus-square-o:before{content:"\f196"}.fa-space-shuttle:before{content:"\f197"}.fa-slack:before{content:"\f198"}.fa-envelope-square:before{content:"\f199"}.fa-wordpress:before{content:"\f19a"}.fa-openid:before{content:"\f19b"}.fa-institution:before,.fa-bank:before,.fa-university:before{content:"\f19c"}.fa-mortar-board:before,.fa-graduation-cap:before{content:"\f19d"}.fa-yahoo:before{content:"\f19e"}.fa-google:before{content:"\f1a0"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-square:before{content:"\f1a2"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-stumbleupon:before{content:"\f1a4"}.fa-delicious:before{content:"\f1a5"}.fa-digg:before{content:"\f1a6"}.fa-pied-piper-square:before,.fa-pied-piper:before{content:"\f1a7"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-drupal:before{content:"\f1a9"}.fa-joomla:before{content:"\f1aa"}.fa-language:before{content:"\f1ab"}.fa-fax:before{content:"\f1ac"}.fa-building:before{content:"\f1ad"}.fa-child:before{content:"\f1ae"}.fa-paw:before{content:"\f1b0"}.fa-spoon:before{content:"\f1b1"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-recycle:before{content:"\f1b8"}.fa-automobile:before,.fa-car:before{content:"\f1b9"}.fa-cab:before,.fa-taxi:before{content:"\f1ba"}.fa-tree:before{content:"\f1bb"}.fa-spotify:before{content:"\f1bc"}.fa-deviantart:before{content:"\f1bd"}.fa-soundcloud:before{content:"\f1be"}.fa-database:before{content:"\f1c0"}.fa-file-pdf-o:before{content:"\f1c1"}.fa-file-word-o:before{content:"\f1c2"}.fa-file-excel-o:before{content:"\f1c3"}.fa-file-powerpoint-o:before{content:"\f1c4"}.fa-file-photo-o:before,.fa-file-picture-o:before,.fa-file-image-o:before{content:"\f1c5"}.fa-file-zip-o:before,.fa-file-archive-o:before{content:"\f1c6"}.fa-file-sound-o:before,.fa-file-audio-o:before{content:"\f1c7"}.fa-file-movie-o:before,.fa-file-video-o:before{content:"\f1c8"}.fa-file-code-o:before{content:"\f1c9"}.fa-vine:before{content:"\f1ca"}.fa-codepen:before{content:"\f1cb"}.fa-jsfiddle:before{content:"\f1cc"}.fa-life-bouy:before,.fa-life-saver:before,.fa-support:before,.fa-life-ring:before{content:"\f1cd"}.fa-circle-o-notch:before{content:"\f1ce"}.fa-ra:before,.fa-rebel:before{content:"\f1d0"}.fa-ge:before,.fa-empire:before{content:"\f1d1"}.fa-git-square:before{content:"\f1d2"}.fa-git:before{content:"\f1d3"}.fa-hacker-news:before{content:"\f1d4"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-qq:before{content:"\f1d6"}.fa-wechat:before,.fa-weixin:before{content:"\f1d7"}.fa-send:before,.fa-paper-plane:before{content:"\f1d8"}.fa-send-o:before,.fa-paper-plane-o:before{content:"\f1d9"}.fa-history:before{content:"\f1da"}.fa-circle-thin:before{content:"\f1db"}.fa-header:before{content:"\f1dc"}.fa-paragraph:before{content:"\f1dd"}.fa-sliders:before{content:"\f1de"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-bomb:before{content:"\f1e2"} -------------------------------------------------------------------------------- /ui/static/css/navbar.css: -------------------------------------------------------------------------------- 1 | .navbar-default { 2 | background-color: #3e72a9; 3 | border-color: #ffffff; 4 | } 5 | .navbar-default .navbar-brand { 6 | color: #ecf0f1; 7 | } 8 | .navbar-default .navbar-brand:hover, .navbar-default .navbar-brand:focus { 9 | color: #000000; 10 | } 11 | .navbar-default .navbar-text { 12 | color: #ecf0f1; 13 | } 14 | .navbar-default .navbar-nav > li > a { 15 | color: #ecf0f1; 16 | } 17 | .navbar-default .navbar-nav > li > a:hover, .navbar-default .navbar-nav > li > a:focus { 18 | color: #000000; 19 | } 20 | .navbar-default .navbar-nav > .active > a, .navbar-default .navbar-nav > .active > a:hover, .navbar-default .navbar-nav > .active > a:focus { 21 | color: #000000; 22 | background-color: #ffffff; 23 | } 24 | .navbar-default .navbar-nav > .open > a, .navbar-default .navbar-nav > .open > a:hover, .navbar-default .navbar-nav > .open > a:focus { 25 | color: #000000; 26 | background-color: #ffffff; 27 | } 28 | .navbar-default .navbar-toggle { 29 | border-color: #ffffff; 30 | } 31 | .navbar-default .navbar-toggle:hover, .navbar-default .navbar-toggle:focus { 32 | background-color: #ffffff; 33 | } 34 | .navbar-default .navbar-toggle .icon-bar { 35 | background-color: #ecf0f1; 36 | } 37 | .navbar-default .navbar-collapse, 38 | .navbar-default .navbar-form { 39 | border-color: #ecf0f1; 40 | } 41 | .navbar-default .navbar-link { 42 | color: #ecf0f1; 43 | } 44 | .navbar-default .navbar-link:hover { 45 | color: #000000; 46 | } 47 | 48 | @media (max-width: 767px) { 49 | .navbar-default .navbar-nav .open .dropdown-menu > li > a { 50 | color: #ecf0f1; 51 | } 52 | .navbar-default .navbar-nav .open .dropdown-menu > li > a:hover, .navbar-default .navbar-nav .open .dropdown-menu > li > a:focus { 53 | color: #000000; 54 | } 55 | .navbar-default .navbar-nav .open .dropdown-menu > .active > a, .navbar-default .navbar-nav .open .dropdown-menu > .active > a:hover, .navbar-default .navbar-nav .open .dropdown-menu > .active > a:focus { 56 | color: #000000; 57 | background-color: #ffffff; 58 | } 59 | } -------------------------------------------------------------------------------- /ui/static/css/style.css: -------------------------------------------------------------------------------- 1 | ul .tree{ 2 | padding: 0em; 3 | } 4 | 5 | ul.tree li, ul.tree li ul li { 6 | position:relative; 7 | top:0; 8 | bottom:0; 9 | padding-bottom: 7px; 10 | 11 | } 12 | 13 | ul.tree li ul { 14 | margin-left: 2em; 15 | } 16 | 17 | .tree li { 18 | list-style-type: none; 19 | } 20 | 21 | .tree li a { 22 | padding:0 0 0 10px; 23 | position: relative; 24 | top:1em; 25 | } 26 | 27 | .tree li a:hover { 28 | text-decoration: none; 29 | } 30 | 31 | a.addBorderBefore:before { 32 | content: ""; 33 | display: inline-block; 34 | width: 2px; 35 | height: 28px; 36 | position: absolute; 37 | left: -47px; 38 | top:-16px; 39 | border-left: 1px solid gray; 40 | } 41 | 42 | .tree li:before { 43 | content: ""; 44 | display: inline-block; 45 | width: 25px; 46 | height: 0; 47 | position: relative; 48 | left: 0em; 49 | top:1em; 50 | border-top: 1px solid gray; 51 | } 52 | 53 | ul.tree li ul li:last-child:after, ul.tree li:last-child:after { 54 | content: ''; 55 | display: block; 56 | width: 1em; 57 | height: 1em; 58 | position: relative; 59 | background: #fff; 60 | top: 9px; 61 | left: -1px; 62 | } 63 | 64 | .pointer { 65 | cursor: pointer; 66 | border-left: 0 !important; 67 | } 68 | 69 | /* Search flutuante 70 | #search { 71 | position: absolute; 72 | min-width: 25%; 73 | z-index: 2; 74 | } 75 | 76 | #tree1 { 77 | margin-top: 44px; 78 | } 79 | 80 | #tree1_q { 81 | width: 100%; 82 | }*/ -------------------------------------------------------------------------------- /ui/static/css/throbber.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/globocom/gsenha/ba057d03fa68cdc608a0ea31000de817f5d88098/ui/static/css/throbber.gif -------------------------------------------------------------------------------- /ui/static/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/globocom/gsenha/ba057d03fa68cdc608a0ea31000de817f5d88098/ui/static/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /ui/static/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/globocom/gsenha/ba057d03fa68cdc608a0ea31000de817f5d88098/ui/static/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /ui/static/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/globocom/gsenha/ba057d03fa68cdc608a0ea31000de817f5d88098/ui/static/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /ui/static/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/globocom/gsenha/ba057d03fa68cdc608a0ea31000de817f5d88098/ui/static/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /ui/static/gsenha-chrome.crx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/globocom/gsenha/ba057d03fa68cdc608a0ea31000de817f5d88098/ui/static/gsenha-chrome.crx -------------------------------------------------------------------------------- /ui/static/js/adduser.js: -------------------------------------------------------------------------------- 1 | function handleFiles() { 2 | startRead('pkfile'); 3 | } 4 | 5 | 6 | function startRead(id) { 7 | var file = document.getElementById(id).files[0]; 8 | var pk; 9 | if(file){ 10 | getAsText(file); 11 | } 12 | } 13 | 14 | function getAsText(readFile) { 15 | 16 | var reader = new FileReader(); 17 | reader.onload = function(evt){ document.getElementById('pk').value = evt.target.result } 18 | reader.readAsText(readFile); 19 | 20 | } -------------------------------------------------------------------------------- /ui/static/js/cleanSessionStorage.js: -------------------------------------------------------------------------------- 1 | function delIdPasswd(){ 2 | var tmp = sessionStorage.id_passwd; 3 | 4 | if(tmp != "undefined") 5 | { 6 | sessionStorage.removeItem("id_passwd"); 7 | } 8 | } -------------------------------------------------------------------------------- /ui/static/js/clipBoard.js: -------------------------------------------------------------------------------- 1 | function check_clipboard() { 2 | 3 | var teste = typeof(document.execCommand('copy')); 4 | if(teste == "boolean") 5 | { 6 | var elem = document.getElementsByName("clip"); 7 | for (var i = 0; i < elem.length; i++) { 8 | 9 | var new_elem_ext = document.createElement("SPAN"); 10 | var new_elem_int = document.createElement("SPAN"); 11 | 12 | new_elem_int.className = "glyphicon glyphicon-circle-arrow-up"; 13 | new_elem_ext.className = "input-group-addon pointer copy-to-clipboard"; 14 | new_elem_ext.title = "copied to clipboard"; 15 | new_id = elem[i].id; 16 | new_elem_ext.id = new_id.replace("clip","copy"); 17 | new_elem_ext.setAttribute("onClick", "copyToClipboard('"+new_elem_ext.id+"');"); 18 | 19 | new_elem_ext.setAttribute("data-toggle", "tooltip"); 20 | new_elem_ext.setAttribute("data-placement", "bottom"); 21 | new_elem_ext.setAttribute("data-trigger", "click"); 22 | 23 | new_elem_ext.appendChild(new_elem_int); 24 | elem[i].appendChild(new_elem_ext); 25 | 26 | } 27 | 28 | $('.copy-to-clipboard').tooltip('hide'); 29 | $('.copy-to-clipboard').on('click', function(){ 30 | setTimeout(function(){ 31 | $('.copy-to-clipboard').tooltip('hide'); 32 | }, 1000); 33 | }); 34 | } 35 | } 36 | 37 | function copyToClipboard(id) 38 | { 39 | var elem = document.getElementById(id.replace("copy","senha")); 40 | elem.type = "text"; 41 | elem.select(); 42 | document.execCommand('copy'); 43 | elem.type = "password"; 44 | } -------------------------------------------------------------------------------- /ui/static/js/deletePass.js: -------------------------------------------------------------------------------- 1 | function deletePass(id){ 2 | 3 | if(confirm("Do you realy want to delete this password?") == true) 4 | { 5 | 6 | var req = new XMLHttpRequest(); 7 | 8 | req.onreadystatechange = function() { 9 | if (req.readyState == 4) { 10 | location.reload(); 11 | } 12 | } 13 | 14 | req.open("DELETE","https://"+window.location.host+"/delete/password/"+id,true); 15 | req.send(); 16 | } 17 | } -------------------------------------------------------------------------------- /ui/static/js/exportPasswd.js: -------------------------------------------------------------------------------- 1 | function exportPasswd(){ 2 | if(confirm("You are about to export all your passwords in clear text, do you realy want to continue?") == true) 3 | { 4 | var saveData = (function (){ 5 | var a = document.createElement("a"); 6 | document.body.appendChild(a); 7 | a.style = "display: none"; 8 | return function (data, fileName) { 9 | var blob = new Blob([data], {type: "text/csv"}), 10 | url = window.URL.createObjectURL(blob); 11 | a.href = url; 12 | a.download = fileName; 13 | a.click(); 14 | window.URL.revokeObjectURL(url); 15 | }; 16 | }()); 17 | 18 | 19 | var elems = document.getElementById('senhas pessoais').getElementsByClassName("panel panel-primary"); 20 | 21 | var text = "uuid,group,title,url,user,password,notes\n" 22 | 23 | var decrypt = new JSEncrypt(); 24 | decrypt.setPrivateKey(sessionStorage.pk); 25 | 26 | for (var i = 0; i < elems.length; i++) { 27 | passwd_tmp = elems[i].getAttribute("name"); 28 | passwd = decrypt.decrypt(passwd_tmp); 29 | name = elems[i].getElementsByClassName("panel-heading")[0].childNodes[0].nodeValue; 30 | 31 | url = elems[i].getElementsByClassName("panel-body")[0].getElementsByClassName("form-group")[1].childNodes[3].value; 32 | if (url.length == 684) 33 | { 34 | url = decrypt.decrypt(url); 35 | } 36 | login = elems[i].getElementsByClassName("panel-body")[0].getElementsByClassName("form-group")[2].childNodes[3].value; 37 | if (login.length == 684) 38 | { 39 | login = decrypt.decrypt(login); 40 | } 41 | description = elems[i].getElementsByClassName("panel-body")[0].getElementsByClassName("form-group")[3].childNodes[3].value; 42 | if (description.length == 684) 43 | { 44 | description = decrypt.decrypt(description); 45 | } 46 | 47 | text = text+",,"+name+","+url+","+login+","+passwd+","+description+"\n"; 48 | } 49 | 50 | 51 | var fileName = "senhas.csv"; 52 | 53 | saveData(text,fileName) 54 | } 55 | } -------------------------------------------------------------------------------- /ui/static/js/generatePasswd.js: -------------------------------------------------------------------------------- 1 | function generateRandomChar() { 2 | 3 | char_set = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789`˜!@#$%ˆ&*()-_=+[]{}\\|;:/?.>,<" 4 | 5 | if(window.crypto && window.crypto.getRandomValues) 6 | { 7 | while(true) 8 | { 9 | var buff = new Uint8Array(1); 10 | window.crypto.getRandomValues(buff); 11 | if (char_set.indexOf(String.fromCharCode(buff[0])) > -1 ) return String.fromCharCode(buff[0]); 12 | } 13 | } 14 | } 15 | 16 | function generatePasswd(length){ 17 | 18 | passwd = ""; 19 | 20 | if(length > 50) { length = 50; } 21 | 22 | for(var i = 0; i 1) 12 | { 13 | var tmp = {"folder":results.data[i][1],"name":results.data[i][2],"url":results.data[i][3],"login":results.data[i][4],"passwd":results.data[i][5],"description":results.data[i][6]}; 14 | passwds_list.push(tmp); 15 | } 16 | } 17 | var passwds = {"passwds":passwds_list} 18 | var stringJson = JSON.stringify(passwds); 19 | var req = new XMLHttpRequest(); 20 | req.onreadystatechange = function() { 21 | if (req.readyState == 4) { 22 | alert(req.responseText); 23 | window.location.href = '/passwords'; 24 | } 25 | } 26 | req.open("POST","https://"+window.location.host+"/import",true); 27 | req.setRequestHeader("Content-type", "application/json"); 28 | req.send(stringJson); 29 | } 30 | }); 31 | } 32 | else if(file.type == "text/xml") 33 | { 34 | if(window.DOMParser) 35 | { 36 | var parser = new DOMParser(); 37 | var reader = new FileReader(); 38 | 39 | reader.onload = function(){ 40 | var xmlDoc = parser.parseFromString(this.result,"text/xml"); 41 | 42 | var groups = xmlDoc.getElementsByTagName("group"); 43 | 44 | var passwds_list = []; 45 | 46 | for (var i = 0; i < groups.length; i++) 47 | { 48 | if(groups[i].getElementsByTagName("entry")[0] != undefined) 49 | { 50 | for (var j = 0; j < groups[i].getElementsByTagName("entry").length; j++) { 51 | var dict = {}; 52 | 53 | tmp_folder = groups[i].getElementsByTagName("title")[0].childNodes[0].nodeValue; 54 | 55 | dict["folder"] = tmp_folder; 56 | 57 | if(groups[i].getElementsByTagName("entry")[0].getElementsByTagName("title")[0].childNodes[0] != undefined) 58 | { 59 | tmp_name = groups[i].getElementsByTagName("entry")[0].getElementsByTagName("title")[0].childNodes[0].nodeValue; 60 | dict["name"] = tmp_name; 61 | } 62 | if(groups[i].getElementsByTagName("entry")[0].getElementsByTagName("password")[0].childNodes[0]) 63 | { 64 | tmp_passwd = groups[i].getElementsByTagName("entry")[0].getElementsByTagName("password")[0].childNodes[0].nodeValue; 65 | dict["passwd"] = tmp_passwd; 66 | } 67 | 68 | if(groups[i].getElementsByTagName("entry")[0].getElementsByTagName("username")[0].childNodes[0] != undefined) 69 | { 70 | tmp_login = groups[i].getElementsByTagName("entry")[0].getElementsByTagName("username")[0].childNodes[0].nodeValue; 71 | dict["login"] = tmp_login; 72 | }else{dict["login"] = ""} 73 | if(groups[i].getElementsByTagName("entry")[0].getElementsByTagName("url")[0].childNodes[0] != undefined) 74 | { 75 | tmp_url = groups[i].getElementsByTagName("entry")[0].getElementsByTagName("url")[0].childNodes[0].nodeValue; 76 | dict["url"] = tmp_url; 77 | }else{dict["url"] = ""} 78 | if(groups[i].getElementsByTagName("entry")[0].getElementsByTagName("comment")[0].childNodes[0] != undefined) 79 | { 80 | tmp_description = groups[i].getElementsByTagName("entry")[0].getElementsByTagName("comment")[0].childNodes[0].nodeValue; 81 | dict["description"] = tmp_description; 82 | }else{dict["description"] = ""} 83 | 84 | passwds_list.push(dict); 85 | } 86 | } 87 | } 88 | 89 | var passwds = {"passwds":passwds_list} 90 | var stringJson = JSON.stringify(passwds); 91 | var req = new XMLHttpRequest(); 92 | req.onreadystatechange = function() { 93 | if (req.readyState == 4) { 94 | alert(req.responseText); 95 | window.location.href = '/passwords'; 96 | } 97 | } 98 | req.open("POST","https://"+window.location.host+"/import",true); 99 | req.setRequestHeader("Content-type", "application/json"); 100 | req.send(stringJson); 101 | } 102 | reader.readAsText(file); 103 | } 104 | } 105 | else 106 | { 107 | alert("Tipo de arquivo não reconhecido."); 108 | } 109 | } -------------------------------------------------------------------------------- /ui/static/js/papaparse.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | Papa Parse 3 | v4.1.1 4 | https://github.com/mholt/PapaParse 5 | */ 6 | !function(e){"use strict";function t(t,r){if(r=r||{},r.worker&&w.WORKERS_SUPPORTED){var n=h();return n.userStep=r.step,n.userChunk=r.chunk,n.userComplete=r.complete,n.userError=r.error,r.step=m(r.step),r.chunk=m(r.chunk),r.complete=m(r.complete),r.error=m(r.error),delete r.worker,void n.postMessage({input:t,config:r,workerId:n.id})}var o=null;return"string"==typeof t?o=r.download?new i(r):new a(r):(e.File&&t instanceof File||t instanceof Object)&&(o=new s(r)),o.stream(t)}function r(e,t){function r(){"object"==typeof t&&("string"==typeof t.delimiter&&1==t.delimiter.length&&-1==w.BAD_DELIMITERS.indexOf(t.delimiter)&&(u=t.delimiter),("boolean"==typeof t.quotes||t.quotes instanceof Array)&&(o=t.quotes),"string"==typeof t.newline&&(f=t.newline))}function n(e){if("object"!=typeof e)return[];var t=[];for(var r in e)t.push(r);return t}function i(e,t){var r="";"string"==typeof e&&(e=JSON.parse(e)),"string"==typeof t&&(t=JSON.parse(t));var n=e instanceof Array&&e.length>0,i=!(t[0]instanceof Array);if(n){for(var a=0;a0&&(r+=u),r+=s(e[a],a);t.length>0&&(r+=f)}for(var o=0;od;d++){d>0&&(r+=u);var c=n&&i?e[d]:d;r+=s(t[o][c],d)}o-1||" "==e.charAt(0)||" "==e.charAt(e.length-1);return r?'"'+e+'"':e}function a(e,t){for(var r=0;r-1)return!0;return!1}var o=!1,u=",",f="\r\n";if(r(),"string"==typeof e&&(e=JSON.parse(e)),e instanceof Array){if(!e.length||e[0]instanceof Array)return i(null,e);if("object"==typeof e[0])return i(n(e[0]),e)}else if("object"==typeof e)return"string"==typeof e.data&&(e.data=JSON.parse(e.data)),e.data instanceof Array&&(e.fields||(e.fields=e.data[0]instanceof Array?e.fields:n(e.data[0])),e.data[0]instanceof Array||"object"==typeof e.data[0]||(e.data=[e.data])),i(e.fields||[],e.data||[]);throw"exception: Unable to serialize unrecognized input"}function n(t){function r(e){var t=_(e);t.chunkSize=parseInt(t.chunkSize),this._handle=new o(t),this._handle.streamer=this,this._config=t}this._handle=null,this._paused=!1,this._finished=!1,this._input=null,this._baseIndex=0,this._partialLine="",this._rowCount=0,this._start=0,this._nextChunk=null,this._completeResults={data:[],errors:[],meta:{}},r.call(this,t),this.parseChunk=function(t){var r=this._partialLine+t;this._partialLine="";var n=this._handle.parse(r,this._baseIndex,!this._finished);if(!this._handle.paused()&&!this._handle.aborted()){var i=n.meta.cursor;this._finished||(this._partialLine=r.substring(i-this._baseIndex),this._baseIndex=i),n&&n.data&&(this._rowCount+=n.data.length);var s=this._finished||this._config.preview&&this._rowCount>=this._config.preview;if(k)e.postMessage({results:n,workerId:w.WORKER_ID,finished:s});else if(m(this._config.chunk)){if(this._config.chunk(n,this._handle),this._paused)return;n=void 0,this._completeResults=void 0}return this._config.step||this._config.chunk||(this._completeResults.data=this._completeResults.data.concat(n.data),this._completeResults.errors=this._completeResults.errors.concat(n.errors),this._completeResults.meta=n.meta),!s||!m(this._config.complete)||n&&n.meta.aborted||this._config.complete(this._completeResults),s||n&&n.meta.paused||this._nextChunk(),n}},this._sendError=function(t){m(this._config.error)?this._config.error(t):k&&this._config.error&&e.postMessage({workerId:w.WORKER_ID,error:t,finished:!1})}}function i(e){function t(e){var t=e.getResponseHeader("Content-Range");return parseInt(t.substr(t.lastIndexOf("/")+1))}e=e||{},e.chunkSize||(e.chunkSize=w.RemoteChunkSize),n.call(this,e);var r;this._nextChunk=k?function(){this._readChunk(),this._chunkLoaded()}:function(){this._readChunk()},this.stream=function(e){this._input=e,this._nextChunk()},this._readChunk=function(){if(this._finished)return void this._chunkLoaded();if(r=new XMLHttpRequest,k||(r.onload=g(this._chunkLoaded,this),r.onerror=g(this._chunkError,this)),r.open("GET",this._input,!k),this._config.chunkSize){var e=this._start+this._config.chunkSize-1;r.setRequestHeader("Range","bytes="+this._start+"-"+e),r.setRequestHeader("If-None-Match","webkit-no-cache")}try{r.send()}catch(t){this._chunkError(t.message)}k&&0==r.status?this._chunkError():this._start+=this._config.chunkSize},this._chunkLoaded=function(){if(4==r.readyState){if(r.status<200||r.status>=400)return void this._chunkError();this._finished=!this._config.chunkSize||this._start>t(r),this.parseChunk(r.responseText)}},this._chunkError=function(e){var t=r.statusText||e;this._sendError(t)}}function s(e){e=e||{},e.chunkSize||(e.chunkSize=w.LocalChunkSize),n.call(this,e);var t,r,i="undefined"!=typeof FileReader;this.stream=function(e){this._input=e,r=e.slice||e.webkitSlice||e.mozSlice,i?(t=new FileReader,t.onload=g(this._chunkLoaded,this),t.onerror=g(this._chunkError,this)):t=new FileReaderSync,this._nextChunk()},this._nextChunk=function(){this._finished||this._config.preview&&!(this._rowCount=this._input.size,this.parseChunk(e.target.result)},this._chunkError=function(){this._sendError(t.error)}}function a(e){e=e||{},n.call(this,e);var t,r;this.stream=function(e){return t=e,r=e,this._nextChunk()},this._nextChunk=function(){if(!this._finished){var e=this._config.chunkSize,t=e?r.substr(0,e):r;return r=e?r.substr(e):"",this._finished=!r,this.parseChunk(t)}}}function o(e){function t(){if(b&&c&&(f("Delimiter","UndetectableDelimiter","Unable to auto-detect delimiting character; defaulted to '"+w.DefaultDelimiter+"'"),c=!1),e.skipEmptyLines)for(var t=0;t=y.length?(r.__parsed_extra||(r.__parsed_extra=[]),r.__parsed_extra.push(b.data[t][n])):r[y[n]]=b.data[t][n])}e.header&&(b.data[t]=r,n>y.length?f("FieldMismatch","TooManyFields","Too many fields: expected "+y.length+" fields but parsed "+n,t):n1&&(f+=Math.abs(l-i),i=l):i=l}h/=d.data.length,("undefined"==typeof n||n>f)&&h>1.99&&(n=f,r=o)}return e.delimiter=r,{successful:!!r,bestDelimiter:r}}function a(e){e=e.substr(0,1048576);var t=e.split("\r");if(1==t.length)return"\n";for(var r=0,n=0;n=t.length/2?"\r\n":"\r"}function o(e){var t=l.test(e);return t?parseFloat(e):e}function f(e,t,r,n){b.errors.push({type:e,code:t,message:r,row:n})}var h,d,c,l=/^\s*-?(\d*\.?\d+|\d+\.?\d*)(e[-+]?\d+)?\s*$/i,p=this,g=0,v=!1,k=!1,y=[],b={data:[],errors:[],meta:{}};if(m(e.step)){var R=e.step;e.step=function(n){if(b=n,r())t();else{if(t(),0==b.data.length)return;g+=n.data.length,e.preview&&g>e.preview?d.abort():R(b,p)}}}this.parse=function(r,n,i){if(e.newline||(e.newline=a(r)),c=!1,!e.delimiter){var o=s(r);o.successful?e.delimiter=o.bestDelimiter:(c=!0,e.delimiter=w.DefaultDelimiter),b.meta.delimiter=e.delimiter}var f=_(e);return e.preview&&e.header&&f.preview++,h=r,d=new u(f),b=d.parse(h,n,i),t(),v?{meta:{paused:!0}}:b||{meta:{paused:!1}}},this.paused=function(){return v},this.pause=function(){v=!0,d.abort(),h=h.substr(d.getCharIndex())},this.resume=function(){v=!1,p.streamer.parseChunk(h)},this.aborted=function(){return k},this.abort=function(){k=!0,d.abort(),b.meta.aborted=!0,m(e.complete)&&e.complete(b),h=""}}function u(e){e=e||{};var t=e.delimiter,r=e.newline,n=e.comments,i=e.step,s=e.preview,a=e.fastMode;if(("string"!=typeof t||w.BAD_DELIMITERS.indexOf(t)>-1)&&(t=","),n===t)throw"Comment character same as delimiter";n===!0?n="#":("string"!=typeof n||w.BAD_DELIMITERS.indexOf(n)>-1)&&(n=!1),"\n"!=r&&"\r"!=r&&"\r\n"!=r&&(r="\n");var o=0,u=!1;this.parse=function(e,f,h){function d(e){b.push(e),S=o}function c(t){return h?p():(t||(t=e.substr(o)),w.push(t),o=g,d(w),y&&_(),p())}function l(t){o=t,d(w),w=[],O=e.indexOf(r,o)}function p(e){return{data:b,errors:R,meta:{delimiter:t,linebreak:r,aborted:u,truncated:!!e,cursor:S+(f||0)}}}function _(){i(p()),b=[],R=[]}if("string"!=typeof e)throw"Input must be a string";var g=e.length,m=t.length,v=r.length,k=n.length,y="function"==typeof i;o=0;var b=[],R=[],w=[],S=0;if(!e)return p();if(a||a!==!1&&-1===e.indexOf('"')){for(var E=e.split(r),C=0;C=s)return b=b.slice(0,s),p(!0)}}return p()}for(var x=e.indexOf(t,o),O=e.indexOf(r,o);;)if('"'!=e[o])if(n&&0===w.length&&e.substr(o,k)===n){if(-1==O)return p();o=O+v,O=e.indexOf(r,o),x=e.indexOf(t,o)}else if(-1!==x&&(O>x||-1===O))w.push(e.substring(o,x)),o=x+m,x=e.indexOf(t,o);else{if(-1===O)break;if(w.push(e.substring(o,O)),l(O+v),y&&(_(),u))return p();if(s&&b.length>=s)return p(!0)}else{var I=o;for(o++;;){var I=e.indexOf('"',I+1);if(-1===I)return h||R.push({type:"Quotes",code:"MissingQuotes",message:"Quoted field unterminated",row:b.length,index:o}),c();if(I===g-1){var D=e.substring(o,I).replace(/""/g,'"');return c(D)}if('"'!=e[I+1]){if(e[I+1]==t){w.push(e.substring(o,I).replace(/""/g,'"')),o=I+1+m,x=e.indexOf(t,o),O=e.indexOf(r,o);break}if(e.substr(I+1,v)===r){if(w.push(e.substring(o,I).replace(/""/g,'"')),l(I+1+v),x=e.indexOf(t,o),y&&(_(),u))return p();if(s&&b.length>=s)return p(!0);break}}else I++}}return c()},this.abort=function(){u=!0},this.getCharIndex=function(){return o}}function f(){var e=document.getElementsByTagName("script");return e.length?e[e.length-1].src:""}function h(){if(!w.WORKERS_SUPPORTED)return!1;if(!y&&null===w.SCRIPT_PATH)throw new Error("Script path cannot be determined automatically when Papa Parse is loaded asynchronously. You need to set Papa.SCRIPT_PATH manually.");var t=new e.Worker(w.SCRIPT_PATH||v);return t.onmessage=d,t.id=R++,b[t.id]=t,t}function d(e){var t=e.data,r=b[t.workerId],n=!1;if(t.error)r.userError(t.error,t.file);else if(t.results&&t.results.data){var i=function(){n=!0,c(t.workerId,{data:[],errors:[],meta:{aborted:!0}})},s={abort:i,pause:l,resume:l};if(m(r.userStep)){for(var a=0;a/gm;var U=function(e){Y("beforeSanitizeElements",e,null);if(W(e)){B(e);return true}var t=e.nodeName.toLowerCase();Y("uponSanitizeElement",e,{tagName:t});if(!T[t]||w[t]){if(z&&!R[t]&&typeof e.insertAdjacentHTML==="function"){try{e.insertAdjacentHTML("AfterEnd",e.innerHTML)}catch(r){}}B(e);return true}if(D&&!e.firstElementChild&&(!e.content||!e.content.firstElementChild)){e.innerHTML=e.textContent.replace(/i){e.setAttribute("id",f.value)}}else{if(l==="id"){e.setAttribute(l,"")}e.removeAttribute(l)}if(!a.keepAttr){continue}if(C&&(c==="id"||c==="name")&&(s in t||s in n||s in H)){continue}if((x[c]&&!E[c]||!O&&M&&V.test(c))&&(!J.test(s.replace(K,""))||c==="src"&&s.indexOf("data:")===0&&e.nodeName==="IMG")){try{if(O){s=s.replace(q," ");s=s.replace(P," ")}e.setAttribute(l,s)}catch(u){}}}Y("afterSanitizeAttributes",e,null)};var X=function(e){var t;var r=j(e);Y("beforeSanitizeShadowDOM",e,null);while(t=r.nextNode()){Y("uponSanitizeShadowNode",t,null);if(U(t)){continue}if(t.content instanceof i){X(t.content)}Q(t)}Y("afterSanitizeShadowDOM",e,null)};var Y=function(e,t,n){if(!g[e]){return}g[e].forEach(function(e){e.call(r,t,n,F)})};r.sanitize=function(e,n){if(!e){e=""}if(typeof e!=="string"){e=e.toString()}if(!r.isSupported){if(typeof t.toStaticHTML==="object"||typeof t.toStaticHTML==="function"){return t.toStaticHTML(e)}return e}I(n);if(!N&&!S&&e.indexOf("<")===-1){return e}var o=G(e);if(!o){return N?null:""}var l;var s;var c=j(o);while(l=c.nextNode()){if(l.nodeType===3&&l===s){continue}if(U(l)){continue}if(l.content instanceof i){X(l.content)}Q(l);s=l}var f;if(N){if(L){f=v.call(o.ownerDocument);while(o.firstChild){f.appendChild(o.firstChild)}}else{f=o}if(_){f=h.call(a,f,true)}return f}return S?o.outerHTML:o.innerHTML};r.addHook=function(e,t){if(typeof t!=="function"){return}g[e]=g[e]||[];g[e].push(t)};r.removeHook=function(e){if(g[e]){g[e].pop()}};r.removeHooks=function(e){if(g[e]){g[e]=[]}};r.removeAllHooks=function(){g=[]};return r}); 2 | //# sourceMappingURL=./dist/purify.min.js.map -------------------------------------------------------------------------------- /ui/static/js/showPass.js: -------------------------------------------------------------------------------- 1 | function showPassword(id) { 2 | var e = document.getElementById(id); 3 | 4 | var pki = forge.pki; 5 | 6 | try{ 7 | privateKey = pki.privateKeyFromPem(sessionStorage.pk); 8 | } 9 | catch(err){ 10 | 11 | } 12 | 13 | var password = e.getAttribute("name"); 14 | var url = document.getElementById(id.replace("passwd","url")).value; 15 | var description = document.getElementById(id.replace("passwd","description")).value; 16 | var login = document.getElementById(id.replace("passwd","login")).value; 17 | 18 | try{ 19 | var uncrypted = privateKey.decrypt(forge.util.decode64(password),'RSA-OAEP', { 20 | md: forge.md.sha1.create(), mgf1: { md: forge.md.sha1.create() } }); 21 | } 22 | catch(err){ 23 | alert("Failed to decrypt password, inform your private key again."); 24 | window.location.href = '/privkey'; 25 | } 26 | 27 | var elem = document.getElementById(id.replace("passwd","senha")); 28 | elem.type = "password" 29 | elem.value = uncrypted; 30 | 31 | if (url.length == 684) 32 | { 33 | var url_uncrypted = privateKey.decrypt(forge.util.decode64(url),'RSA-OAEP', { 34 | md: forge.md.sha1.create(), mgf1: { md: forge.md.sha1.create() } }); 35 | 36 | var elem = document.getElementById(id.replace("passwd","url")); 37 | elem.value = DOMPurify.sanitize(url_uncrypted); 38 | } 39 | if (description.length == 684) 40 | { 41 | var description_uncrypted = privateKey.decrypt(forge.util.decode64(description),'RSA-OAEP', { 42 | md: forge.md.sha1.create(), mgf1: { md: forge.md.sha1.create() } }); 43 | 44 | var elem = document.getElementById(id.replace("passwd","description")); 45 | elem.value = DOMPurify.sanitize(description_uncrypted); 46 | } 47 | if (login.length == 684) 48 | { 49 | var login_uncrypted = privateKey.decrypt(forge.util.decode64(login),'RSA-OAEP', { 50 | md: forge.md.sha1.create(), mgf1: { md: forge.md.sha1.create() } }); 51 | 52 | var elem = document.getElementById(id.replace("passwd","login")); 53 | elem.value = DOMPurify.sanitize(login_uncrypted); 54 | } 55 | 56 | var x = document.getElementsByClassName("panel panel-primary"); 57 | var i; 58 | for (i = 0; i < x.length; i++) { 59 | if(x[i] == e) 60 | { 61 | if(e.style.display == "block") 62 | { 63 | x[i].style.display = 'none'; 64 | } 65 | else 66 | { 67 | x[i].style.display = 'block'; 68 | } 69 | } 70 | else 71 | { 72 | x[i].style.display = 'none'; 73 | } 74 | } 75 | } 76 | 77 | 78 | function hideShowPass(id){ 79 | var elem = document.getElementById(id.replace("button","senha")); 80 | 81 | var type = elem.type; 82 | 83 | if(type == "password") 84 | { 85 | elem.type = "text"; 86 | elem.select(); 87 | } 88 | else 89 | { 90 | elem.type = "password"; 91 | } 92 | 93 | } -------------------------------------------------------------------------------- /ui/static/js/unlock.js: -------------------------------------------------------------------------------- 1 | function processLargeArray(passwds) { 2 | // set this to whatever number of items you can process at once 3 | var pki = forge.pki; 4 | privateKey = pki.privateKeyFromPem(sessionStorage.pk); 5 | 6 | publicKey = pki.publicKeyFromPem(document.getElementById('key').value) 7 | var progressBar = document.getElementById('progress'); 8 | 9 | barLength = passwds.length 10 | 11 | var chunk = 1; 12 | var index = 0; 13 | 14 | function doChunk() { 15 | var cnt = chunk; 16 | while (cnt-- && index < passwds.length) { 17 | var i2 = index+1 18 | percent = i2/barLength * 100; 19 | percent_s = percent.toString(); 20 | progressBar.style.width = percent_s.split(".")[0] + "%"; 21 | progressBar.innerHTML = percent_s.split(".")[0] + "%"; 22 | progressBar.setAttribute("aria-valuenow",percent_s.split(".")[0]+"%"); 23 | 24 | try 25 | { 26 | passwd_tmp = privateKey.decrypt(forge.util.decode64(passwds[index]["passwd"]),'RSA-OAEP', { 27 | md: forge.md.sha1.create(), mgf1: { md: forge.md.sha1.create() } }); 28 | 29 | description_tmp = privateKey.decrypt(forge.util.decode64(passwds[index]["description"]),'RSA-OAEP', { 30 | md: forge.md.sha1.create(), mgf1: { md: forge.md.sha1.create() } }); 31 | 32 | url_tmp = privateKey.decrypt(forge.util.decode64(passwds[index]["url"]),'RSA-OAEP', { 33 | md: forge.md.sha1.create(), mgf1: { md: forge.md.sha1.create() } }); 34 | 35 | login_tmp = privateKey.decrypt(forge.util.decode64(passwds[index]["login"]),'RSA-OAEP', { 36 | md: forge.md.sha1.create(), mgf1: { md: forge.md.sha1.create() } }); 37 | } 38 | catch(err){ 39 | alert("Failed to decrypt password, inform your private key and try to unlock again."); 40 | window.location.href = '/privkey'; 41 | } 42 | 43 | passwds[index]["passwd"] = forge.util.encode64(publicKey.encrypt(passwd_tmp, "RSA-OAEP", { md: forge.md.sha1.create(), 44 | mgf1: { md: forge.md.sha1.create() } })); 45 | 46 | passwds[index]["description"] = forge.util.encode64(publicKey.encrypt(description_tmp,"RSA-OAEP", { md: forge.md.sha1.create(), 47 | mgf1: { md: forge.md.sha1.create() } })); 48 | 49 | passwds[index]["url"] = forge.util.encode64(publicKey.encrypt(url_tmp,"RSA-OAEP", { md: forge.md.sha1.create(), 50 | mgf1: { md: forge.md.sha1.create() } })); 51 | 52 | passwds[index]["login"] = forge.util.encode64(publicKey.encrypt(login_tmp,"RSA-OAEP", { md: forge.md.sha1.create(), 53 | mgf1: { md: forge.md.sha1.create() } })); 54 | 55 | if (atob(passwds[index]["passwd"]).length != 512) 56 | { 57 | passwds[index]["passwd"] = publicKey.encrypt(passwd_tmp,"RSA-OAEP"); 58 | } 59 | if (atob(passwds[index]["description"]).length != 512) 60 | { 61 | passwds[index]["description"] = publicKey.encrypt(description_tmp,"RSA-OAEP"); 62 | } 63 | if (atob(passwds[index]["url"]).length != 512) 64 | { 65 | passwds[index]["url"] = publicKey.encrypt(url_tmp,"RSA-OAEP"); 66 | } 67 | if (atob(passwds[index]["login"]).length != 512) 68 | { 69 | passwds[index]["login"] = publicKey.encrypt(login_tmp,"RSA-OAEP"); 70 | } 71 | 72 | if (passwds[index]["passwd"].length != 684 || passwds[index]["description"].length != 684 || passwds[index]["url"].length != 684 || passwds[index]["login"].length != 684) 73 | { 74 | alert("Something went wrong while unlocking, please try again."); 75 | window.location.href = '/unlock'; 76 | } 77 | ++index; 78 | } 79 | if (index < passwds.length) { 80 | setTimeout(doChunk, 2); 81 | } 82 | if (index == passwds.length ) { callback(passwds); }; 83 | } 84 | doChunk(); 85 | } 86 | 87 | function unlock(passwds,token,user,group){ 88 | 89 | passwds = passwds.replace(/\'/g,"\"") 90 | passwds = JSON.parse(passwds) 91 | 92 | processLargeArray(passwds); 93 | 94 | } 95 | 96 | function callback(passwds){ 97 | jSON = {"passwords":passwds,"token":token,"usertounlock":user,"group":group}; 98 | sendJson(jSON); 99 | } 100 | 101 | function sendJson (json) { 102 | var stringJson = JSON.stringify(json); 103 | var req = new XMLHttpRequest(); 104 | 105 | req.onreadystatechange = function() { 106 | if (req.readyState == 4) { 107 | if (req.responseText == "error") { window.location.href = "/unlock"; }; 108 | window.location.href = "/passwords"; 109 | } 110 | } 111 | req.open("PUT","https://"+window.location.host+"/unlock",true); 112 | 113 | req.setRequestHeader("Content-type", "application/json"); 114 | req.send(stringJson); 115 | } -------------------------------------------------------------------------------- /ui/static/js/updatePass.js: -------------------------------------------------------------------------------- 1 | function getIdPasswd(id) { 2 | 3 | var elem = document.getElementById("passwd"+id); 4 | 5 | var name = document.getElementById("name"+id).getAttribute("passwdname") 6 | var url = elem.getElementsByClassName('form-group')[1].getElementsByClassName('form-control')[0].value; 7 | var login = elem.getElementsByClassName('form-group')[2].getElementsByClassName('form-control')[0].value; 8 | var description = elem.getElementsByClassName('form-group')[3].getElementsByClassName('form-control')[0].childNodes[0].nodeValue; 9 | 10 | var jSON = {"name":name,"id":id,"url":url,"login":login,"description":description}; 11 | var stringJson = JSON.stringify(jSON); 12 | 13 | sessionStorage.id_passwd = stringJson; 14 | 15 | window.location.href = '/update/password'; 16 | } 17 | 18 | function setIdPasswd() { 19 | 20 | var elem = JSON.parse(sessionStorage.id_passwd); 21 | 22 | var pki = forge.pki; 23 | 24 | privateKey = pki.privateKeyFromPem(sessionStorage.pk); 25 | 26 | document.getElementById('id_passwd').value = elem["id"]; 27 | document.getElementById('url').placeholder = elem["url"]; 28 | document.getElementById('login').placeholder = elem["login"]; 29 | document.getElementById('description').placeholder = DOMPurify.sanitize(privateKey.decrypt(forge.util.decode64(elem["description"]),'RSA-OAEP', { 30 | md: forge.md.sha1.create(), mgf1: { md: forge.md.sha1.create() } })); 31 | document.getElementById('name').placeholder = elem["name"]; 32 | document.getElementById('passwd').placeholder = "**********"; 33 | document.getElementById('passwd2').placeholder = "**********"; 34 | 35 | 36 | } -------------------------------------------------------------------------------- /ui/templates/addfolder.html: -------------------------------------------------------------------------------- 1 | {% extends "base2.html" %} 2 | {% import "bootstrap/wtf.html" as wtf %} 3 | 4 | {% block title %}GSenha{% endblock %} 5 | 6 | {% block page_content %} 7 | 10 | 11 | {{ wtf.quick_form(form,form_type="horizontal", horizontal_columns=('lg', 5, 3)) }} 12 | {% endblock %} -------------------------------------------------------------------------------- /ui/templates/addpassword.html: -------------------------------------------------------------------------------- 1 | {% extends "base2.html" %} 2 | {% import "bootstrap/wtf.html" as wtf %} 3 | 4 | {% block title %}GSenha{% endblock %} 5 | 6 | {% block page_content %} 7 | 10 | 11 | {{ wtf.quick_form(form,form_type="horizontal", horizontal_columns=('lg', 5, 4)) }} 12 | 13 | 100 | 101 | 102 | 103 | 106 | 107 | {% endblock %} 108 | -------------------------------------------------------------------------------- /ui/templates/adduser.html: -------------------------------------------------------------------------------- 1 | {% extends "base3.html" %} 2 | {% import "bootstrap/wtf.html" as wtf %} 3 | 4 | {% block title %}GSenha{% endblock %} 5 | 6 | {% block page_content %} 7 | 10 | 11 |
12 | 13 | {{ form.hidden_tag() }} 14 | 15 |
16 | 17 |
18 | 19 |
20 |
21 |
22 | 23 |
24 | 25 |
26 |
27 |
28 | ? 29 | 30 |
31 | 32 |
33 |
34 | 35 |
36 | 37 |
38 | 39 |
40 |
41 | 42 |
43 |
44 |
45 | 46 | 47 | 48 | 49 | {% endblock %} -------------------------------------------------------------------------------- /ui/templates/base.html: -------------------------------------------------------------------------------- 1 | {% extends "bootstrap/base.html" %} 2 | 3 | {% block title %}GSenha{% endblock %} 4 | {% block navbar %} 5 | 6 | 7 | 34 | {% endblock %} 35 | 36 | {% block content %} 37 |
38 | {% with messages = get_flashed_messages(with_categories=true) %} 39 | {% if messages %} 40 | {% for category, message in messages %} 41 | {% set class = "alert alert-" + category|string %} 42 |
43 | 44 | {{ message }} 45 |
46 | {% endfor %} 47 | {% endif %} 48 | {% endwith %} 49 | {% block page_content %}{% endblock %} 50 |
51 | {% endblock %} 52 | -------------------------------------------------------------------------------- /ui/templates/base2.html: -------------------------------------------------------------------------------- 1 | {% extends "bootstrap/base.html" %} 2 | 3 | {% block title %}GSenha{% endblock %} 4 | 5 | {% block navbar %} 6 | 7 | 8 | 55 | {% endblock %} 56 | 57 | {% block content %} 58 |
59 | {% with messages = get_flashed_messages(with_categories=true) %} 60 | {% if messages %} 61 | {% for category, message in messages %} 62 | {% set class = "alert alert-" + category|string %} 63 |
64 | 65 | {{ message }} 66 |
67 | {% endfor %} 68 | {% endif %} 69 | {% endwith %} 70 | {% block page_content %}{% endblock %} 71 |
72 | {% endblock %} -------------------------------------------------------------------------------- /ui/templates/base3.html: -------------------------------------------------------------------------------- 1 | {% extends "bootstrap/base.html" %} 2 | 3 | {% block title %}GSenha{% endblock %} 4 | 5 | {% block navbar %} 6 | 7 | 8 | 32 | {% endblock %} 33 | 34 | {% block content %} 35 |
36 | {% with messages = get_flashed_messages(with_categories=true) %} 37 | {% if messages %} 38 | {% for category, message in messages %} 39 | {% set class = "alert alert-" + category|string %} 40 |
41 | 42 | {{ message }} 43 |
44 | {% endfor %} 45 | {% endif %} 46 | {% endwith %} 47 | {% block page_content %}{% endblock %} 48 |
49 | {% endblock %} 50 | -------------------------------------------------------------------------------- /ui/templates/base4.html: -------------------------------------------------------------------------------- 1 | {% extends "bootstrap/base.html" %} 2 | 3 | {% block title %}GSenha{% endblock %} 4 | 5 | {% block navbar %} 6 | 7 | 8 | 58 | {% endblock %} 59 | 60 | {% block content %} 61 |
62 | {% with messages = get_flashed_messages(with_categories=true) %} 63 | {% if messages %} 64 | {% for category, message in messages %} 65 | {% set class = "alert alert-" + category|string %} 66 |
67 | 68 | {{ message }} 69 |
70 | {% endfor %} 71 | {% endif %} 72 | {% endwith %} 73 | {% block page_content %}{% endblock %} 74 |
75 | {% endblock %} 76 | -------------------------------------------------------------------------------- /ui/templates/deletepasswd.html: -------------------------------------------------------------------------------- 1 | {% extends "base2.html" %} 2 | {% import "bootstrap/wtf.html" as wtf %} 3 | 4 | {% block title %}GSenha{% endblock %} 5 | 6 | {% block page_content %} 7 | 10 | 11 | {{ wtf.quick_form(form,form_type="horizontal", horizontal_columns=('lg', 5, 4)) }} 12 | {% endblock %} -------------------------------------------------------------------------------- /ui/templates/delfolder.html: -------------------------------------------------------------------------------- 1 | {% extends "base2.html" %} 2 | {% import "bootstrap/wtf.html" as wtf %} 3 | 4 | {% block title %}GSenha{% endblock %} 5 | 6 | {% block page_content %} 7 | 10 | 11 | {{ wtf.quick_form(form,form_type="horizontal", horizontal_columns=('lg', 5, 3)) }} 12 | {% endblock %} -------------------------------------------------------------------------------- /ui/templates/docs.html: -------------------------------------------------------------------------------- 1 | {% extends "bootstrap/base.html" %} 2 | {% block title %}GSenha{% endblock %} 3 | {% block styles %} 4 | {{super()}} 5 | 6 | {% endblock %} 7 | 8 | {% block navbar %} 9 | 10 | 31 |
32 |
33 |
34 |

Welcome to GSenha

35 |

GSenha is the front-end for GSenha-API.

36 |

The goal is to make it easy and secure to store and share passwords in a corporate environment.

37 |
38 |
39 | 40 |
41 |
42 |

Architecture

43 |
44 |
45 |

GSenha-API is a password manager, but not an usual one. Its architecture was designed to avoid information leakage in the case of a compromise. It is possible to store a password and share it among a group of users in a secure way, and also store a personal password, just for you. Storing a personal password is just like using another well-konw password manager like KeePass, PasswordSafe, Password Gorilla and others. The goal in GSenha is to be able to store a password and allow other people to have access to it in a secure way, without backdoors and no shared secret keys. This is done with asymmetric cryptography (private and public keys).

46 | 47 |
48 |
49 | 50 |
51 |
52 |

GSenha usage

53 |
54 |
55 |

In order to start using GSenha you have to add yourself. It is necessary a RSA key pair, to generate it just follow the instructions below. The key pair length must be 4096 bits.

56 |
57 |
58 | 59 |
60 |
61 |

Key generation

62 |
63 |
64 |

Generating private key:

65 | $ openssl genrsa -out privkey.pem 4096 66 |

67 |

The generated file is your private key, its name will be "privkey.pem". Keep it secret!

68 |

The file should begins with "-----BEGIN RSA PRIVATE KEY-----" and ends with "-----END RSA PRIVATE KEY-----".

69 |

Generating public key:

70 | $ openssl rsa -in privkey.pem -outform PEM -pubout -out public.pem 71 |

72 |

The generated file is your public key, its name will be "public.pem".

73 |

The file should begins with "-----BEGIN PUBLIC KEY-----" and ends with "-----END PUBLIC KEY-----".

74 |

This one will be informed when adding yourserlf.

75 |
76 |
77 | 78 |
79 |
80 |

Add a user

81 |
82 |
83 |

A new user should go to "Add User" on the navbar and inform his/her LDAP credentials and his/her public key.

84 |

After that him/her can login. The private key will never leave your browser.

85 |
86 |
87 | 88 |
89 |
90 |

Unlock a new user

91 |
92 |
93 |

If you are the first member of a group it will not be necessary any kind of unlock. In case there are already passwords added to a group you are member of, a new user will not be able to see them.

94 |

In this case an unlock is necessary. To do this an older user (that can see the passwords) must go to "Unlock User" on the navbar, inform the username and the group. After that the new user will be able to see all passwords.

95 |
96 |
97 | 98 |
99 |
100 |

Private key fallback

101 |
102 |
103 |

The system is not able to retrieve your private key in case of loss. It is the user obligation to keep his/her private key safe. DO NOT LOSE YOUR PRIVATE KEY!

104 |
105 |
106 |
107 | {% endblock %} -------------------------------------------------------------------------------- /ui/templates/error.html: -------------------------------------------------------------------------------- 1 | {% extends "base2.html" %} 2 | {% import "bootstrap/wtf.html" as wtf %} 3 | 4 | {% block title %}GSenha{% endblock %} -------------------------------------------------------------------------------- /ui/templates/import.html: -------------------------------------------------------------------------------- 1 | {% extends "base2.html" %} 2 | 3 | {% block page_content %} 4 | 7 | 8 |
9 |
10 | 11 | 12 |
13 |
14 |

Supported Files

15 |
16 |
17 |

Files generated from Password Safe / Password Gorilla export (.csv) and from KeePassX (.xml)

18 |

Or .csv files with the following structure: uuid,group,title,url,user,password,notes 19 |

Where, 20 |

uuid: password id from the password manager, it will not be used.

21 |

group: it will be a folder, if it does not exist it will be created.

22 |

tittle: it will be the password name.

23 |

url: same meaning

24 |

user: it will be the login

25 |

password: same meaning

26 |

notes: it will be the description

27 |

All passwords will be imported to your personal folder.

28 |
29 |
30 | 31 | 32 |
33 |
34 | 35 | 36 |
37 | 38 |
39 | 40 |
41 |
42 |
43 | 44 |
45 |
46 |
47 |
48 | 49 |
50 | 51 | 52 | 53 | 54 | {% endblock %} -------------------------------------------------------------------------------- /ui/templates/index.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | {% import "bootstrap/wtf.html" as wtf %} 3 | 4 | {% block title %}GSenha{% endblock %} 5 | 6 | {% block page_content %} 7 | 10 | 11 | {{ wtf.quick_form(form) }} 12 | {% endblock %} -------------------------------------------------------------------------------- /ui/templates/login.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block page_content %} 4 | 7 | 8 |
9 | 10 | {{ form.hidden_tag() }} 11 | 12 |
13 | 14 |
15 | 16 |
17 |
18 |
19 | 20 |
21 | 22 |
23 |
24 |
25 | ? 26 | 27 |
28 | 29 |
30 |
31 | 32 |
33 | 34 |
35 | 36 |
37 |
38 | 39 |
40 |
41 |
42 | 43 | 44 | 45 | 46 | {% endblock %} -------------------------------------------------------------------------------- /ui/templates/passwords.html: -------------------------------------------------------------------------------- 1 | {% extends "base4.html" %} 2 | {% import "bootstrap/wtf.html" as wtf %} 3 | 4 | {% block title %}GSenha{% endblock %} 5 | 6 | 7 | {% block page_content %} 8 | 9 | 10 | 13 | 14 |
15 | 16 | 17 |
18 | 21 | 22 | 23 |
24 |
    25 |
  • Personal Passwords 26 | {% for folder in tree['Folders'][0]['Personal Folders'] %} 27 | {% set tmp = folder['name'].split('/') %} 28 | {% set folder_name = tmp[(tmp|length)-1] %} 29 |
      30 |
    • {{ folder_name }} 31 | {% for password in passwd['Personal Passwords'][folder['name']] %} 32 |
        33 | 34 | {% set showid = "passwd" + password['ID']|string %} 35 | {% set id = password['ID'] %} 36 | {% set idName = "name" + password['ID']|string %} 37 | {% set encrypted_passwd = password['password'] %} 38 | 39 |
      • 40 | {{ password['name'] }} 41 |
      • 42 |
      43 | {% endfor %} 44 | {% if folder['children'] is defined %} 45 | {% for children_folder in folder['children'] %} 46 | {% set tmp2 = children_folder['name'].split('/') %} 47 | {% set folder_name2 = tmp2[(tmp2|length)-1] %} 48 |
        49 |
      • {{ folder_name2 }} 50 | {% for password in passwd['Personal Passwords'][children_folder['name']] %} 51 |
          52 | {% set showid = "passwd" + password['ID']|string %} 53 | {% set id = password['ID'] %} 54 | {% set idName = "name" + password['ID']|string %} 55 | {% set encrypted_passwd = password['password'] %} 56 | 57 |
        • 58 | {{ password['name'] }} 59 |
        • 60 |
        61 | {% endfor %} 62 | 63 |
      64 | {% endfor %} 65 | {% endif %} 66 | 67 |
    • 68 |
    69 | {% endfor %} 70 |
  • 71 |
72 | 73 |
    74 |
  • Shared Passwords 75 | {% for group in tree['Folders'][1]['Group Folders'] %} 76 | {% for folder in group %} 77 | {% set tmp = folder['name'].split('/') %} 78 | {% set folder_name = tmp[(tmp|length)-1] %} 79 |
      80 |
    • {{ folder_name }} 81 | {% for password in passwd['Shared Passwords'][folder['name']] %} 82 |
        83 | 84 | {% set showid = "passwd" + password['ID']|string %} 85 | {% set id = password['ID'] %} 86 | {% set idName = "name" + password['ID']|string %} 87 | {% set encrypted_passwd = password['password'] %} 88 | 89 |
      • 90 | {{ password['name'] }} 91 |
      • 92 |
      93 | {% endfor %} 94 | {% if folder['children'] is defined %} 95 | {% for children_folder in folder['children'] %} 96 | {% set tmp2 = children_folder['name'].split('/') %} 97 | {% set folder_name2 = tmp2[(tmp2|length)-1] %} 98 |
        99 |
      • {{ folder_name2 }} 100 | {% for password in passwd['Shared Passwords'][children_folder['name']] %} 101 |
          102 | {% set showid = "passwd" + password['ID']|string %} 103 | {% set id = password['ID'] %} 104 | {% set idName = "name" + password['ID']|string %} 105 | {% set encrypted_passwd = password['password'] %} 106 | 107 |
        • 108 | {{ password['name'] }} 109 |
        • 110 |
        111 | {% endfor %} 112 | 113 |
      114 | {% endfor %} 115 | {% endif %} 116 | 117 |
    • 118 |
    119 | {% endfor %} 120 | {% endfor %} 121 |
  • 122 |
123 |
124 |
125 | 126 | 127 |
128 | 129 |
130 | 131 | {% for folder in folders['Personal Folders'] %} 132 | {% for password in passwd['Personal Passwords'][folder] %} 133 | {% set showid = "passwd" + password['ID']|string %} 134 | {% set id = password['ID'] %} 135 | {% set encrypted_passwd = password['password'] %} 136 | 137 | 183 | {% endfor %} 184 | {% endfor %} 185 |
186 |
187 | 188 | {% for folder in folders['Group Folders'] %} 189 | {% for password in passwd['Shared Passwords'][folder] %} 190 | {% set showid = "passwd" + password['ID']|string %} 191 | {% set id = password['ID'] %} 192 | {% set encrypted_passwd = password['password'] %} 193 | 194 | 240 | {% endfor %} 241 | {% endfor %} 242 |
243 |
244 | 245 | 246 |
247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 275 | 276 | 277 | 278 | {% endblock %} -------------------------------------------------------------------------------- /ui/templates/plugin_chrome.html: -------------------------------------------------------------------------------- 1 | {% extends "bootstrap/base.html" %} 2 | {% block title %}GSenha{% endblock %} 3 | {% block styles %} 4 | {{super()}} 5 | 6 | {% endblock %} 7 | 8 | {% block navbar %} 9 | 10 | 30 | 31 |
32 |
33 |
34 |

Browser Plugin

35 |

Já está disponivel o plugin para Chrome.

36 |

37 | Download 38 |

39 |
40 |
41 |
42 | 43 | {% endblock %} -------------------------------------------------------------------------------- /ui/templates/privkey.html: -------------------------------------------------------------------------------- 1 | {% extends "base2.html" %} 2 | {% import "bootstrap/wtf.html" as wtf %} 3 | 4 | {% block title %}GSenha{% endblock %} 5 | 6 | {% block page_content %} 7 | 10 | 11 |
12 |
13 | 14 |
15 | 16 |
17 |
18 | 19 |
20 | 21 |
22 | 23 |
24 |
25 | 26 |
27 |
28 |
29 | 30 | 31 | {% endblock %} -------------------------------------------------------------------------------- /ui/templates/unlock.html: -------------------------------------------------------------------------------- 1 | {% extends "base2.html" %} 2 | {% import "bootstrap/wtf.html" as wtf %} 3 | 4 | {% block title %}GSenha{% endblock %} 5 | 6 | {% block page_content %} 7 | 10 | {{ wtf.quick_form(form,form_type="horizontal", horizontal_columns=('lg', 5, 3)) }} 11 | 12 | {% endblock %} -------------------------------------------------------------------------------- /ui/templates/unlocking.html: -------------------------------------------------------------------------------- 1 | {% extends "base2.html" %} 2 | {% import "bootstrap/wtf.html" as wtf %} 3 | 4 | {% block title %}GSenha{% endblock %} 5 | 6 | {% block page_content %} 7 | 10 | 11 |
12 | 13 |
14 |
15 |

Unlocking a user

16 |
17 |
18 |

All passwords from the group are being decrypted and encrypted with the server public key. When done all passwords will be send to server, decrypted with its pirvate key and encrypted with the new user public key. In the end the server's key will be deleted.

19 |

In this way the server will never know any private key, and yours will never leave your browser.

20 |
21 |
22 | 23 |
24 |
26 | 0% 27 |
28 |
29 | 30 | 31 | 32 |
33 |
34 | 35 |
36 |
37 | 38 |
39 |
40 | 41 |
42 |
43 | 44 | 45 | 46 | 47 | 55 | 56 | {% endblock %} -------------------------------------------------------------------------------- /ui/templates/updatepass.html: -------------------------------------------------------------------------------- 1 | {% extends "base2.html" %} 2 | {% import "bootstrap/wtf.html" as wtf %} 3 | 4 | {% block title %}GSenha{% endblock %} 5 | 6 | {% block page_content %} 7 | 11 | 12 | {{ wtf.quick_form(form,form_type="horizontal", horizontal_columns=('lg', 5, 3)) }} 13 | 14 | 15 | 16 | 17 | 20 | 21 | {% endblock %} -------------------------------------------------------------------------------- /ui/templates/updatepk.html: -------------------------------------------------------------------------------- 1 | {% extends "base2.html" %} 2 | {% import "bootstrap/wtf.html" as wtf %} 3 | 4 | {% block title %}GSenha{% endblock %} 5 | 6 | {% block page_content %} 7 | 10 | 11 | {{ wtf.quick_form(form,form_type="horizontal", horizontal_columns=('lg', 5, 3)) }} 12 | {% endblock %} --------------------------------------------------------------------------------