Skip to content

Commit

Permalink
Merge #47
Browse files Browse the repository at this point in the history
47: Basic access control r=MikailBag a=MikailBag



Co-authored-by: Mikail Bagishov <bagishov.mikail@yandex.ru>
  • Loading branch information
bors[bot] and MikailBag authored Sep 20, 2019
2 parents 60ecce1 + dd6656f commit 24032a8
Show file tree
Hide file tree
Showing 14 changed files with 305 additions and 115 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions cfg/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,13 @@ impl Config {
pub fn find_problem(&self, name: &str) -> Option<&Problem> {
self.problems.get(name)
}

pub fn find_contest(&self, name: &str) -> Option<&Contest> {
match name {
"TODO" => Some(&self.contests[0]),
_ => None,
}
}
}

pub fn parse_file(path: PathBuf) -> Config {
Expand Down
1 change: 1 addition & 0 deletions frontend-engine/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ serde_json = "1.0.40"
branca = "0.9.0"
juniper_rocket = "0.4.1"
backtrace = "0.3.35"
snafu = "0.5.0"


[dev-dependencies]
Expand Down
4 changes: 4 additions & 0 deletions frontend-engine/src/gql_server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@ impl ApiError {
s.dev_backtrace();
s
}

pub fn access_denied(ctx: &Context) -> Self {
Self::new(ctx, "AccessDenied")
}
}

mod impl_display {
Expand Down
3 changes: 1 addition & 2 deletions frontend-engine/src/gql_server/auth.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
use super::prelude::*;
use crate::security::Token;

pub(super) fn simple(
ctx: &Context,
Expand All @@ -17,7 +16,7 @@ pub(super) fn simple(
reject_reason = "UnknownUser";
}
if success {
let token = Token::issue_for_user(&login);
let token = ctx.token_mgr.create_token(&login).internal(ctx)?;
let buf = token.serialize(&ctx.secret_key);
let sess = schema::SessionToken {
data: buf,
Expand Down
34 changes: 33 additions & 1 deletion frontend-engine/src/gql_server/context.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::security::TokenFromRequestError;
use crate::security::{AccessChecker, Token, TokenFromRequestError, TokenMgr};
use std::sync::Arc;

pub(crate) type DbPool = Arc<dyn db::DbConn>;
Expand All @@ -10,6 +10,17 @@ pub(crate) struct ContextData {
pub(crate) secret_key: Arc<[u8]>,
pub(crate) env: crate::config::Env,
pub(crate) logger: slog::Logger,
pub(crate) token_mgr: TokenMgr,
pub(crate) token: Token,
}

impl ContextData {
pub(crate) fn access(&self) -> AccessChecker {
AccessChecker {
token: &self.token,
cfg: &self.cfg,
}
}
}

#[derive(Clone)]
Expand Down Expand Up @@ -41,12 +52,31 @@ impl<'a, 'r> rocket::request::FromRequest<'a, 'r> for ContextData {
.guard::<rocket::State<crate::security::SecretKey>>()
.expect("State<SecretKey> missing");

let token = request
.headers()
.get("X-Jjs-Auth")
.next()
.ok_or(TokenFromRequestError::Missing);

let token = token
.and_then(|header| Token::deserialize(&*secret_key, header.as_bytes(), env.is_dev()));
let token = match token {
Ok(tok) => Ok(tok),
Err(e) => match e {
TokenFromRequestError::Missing => Ok(Token::new_guest()),
_ => Err(e),
},
};
let token = token.map_err(|e| Err((rocket::http::Status::BadRequest, e)))?;

rocket::Outcome::Success(ContextData {
db: factory.pool.clone(),
cfg: factory.cfg.clone(),
env: *env,
secret_key: Arc::clone(&(*secret_key).0),
logger: factory.logger.clone(),
token_mgr: TokenMgr::new(factory.pool.clone()),
token,
})
}
}
Expand Down Expand Up @@ -78,6 +108,8 @@ impl ContextFactory {
secret_key: Arc::new([]),
env: crate::config::Env::Dev,
logger: self.logger.clone(),
token_mgr: TokenMgr::new(self.pool.clone()),
token: Token::new_root(),
}
}
}
Expand Down
13 changes: 9 additions & 4 deletions frontend-engine/src/gql_server/runs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,20 +39,22 @@ pub(super) fn submit_simple(
let toolchain = ctx.cfg.toolchains.iter().find(|t| t.name == toolchain);
let toolchain = match toolchain {
Some(tc) => tc.clone(),
None => return "unknown toolchain".report(ctx),
None => return Err(ApiError::new(ctx, "ToolchainUnknown")),
};
if contest != "TODO" {
return "unknown contest".report(ctx);
return Err(ApiError::new(ctx, "ContestUnknown"));
}
if !ctx.access().user_can_submit(&contest).internal(ctx)? {
return Err(ApiError::access_denied(ctx));
}

let problem = ctx.cfg.contests[0]
.problems
.iter()
.find(|pr| pr.code == problem)
.cloned();
let problem = match problem {
Some(p) => p,
None => return "unknown problem".report(ctx),
None => return Err(ApiError::new(ctx, "ProblemUnknown")),
};
let prob_name = problem.name.clone();

Expand Down Expand Up @@ -96,6 +98,9 @@ pub(super) fn modify(
rejudge: Option<bool>,
delete: Option<bool>,
) -> ApiResult<()> {
if !ctx.access().user_can_modify_run(id).internal(ctx)? {
return Err(ApiError::access_denied(ctx));
}
let should_delete = delete.unwrap_or(false);
if should_delete {
if status.is_some() || rejudge.is_some() {
Expand Down
14 changes: 13 additions & 1 deletion frontend-engine/src/security.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,19 @@
mod access_ck;
mod token;
mod token_mgr;

pub(crate) use access_ck::AccessChecker;
use std::sync::Arc;
pub use token::{Token, TokenFromRequestError};
pub(crate) use token::{Token, TokenFromRequestError};
pub(crate) use token_mgr::TokenMgr;

#[derive(Clone)]
pub struct SecretKey(pub Arc<[u8]>);

impl std::ops::Deref for SecretKey {
type Target = [u8];

fn deref(&self) -> &Self::Target {
&*(self.0)
}
}
49 changes: 49 additions & 0 deletions frontend-engine/src/security/access_ck.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
use crate::security::Token;
use snafu::Snafu;

/// Access check service
pub(crate) struct AccessChecker<'a> {
pub(crate) token: &'a Token,
pub(crate) cfg: &'a cfg::Config,
//TODO: pub(crate) db: &'a dyn db::DbConn
}

#[derive(Debug, Snafu)]
pub(crate) enum AccessCheckError {
NotFound,
}

pub(crate) type AccessResult = Result<bool, AccessCheckError>;

impl AccessChecker<'_> {
pub(crate) fn user_can_submit(&self, contest_id: &str) -> AccessResult {
let contest = self
.cfg
.find_contest(contest_id)
.ok_or(AccessCheckError::NotFound)?;
if self.user_is_contest_sudo(contest_id)? {
return Ok(true);
}
for registered_group in &contest.group {
if self.token.user_info.groups.contains(registered_group) {
return Ok(true);
}
}
Ok(false)
}

fn is_sudo(&self) -> AccessResult {
// When namespaces are introduced, this function will account for that
Ok(self.token.user_info.name == "Global/Root")
}

fn user_is_contest_sudo(&self, _contest_id: &str) -> AccessResult {
// TODO
self.is_sudo()
}

pub(crate) fn user_can_modify_run(&self, _run_id: i32) -> AccessResult {
// TODO
Ok(true)
}
}
42 changes: 29 additions & 13 deletions frontend-engine/src/security/token.rs
Original file line number Diff line number Diff line change
@@ -1,27 +1,19 @@
use serde::{Deserialize, Serialize};

// TODO
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct UserInfo {
name: String,
groups: Vec<String>,
/// TODO: name should have hierarchical type
pub(super) name: String,
pub(super) groups: Vec<String>,
}

/// Struct representing API session
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Token {
user_info: UserInfo,
pub(super) user_info: UserInfo,
}

impl Token {
pub fn issue_for_user(user_name: &str) -> Token {
Token {
user_info: UserInfo {
name: user_name.to_string(),
groups: Vec::new(),
},
}
}

pub fn issue_for_virtual_user(name: String, groups: Vec<String>) -> Token {
Token {
user_info: UserInfo { name, groups },
Expand Down Expand Up @@ -54,6 +46,30 @@ impl Token {
rand_gen.fill(&mut nonce);
branca::encode(&ser, key, 0).expect("Token encoding error")
}

pub fn deserialize(
key: &[u8],
data: &[u8],
allow_dev: bool,
) -> Result<Self, TokenFromRequestError> {
let data = match std::str::from_utf8(data) {
Ok(d) => d,
Err(_) => return Err(TokenFromRequestError::BadFormat),
};
if allow_dev && data.starts_with("dev_") {
let data = data.trim_start_matches("dev_");
if data == "root" {
return Ok(Token::new_root());
}
return Err(TokenFromRequestError::BadFormat);
}
let token_data = match branca::decode(data, key, 0) {
Ok(s) => s,
Err(err) => return Err(TokenFromRequestError::Branca(err)),
};
let res = serde_json::from_str(&token_data).expect("Token decoding error");
Ok(res)
}
}

#[derive(Debug)]
Expand Down
42 changes: 42 additions & 0 deletions frontend-engine/src/security/token_mgr.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
use crate::security::{token::UserInfo, Token};
use snafu::Snafu;
use std::sync::Arc;

/// Token Manager - entity manipulating tokens
pub(crate) struct TokenMgr {
db: Arc<dyn db::DbConn>,
}

#[derive(Debug, Snafu)]
pub enum TokenMgrError {
#[snafu(display("db error: {}", source))]
Db { source: db::Error },
#[snafu(display("user not exists"))]
UserMissing,
}

impl From<db::Error> for TokenMgrError {
fn from(source: db::Error) -> Self {
Self::Db { source }
}
}

impl TokenMgr {
pub fn new(db: Arc<dyn db::DbConn>) -> Self {
Self { db }
}

// TODO: use custom errors
pub fn create_token(&self, username: &str) -> Result<Token, TokenMgrError> {
let user_data = self
.db
.user_try_load_by_login(username)?
.ok_or(TokenMgrError::UserMissing)?;
Ok(Token {
user_info: UserInfo {
name: user_data.username,
groups: user_data.groups,
},
})
}
}
Loading

0 comments on commit 24032a8

Please # to comment.