Skip to content
GitLab
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
bwinf
medal
Commits
928308cc
Commit
928308cc
authored
Apr 04, 2019
by
Daniel Brüning
Browse files
romoved all warnings
parent
1fb52bff
Pipeline
#57
passed with stage
in 3 minutes and 53 seconds
Changes
4
Pipelines
1
Hide whitespace changes
Inline
Side-by-side
src/db_conn_sqlite.rs
View file @
928308cc
...
...
@@ -14,7 +14,7 @@ use self::time::Duration;
use
std
::
path
::{
Path
};
use
self
::
bcrypt
::{
DEFAULT_COST
,
hash
,
verify
,
BcryptError
};
use
self
::
bcrypt
::{
verify
};
use
::
functions
;
// todo: remove (usertype in db)
...
...
@@ -190,7 +190,8 @@ impl MedalConnection for Connection {
}
}
fn
login
(
&
self
,
session
:
Option
<&
str
>
,
username
:
&
str
,
password
:
&
str
)
->
Result
<
String
,()
>
{
//TODO: use session
fn
login
(
&
self
,
_session
:
Option
<&
str
>
,
username
:
&
str
,
password
:
&
str
)
->
Result
<
String
,()
>
{
match
self
.query_row
(
"SELECT id, password, salt FROM session_user WHERE username = ?1"
,
&
[
&
username
],
...
...
@@ -215,7 +216,9 @@ impl MedalConnection for Connection {
_
=>
{
Err
(())
}
}
}
fn
login_with_code
(
&
self
,
session
:
Option
<&
str
>
,
logincode
:
&
str
)
->
Result
<
String
,()
>
{
//TODO: use session
fn
login_with_code
(
&
self
,
_session
:
Option
<&
str
>
,
logincode
:
&
str
)
->
Result
<
String
,()
>
{
match
self
.query_row
(
"SELECT id FROM session_user WHERE logincode = ?1"
,
&
[
&
logincode
],
...
...
@@ -237,7 +240,8 @@ impl MedalConnection for Connection {
}
}
fn
login_foreign
(
&
self
,
session
:
Option
<&
str
>
,
foreign_id
:
u32
,
foreign_type
:
functions
::
UserType
,
firstname
:
&
str
,
lastname
:
&
str
)
->
Result
<
String
,()
>
{
//TODO: use session
fn
login_foreign
(
&
self
,
_session
:
Option
<&
str
>
,
foreign_id
:
u32
,
foreign_type
:
functions
::
UserType
,
firstname
:
&
str
,
lastname
:
&
str
)
->
Result
<
String
,()
>
{
let
session_token
:
String
=
thread_rng
()
.sample_iter
(
&
Alphanumeric
)
.take
(
10
)
.collect
();
let
csrf_token
:
String
=
thread_rng
()
.sample_iter
(
&
Alphanumeric
)
.take
(
10
)
.collect
();
let
now
=
time
::
get_time
();
...
...
@@ -260,7 +264,8 @@ impl MedalConnection for Connection {
}
}
fn
create_user_with_groupcode
(
&
self
,
session
:
Option
<&
str
>
,
groupcode
:
&
str
)
->
Result
<
String
,()
>
{
//TODO: use session
fn
create_user_with_groupcode
(
&
self
,
_session
:
Option
<&
str
>
,
groupcode
:
&
str
)
->
Result
<
String
,()
>
{
match
self
.query_row
(
"SELECT id FROM usergroup WHERE groupcode = ?1"
,
&
[
&
groupcode
],
...
...
src/functions.rs
View file @
928308cc
...
...
@@ -10,7 +10,7 @@ use db_conn::{MedalConnection};
use
db_objects
::{
Submission
,
Group
,
SessionUser
};
use
self
::
bcrypt
::{
DEFAULT_COST
,
hash
,
verify
,
BcryptError
};
use
self
::
bcrypt
::{
hash
};
#[derive(Serialize,
Deserialize)]
pub
struct
SubTaskInfo
{
...
...
@@ -35,6 +35,7 @@ pub struct ContestInfo {
pub
tasks
:
Vec
<
TaskInfo
>
,
}
#[allow(dead_code)]
//CsrfCheckFailed, SessionTimeout, DatabaseError and UnmatchedPasswords are used in webfw_iron.rs.
#[derive(Clone)]
pub
enum
MedalError
{
NotLoggedIn
,
...
...
@@ -103,10 +104,9 @@ pub fn show_contest<T: MedalConnection>(conn: &T, contest_id: u32, session_token
let
mut
tasks
=
Vec
::
new
();
for
(
task
,
grade
)
in
c
.taskgroups
.into_iter
()
.zip
(
grades
)
{
let
mut
not_print_yet
=
true
;
let
mut
blackstars
:
usize
=
0
;
let
mut
stasks
=
Vec
::
new
();
for
st
in
task
.tasks
{
blackstars
=
0
;
let
mut
blackstars
:
usize
=
0
;
if
not_print_yet
&&
st
.stars
>=
grade
.grade
.unwrap_or
(
0
)
{
blackstars
=
grade
.grade
.unwrap_or
(
0
)
as
usize
;
not_print_yet
=
false
;
...
...
@@ -185,7 +185,8 @@ pub fn show_contest_results<T: MedalConnection>(conn: &T, contest_id: u32, sessi
for
(
group
,
groupdata
)
in
resultdata
{
let
mut
groupresults
:
Vec
<
(
String
,
Vec
<
String
>
)
>
=
Vec
::
new
();
for
(
user
,
userdata
)
in
groupdata
{
//TODO: use user
for
(
_user
,
userdata
)
in
groupdata
{
let
mut
userresults
:
Vec
<
String
>
=
Vec
::
new
();
userresults
.push
(
String
::
new
());
...
...
@@ -227,8 +228,10 @@ pub fn show_contest_results<T: MedalConnection>(conn: &T, contest_id: u32, sessi
Ok
((
"contestresults"
.to_owned
(),
data
))
}
pub
fn
start_contest
<
T
:
MedalConnection
>
(
conn
:
&
T
,
contest_id
:
u32
,
session_token
:
String
,
csrf_token
:
String
)
->
MedalResult
<
()
>
{
let
data
=
json_val
::
Map
::
new
();
//TODO: use csrf_token
pub
fn
start_contest
<
T
:
MedalConnection
>
(
conn
:
&
T
,
contest_id
:
u32
,
session_token
:
String
,
_csrf_token
:
String
)
->
MedalResult
<
()
>
{
//TODO: use data
let
_data
=
json_val
::
Map
::
new
();
match
conn
.new_participation
(
&
session_token
,
contest_id
)
{
Ok
(
_
)
=>
Ok
(()),
...
...
@@ -407,7 +410,7 @@ pub fn show_group<T: MedalConnection>(conn: &T, group_id: u32, session_token: St
Ok
((
"group"
.to_string
(),
data
))
}
pub
fn
modify_group
<
T
:
MedalConnection
>
(
conn
:
&
T
,
group_id
:
u32
,
session_token
:
String
)
->
MedalResult
<
()
>
{
pub
fn
modify_group
<
T
:
MedalConnection
>
(
_
conn
:
&
T
,
_
group_id
:
u32
,
_
session_token
:
String
)
->
MedalResult
<
()
>
{
unimplemented!
()
}
...
...
@@ -437,10 +440,11 @@ pub fn add_group<T: MedalConnection>(conn: &T, session_token: String, csrf_token
Ok
(
group
.id
.unwrap
())
}
#[allow(dead_code)]
pub
fn
show_groups_results
<
T
:
MedalConnection
>
(
conn
:
&
T
,
contest_id
:
u32
,
session_token
:
String
)
->
MedalValueResult
{
let
session
=
conn
.get_session_or_new
(
&
session_token
)
.ensure_logged_in
()
.ok_or
(
MedalError
::
NotLoggedIn
)
?
;
let
g
=
conn
.get_contest_groups_grades
(
session
.id
,
contest_id
);
//TODO: use g
let
_g
=
conn
.get_contest_groups_grades
(
session
.id
,
contest_id
);
let
data
=
json_val
::
Map
::
new
();
...
...
@@ -469,7 +473,7 @@ pub fn show_profile<T: MedalConnection>(conn: &T, session_token: String, user_id
query_string
.map
(|
query
|
{
if
query
.starts_with
(
"status="
)
{
let
status
:
&
str
=
&
query
[
7
..
];
if
[
"
n
othing
_c
hanged"
,
"
d
ata
_c
hanged"
,
"
p
assword
_c
hanged"
,
"
p
assword
_m
issmatch"
]
.contains
(
&
status
)
{
if
[
"
N
othing
C
hanged"
,
"
D
ata
C
hanged"
,
"
P
assword
C
hanged"
,
"
P
assword
M
issmatch"
]
.contains
(
&
status
)
{
data
.insert
((
status
)
.to_string
(),
to_json
(
&
true
));
}
}});
...
...
@@ -506,16 +510,16 @@ fn hash_password(password: &str, salt: &str) -> Result<String, MedalError> {
let
password_and_salt
=
[
password
,
salt
]
.concat
()
.to_string
();
match
hash
(
password_and_salt
,
5
)
{
Ok
(
result
)
=>
Ok
(
result
),
Err
(
e
)
=>
Err
(
MedalError
::
PasswordHashingError
)
Err
(
_
)
=>
Err
(
MedalError
::
PasswordHashingError
)
}
}
#[derive(Debug)]
pub
enum
ProfileStatus
{
n
othing
_c
hanged
,
d
ata
_c
hanged
,
p
assword
_c
hanged
,
p
assword
_m
issmatch
,
N
othing
C
hanged
,
D
ata
C
hanged
,
P
assword
C
hanged
,
P
assword
M
issmatch
,
}
impl
std
::
convert
::
Into
<
String
>
for
ProfileStatus
{
fn
into
(
self
)
->
String
{
...
...
@@ -535,10 +539,10 @@ pub fn edit_profile<T: MedalConnection>(conn: &T, session_token: String, user_id
&&
session
.grade
==
grade
&&
password
==
""
&&
password_repeat
==
""
{
return
Ok
(
ProfileStatus
::
n
othing
_c
hanged
);
return
Ok
(
ProfileStatus
::
N
othing
C
hanged
);
}
let
mut
result
=
ProfileStatus
::
d
ata
_c
hanged
;
let
mut
result
=
ProfileStatus
::
D
ata
C
hanged
;
let
mut
password_salt
=
None
;
...
...
@@ -546,11 +550,11 @@ pub fn edit_profile<T: MedalConnection>(conn: &T, session_token: String, user_id
if
password
==
password_repeat
{
let
salt
:
String
=
thread_rng
()
.sample_iter
(
&
Alphanumeric
)
.take
(
10
)
.collect
();
let
hash
=
hash_password
(
&
password
,
&
salt
)
?
;
password_salt
=
Some
((
hash
,
salt
));
result
=
ProfileStatus
::
p
assword
_c
hanged
;
result
=
ProfileStatus
::
P
assword
C
hanged
;
}
else
{
result
=
ProfileStatus
::
p
assword
_m
issmatch
;
result
=
ProfileStatus
::
P
assword
M
issmatch
;
}
}
...
...
@@ -564,7 +568,7 @@ pub fn edit_profile<T: MedalConnection>(conn: &T, session_token: String, user_id
session
.password
=
Some
(
password
);
session
.salt
=
Some
(
salt
);
}
conn
.save_session
(
session
);
}
Some
(
user_id
)
=>
{
...
...
@@ -583,7 +587,7 @@ pub fn edit_profile<T: MedalConnection>(conn: &T, session_token: String, user_id
user
.password
=
Some
(
password
);
user
.salt
=
Some
(
salt
);
}
conn
.save_session
(
user
);
}
}
...
...
src/main.rs
View file @
928308cc
...
...
@@ -60,9 +60,9 @@ pub struct Config {
fn
read_config_from_file
(
file
:
&
Path
)
->
Config
{
use
std
::
io
::
Read
;
println!
(
"Reading configuration file '{}'"
,
file
.to_str
()
.unwrap_or
(
"<Encoding error>"
));
let
mut
config
:
Config
=
if
let
Ok
(
mut
file
)
=
fs
::
File
::
open
(
file
)
{
let
mut
contents
=
String
::
new
();
file
.read_to_string
(
&
mut
contents
)
.unwrap
();
...
...
@@ -157,7 +157,7 @@ fn add_admin_user(conn: &mut Connection, resetpw: bool) {
None
=>
{
print!
(
"New Database. Creating new admin user with credentials 'admin':"
);
conn
.new_session
()
},
Some
(
user
)
=>
{
if
!
resetpw
{
...
...
@@ -166,7 +166,6 @@ fn add_admin_user(conn: &mut Connection, resetpw: bool) {
print!
(
"Request to reset admin password. Set credentials 'admin':"
);
user
}
_
=>
return
};
use
rand
::{
thread_rng
,
Rng
,
distributions
::
Alphanumeric
};
...
...
@@ -189,7 +188,7 @@ fn add_admin_user(conn: &mut Connection, resetpw: bool) {
fn
main
()
{
let
opt
=
Opt
::
from_args
();
//println!("{:?}", opt); // Show in different debug level?
let
mut
config
=
read_config_from_file
(
&
opt
.configfile
);
if
opt
.databasefile
.is_some
()
{
config
.database_file
=
opt
.databasefile
;
}
...
...
@@ -235,24 +234,24 @@ mod tests {
let
srvr
=
start_server
(
conn
,
config
);
start_tx
.send
(())
.unwrap
();
stop_rx
.recv
()
.unwrap
();
srvr
.unwrap
()
.close
()
.unwrap
();
srvr
.unwrap
()
.close
()
.unwrap
();
});
// wait for server to start:
start_rx
.recv
()
.unwrap
();
thread
::
sleep
(
time
::
Duration
::
from_millis
(
100
));
f
();
stop_tx
.send
(())
.unwrap
();
stop_tx
.send
(())
.unwrap
();
}
#[test]
fn
start_server_and_check_request
()
{
start_server_and_fn
(
8080
,
||{
let
mut
resp
=
reqwest
::
get
(
"http://localhost:8080"
)
.unwrap
();
assert!
(
resp
.status
()
.is_success
());
assert!
(
resp
.status
()
.is_success
());
let
mut
content
=
String
::
new
();
resp
.read_to_string
(
&
mut
content
);
assert!
(
content
.contains
(
"<h1>Jugendwettbewerb Informatik</h1>"
));
...
...
@@ -267,7 +266,7 @@ mod tests {
assert!
(
!
content
.contains
(
"Error"
));
})
}
#[test]
fn
check_login_wrong_credentials
()
{
start_server_and_fn
(
8081
,
||{
...
...
src/webfw_iron.rs
View file @
928308cc
...
...
@@ -141,7 +141,7 @@ impl<'a, 'b> RequestSession for Request<'a, 'b> {
use
rand
::{
thread_rng
,
Rng
};
let
new_session_key
:
String
=
thread_rng
()
.
gen_ascii_chars
(
)
.take
(
28
)
.collect
();
let
new_session_key
:
String
=
thread_rng
()
.
sample_iter
(
&
Alphanumeric
)
.take
(
28
)
.collect
();
self
.session
()
.set
(
SessionToken
{
token
:
new_session_key
})
.unwrap
();
Err
(
IronError
{
error
:
Box
::
new
(
SessionError
{
message
:
"No valid session found, redirecting to cookie page"
.to_string
()
}),
response
:
Response
::
with
((
status
::
Found
,
RedirectRaw
(
format!
(
"/cookie?{}"
,
self
.url
.path
()
.join
(
"/"
)))))
})
...
...
@@ -280,7 +280,7 @@ fn contestresults(req: &mut Request) -> IronResult<Response> {
let
session_token
=
req
.require_session_token
()
?
;
let
(
template
,
data
)
=
with_conn!
[
functions
::
show_contest_results
,
req
,
contest_id
,
session_token
]
.aug
(
req
)
?
;
let
mut
resp
=
Response
::
new
();
resp
.set_mut
(
Template
::
new
(
&
template
,
data
))
.set_mut
(
status
::
Ok
);
Ok
(
resp
)
...
...
@@ -296,16 +296,16 @@ fn contest_post(req: &mut Request) -> IronResult<Response> {
};
// TODO: Was mit dem Result?
let
startcontestresult
=
with_conn!
[
functions
::
start_contest
,
req
,
contest_id
,
session_token
,
csrf_token
]
.aug
(
req
)
?
;
let
_
startcontestresult
=
with_conn!
[
functions
::
start_contest
,
req
,
contest_id
,
session_token
,
csrf_token
]
.aug
(
req
)
?
;
Ok
(
Response
::
with
((
status
::
Found
,
Redirect
(
url_for!
(
req
,
"contest"
,
"contestid"
=>
format!
(
"{}"
,
contest_id
))))))
}
fn
login
(
req
:
&
mut
Request
)
->
IronResult
<
Response
>
{
let
(
self_url
,
oauth_url
)
=
{
let
(
self_url
,
oauth_url
)
=
{
let
mutex
=
req
.get
::
<
Write
<
SharedConfiguration
>>
()
.unwrap
();
let
config
=
mutex
.lock
()
.unwrap_or_else
(|
e
|
e
.into_inner
());
(
config
.self_url
.clone
(),
config
.oauth_url
.clone
())
};
...
...
@@ -326,7 +326,7 @@ fn login_post(req: &mut Request) -> IronResult<Response> {
};
// TODO: Submit current session to login
let
loginresult
=
with_conn!
[
functions
::
login
,
req
,
logindata
];
match
loginresult
{
...
...
@@ -379,7 +379,7 @@ fn logout(req: &mut Request) -> IronResult<Response> {
let
session_token
=
req
.get_session_token
();
println!
(
"Loggin out session {:?}"
,
session_token
);
with_conn!
[
functions
::
logout
,
req
,
session_token
];
Ok
(
Response
::
with
((
status
::
Found
,
Redirect
(
url_for!
(
req
,
"greet"
)))))
...
...
@@ -422,9 +422,9 @@ fn submission_post(req: &mut Request) -> IronResult<Response> {
println!
(
"{}"
,
data
);
println!
(
"{}"
,
task_id
);
println!
(
"{}"
,
grade
);
let
result
=
with_conn!
[
functions
::
save_submission
,
req
,
task_id
,
session_token
,
csrf_token
,
data
,
grade
,
subtask
];
match
result
{
Ok
(
_
)
=>
Ok
(
Response
::
with
((
status
::
Ok
,
...
...
@@ -452,9 +452,9 @@ fn task(req: &mut Request) -> IronResult<Response> {
fn
groups
(
req
:
&
mut
Request
)
->
IronResult
<
Response
>
{
let
session_token
=
req
.require_session_token
()
?
;
let
(
template
,
data
)
=
with_conn!
[
functions
::
show_groups
,
req
,
session_token
]
.aug
(
req
)
?
;
let
mut
resp
=
Response
::
new
();
resp
.set_mut
(
Template
::
new
(
&
template
,
data
))
.set_mut
(
status
::
Ok
);
Ok
(
resp
)
...
...
@@ -463,9 +463,9 @@ fn groups(req: &mut Request) -> IronResult<Response> {
fn
group
(
req
:
&
mut
Request
)
->
IronResult
<
Response
>
{
let
group_id
=
req
.expect_int
::
<
u32
>
(
"groupid"
)
?
;
let
session_token
=
req
.require_session_token
()
?
;
let
(
template
,
data
)
=
with_conn!
[
functions
::
show_group
,
req
,
group_id
,
session_token
]
.aug
(
req
)
?
;
let
mut
resp
=
Response
::
new
();
resp
.set_mut
(
Template
::
new
(
&
template
,
data
))
.set_mut
(
status
::
Ok
);
Ok
(
resp
)
...
...
@@ -475,7 +475,8 @@ fn group_post(req: &mut Request) -> IronResult<Response> {
let
group_id
=
req
.expect_int
::
<
u32
>
(
"groupid"
)
?
;
let
session_token
=
req
.expect_session_token
()
?
;
let
changegroupresult
=
with_conn!
[
functions
::
modify_group
,
req
,
group_id
,
session_token
]
.aug
(
req
)
?
;
//TODO: use changegroupresult
let
_changegroupresult
=
with_conn!
[
functions
::
modify_group
,
req
,
group_id
,
session_token
]
.aug
(
req
)
?
;
Ok
(
Response
::
with
((
status
::
Found
,
Redirect
(
url_for!
(
req
,
"group"
,
"groupid"
=>
format!
(
"{}"
,
group_id
))))))
}
...
...
@@ -493,16 +494,16 @@ fn new_group(req: &mut Request) -> IronResult<Response> {
println!
(
"{}"
,
name
);
let
group_id
=
with_conn!
[
functions
::
add_group
,
req
,
session_token
,
csrf
,
name
,
tag
]
.aug
(
req
)
?
;
Ok
(
Response
::
with
((
status
::
Found
,
Redirect
(
url_for!
(
req
,
"group"
,
"groupid"
=>
format!
(
"{}"
,
group_id
))))))
}
fn
profile
(
req
:
&
mut
Request
)
->
IronResult
<
Response
>
{
let
session_token
=
req
.require_session_token
()
?
;
let
query_string
=
req
.url
.query
()
.map
(|
s
|
s
.to_string
());
let
(
template
,
data
)
=
with_conn!
[
functions
::
show_profile
,
req
,
session_token
,
None
,
query_string
]
.aug
(
req
)
?
;
let
mut
resp
=
Response
::
new
();
resp
.set_mut
(
Template
::
new
(
&
template
,
data
))
.set_mut
(
status
::
Ok
);
Ok
(
resp
)
...
...
@@ -521,8 +522,9 @@ fn profile_post(req: &mut Request) -> IronResult<Response> {
iexpect!
(
formdata
.get
(
"grade"
))[
0
]
.parse
::
<
u8
>
()
.unwrap_or
(
0
)
)
};
let
profilechangeresult
=
with_conn!
[
functions
::
edit_profile
,
req
,
session_token
,
None
,
csrf_token
,
firstname
,
lastname
,
pwd
,
pwd_repeat
,
grade
]
.aug
(
req
)
?
;
//TODO: use profilechangeresult
let
_profilechangeresult
=
with_conn!
[
functions
::
edit_profile
,
req
,
session_token
,
None
,
csrf_token
,
firstname
,
lastname
,
pwd
,
pwd_repeat
,
grade
]
.aug
(
req
)
?
;
Ok
(
Response
::
with
((
status
::
Found
,
Redirect
(
url_for!
(
req
,
"profile"
)))))
}
...
...
@@ -531,9 +533,9 @@ fn user(req: &mut Request) -> IronResult<Response> {
let
user_id
=
req
.expect_int
::
<
u32
>
(
"userid"
)
?
;
let
session_token
=
req
.expect_session_token
()
?
;
let
query_string
=
req
.url
.query
()
.map
(|
s
|
s
.to_string
());
let
(
template
,
data
)
=
with_conn!
[
functions
::
show_profile
,
req
,
session_token
,
Some
(
user_id
),
query_string
]
.aug
(
req
)
?
;
let
mut
resp
=
Response
::
new
();
resp
.set_mut
(
Template
::
new
(
&
template
,
data
))
.set_mut
(
status
::
Ok
);
Ok
(
resp
)
...
...
@@ -554,8 +556,9 @@ fn user_post(req: &mut Request) -> IronResult<Response> {
)
};
let
profilechangeresult
=
with_conn!
[
functions
::
edit_profile
,
req
,
session_token
,
Some
(
user_id
),
csrf_token
,
firstname
,
lastname
,
pwd
,
pwd_repeat
,
grade
]
.aug
(
req
)
?
;
//TODO: use profilechangeresult
let
_profilechangeresult
=
with_conn!
[
functions
::
edit_profile
,
req
,
session_token
,
Some
(
user_id
),
csrf_token
,
firstname
,
lastname
,
pwd
,
pwd_repeat
,
grade
]
.aug
(
req
)
?
;
Ok
(
Response
::
with
((
status
::
Found
,
Redirect
(
url_for!
(
req
,
"user"
,
"userid"
=>
format!
(
"{}"
,
user_id
))))))
}
...
...
@@ -725,7 +728,7 @@ pub fn get_handlebars_engine() -> impl AfterMiddleware {
fn
cookie_warning
(
req
:
&
mut
Request
)
->
IronResult
<
Response
>
{
match
req
.get_session_token
()
{
Some
(
session_token
)
=>
{
Some
(
_
session_token
)
=>
{
// TODO: Set session!
// TODO:
Ok
(
Response
::
with
((
status
::
Found
,
RedirectRaw
(
format!
(
"/{}"
,
req
.url
.query
()
.unwrap_or
(
""
))))))
...
...
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment