Skip to content
This repository was archived by the owner on Aug 20, 2024. It is now read-only.

Commit

Permalink
Log unauthorized endpoint access (#570)
Browse files Browse the repository at this point in the history
* Log unauthorized endpoint access

* Modifications from review
  • Loading branch information
Timo614 authored and stringhandler committed Nov 21, 2018
1 parent 71266bd commit 9693be0
Show file tree
Hide file tree
Showing 33 changed files with 336 additions and 420 deletions.
1 change: 0 additions & 1 deletion Cargo.lock

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

1 change: 0 additions & 1 deletion api/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ bigneon_db = { path = "../db" }
chrono = {version = "0.4", features = ["serde"]}
diesel = {version = "1.3", features = ["r2d2"]}
dotenv = "0.13"
itertools = "0.7"
jsonwebtoken = "5"
lettre = "0.8"
lettre_email = "0.8"
Expand Down
51 changes: 37 additions & 14 deletions api/src/auth/user.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,31 @@ use bigneon_db::models::{Organization, Scopes};
use diesel::PgConnection;
use errors::*;
use jwt::{decode, Validation};
use log::Level::Warn;
use middleware::RequestConnection;
use serde_json::Value;
use server::AppState;
use std::collections::HashMap;
use uuid::Uuid;

#[derive(Clone, Debug)]
pub struct User {
pub user: DbUser,
pub global_scopes: Vec<String>,
pub ip_address: Option<String>,
pub uri: String,
pub method: String,
}

impl User {
pub fn new(user: DbUser) -> User {
pub fn new(user: DbUser, request: &HttpRequest<AppState>) -> User {
let global_scopes = user.get_global_scopes();
User {
user,
global_scopes,
ip_address: request.connection_info().remote().map(|i| i.to_string()),
uri: request.uri().to_string(),
method: request.method().to_string(),
}
}

Expand All @@ -32,32 +41,46 @@ impl User {
self.user.email.clone()
}

pub fn has_scope(
fn has_scope(
&self,
scope: Scopes,
organization: Option<&Organization>,
connection: &PgConnection,
connection: Option<&PgConnection>,
) -> Result<bool, BigNeonError> {
if self.global_scopes.contains(&scope.to_string()) {
return Ok(true);
}

if let Some(organization) = organization {
return Ok(organization
.get_scopes_for_user(&self.user, connection)?
.contains(&scope.to_string()));
let mut logging_data = HashMap::new();
if let (Some(organization), Some(connection)) = (organization, connection) {
let organization_scopes = organization.get_scopes_for_user(&self.user, connection)?;
logging_data.insert("organization_scopes", json!(organization_scopes));
logging_data.insert("organization_id", json!(organization.id));
if organization_scopes.contains(&scope.to_string()) {
return Ok(true);
}
}

logging_data.insert("accessed_scope", json!(scope.to_string()));
logging_data.insert("global_scopes", json!(self.global_scopes));
self.log_unauthorized_access_attempt(logging_data);
Ok(false)
}

pub fn requires_scope(&self, scope: Scopes) -> Result<(), AuthError> {
if self.global_scopes.contains(&scope.to_string()) {
pub fn log_unauthorized_access_attempt(&self, mut logging_data: HashMap<&'static str, Value>) {
logging_data.insert("user_id", json!(self.id()));
logging_data.insert("user_name", json!(self.user.full_name()));
logging_data.insert("ip_address", json!(self.ip_address));
logging_data.insert("url", json!(self.uri));
logging_data.insert("method", json!(self.method));
jlog!(Warn, "Unauthorized access attempt", logging_data);
}

pub fn requires_scope(&self, scope: Scopes) -> Result<(), BigNeonError> {
if self.has_scope(scope, None, None)? {
return Ok(());
}
Err(AuthError::new(
"User does not have the required permissions".to_string(),
))
Err(AuthError::new("User does not have the required permissions".to_string()).into())
}

pub fn requires_scope_for_organization(
Expand All @@ -66,7 +89,7 @@ impl User {
organization: &Organization,
conn: &PgConnection,
) -> Result<(), BigNeonError> {
if self.has_scope(scope, Some(organization), conn)? {
if self.has_scope(scope, Some(organization), Some(conn))? {
return Ok(());
}
Err(AuthError::new("User does not have the required permissions".to_string()).into())
Expand Down Expand Up @@ -99,7 +122,7 @@ impl FromRequest<AppState> for User {
).map_err(|e| BigNeonError::from(e))?;
let connection = req.connection()?;
match DbUser::find(token.claims.get_id()?, connection.get()) {
Ok(user) => Ok(User::new(user)),
Ok(user) => Ok(User::new(user, req)),
Err(e) => Err(error::ErrorInternalServerError(e)),
}
}
Expand Down
34 changes: 12 additions & 22 deletions api/src/controllers/artists.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ use auth::user::User;
use bigneon_db::models::*;
use db::Connection;
use errors::*;
use helpers::application;
use models::PathParameters;

pub fn index(
Expand All @@ -30,19 +29,14 @@ pub fn create(
(connection, new_artist, user): (Connection, Json<NewArtist>, User),
) -> Result<HttpResponse, BigNeonError> {
let connection = connection.get();
if !user.has_scope(Scopes::ArtistWrite, None, connection)? {
if new_artist.organization_id.is_none() {
return application::unauthorized();
} else if let Some(organization_id) = new_artist.organization_id {
let organization = Organization::find(organization_id, connection)?;
if !user.has_scope(Scopes::ArtistWrite, Some(&organization), connection)? {
return application::unauthorized();
}
}
if let Some(organization_id) = new_artist.organization_id {
let organization = Organization::find(organization_id, connection)?;
user.requires_scope_for_organization(Scopes::ArtistWrite, &organization, connection)?;
} else {
user.requires_scope(Scopes::ArtistWrite)?;
}

let mut artist = new_artist.commit(connection)?;

// New artists belonging to an organization start private
if artist.organization_id.is_some() {
artist = artist.set_privacy(true, connection)?;
Expand Down Expand Up @@ -79,14 +73,11 @@ pub fn update(
) -> Result<HttpResponse, BigNeonError> {
let connection = connection.get();
let artist = Artist::find(&parameters.id, connection)?;
if !user.has_scope(Scopes::ArtistWrite, None, connection)? {
if !artist.is_private || artist.organization_id.is_none() {
return application::unauthorized();
} else if let Some(organization) = artist.organization(connection)? {
if !user.has_scope(Scopes::ArtistWrite, Some(&organization), connection)? {
return application::unauthorized();
}
}
if !artist.is_private || artist.organization_id.is_none() {
user.requires_scope(Scopes::ArtistWrite)?;
} else {
let organization = artist.organization(connection)?.unwrap();
user.requires_scope_for_organization(Scopes::ArtistWrite, &organization, connection)?;
}

let updated_artist = artist.update(&artist_parameters, connection)?;
Expand All @@ -96,10 +87,9 @@ pub fn update(
pub fn toggle_privacy(
(connection, parameters, user): (Connection, Path<PathParameters>, User),
) -> Result<HttpResponse, BigNeonError> {
user.requires_scope(Scopes::ArtistWrite)?;

let connection = connection.get();
if !user.has_scope(Scopes::ArtistWrite, None, connection)? {
return application::unauthorized();
}
let artist = Artist::find(&parameters.id, connection)?;
let updated_artist = artist.set_privacy(!artist.is_private, connection)?;
Ok(HttpResponse::Ok().json(updated_artist))
Expand Down
9 changes: 7 additions & 2 deletions api/src/controllers/auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,12 @@ pub fn token(
}

pub fn token_refresh(
(state, connection, refresh_request): (State<AppState>, Connection, Json<RefreshRequest>),
(state, connection, refresh_request, request): (
State<AppState>,
Connection,
Json<RefreshRequest>,
HttpRequest<AppState>,
),
) -> Result<HttpResponse, BigNeonError> {
let mut validation = Validation::default();
validation.validate_exp = false;
Expand All @@ -122,7 +127,7 @@ pub fn token_refresh(
)?;
Ok(HttpResponse::Ok().json(response))
} else {
application::unauthorized_with_message("Invalid token")
application::unauthorized_with_message("Invalid token", &request, None)
}
}

Expand Down
6 changes: 2 additions & 4 deletions api/src/controllers/cart.rs
Original file line number Diff line number Diff line change
Expand Up @@ -227,11 +227,9 @@ fn checkout_external(
checkout_request: &CheckoutCartRequest,
user: &User,
) -> Result<HttpResponse, BigNeonError> {
let connection = conn.get();
if !user.has_scope(Scopes::OrderMakeExternalPayment, None, connection)? {
return application::unauthorized();
}
user.requires_scope(Scopes::OrderMakeExternalPayment)?;

let connection = conn.get();
if order.status()? != OrderStatus::Draft {
return application::unprocessable(
"Could not complete this cart because it is not in the correct status",
Expand Down
65 changes: 16 additions & 49 deletions api/src/controllers/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ use bigneon_db::models::*;
use chrono::prelude::*;
use db::Connection;
use errors::*;
use helpers::application;
use models::{PathParameters, UserDisplayTicketType, WebPayload};
use serde_json::Value;
use serde_with::{self, CommaSeparator};
Expand Down Expand Up @@ -282,14 +281,8 @@ pub fn create(
(connection, new_event, user): (Connection, Json<NewEvent>, User),
) -> Result<HttpResponse, BigNeonError> {
let connection = connection.get();

if !user.has_scope(Scopes::EventWrite, None, connection)? && !user.has_scope(
Scopes::EventWrite,
Some(&Organization::find(new_event.organization_id, connection)?),
connection,
)? {
return application::unauthorized();
}
let organization = Organization::find(new_event.organization_id, connection)?;
user.requires_scope_for_organization(Scopes::EventWrite, &organization, connection)?;

let event = new_event.commit(connection)?;
Ok(HttpResponse::Created().json(&event))
Expand All @@ -316,13 +309,8 @@ pub fn update(
) -> Result<HttpResponse, BigNeonError> {
let connection = connection.get();
let event = Event::find(parameters.id, connection)?;
if !user.has_scope(
Scopes::EventWrite,
Some(&event.organization(connection)?),
connection,
)? {
return application::unauthorized();
}
let organization = event.organization(connection)?;
user.requires_scope_for_organization(Scopes::EventWrite, &organization, connection)?;

let updated_event = event.update(event_parameters.into_inner(), connection)?;
Ok(HttpResponse::Ok().json(&updated_event))
Expand All @@ -333,13 +321,8 @@ pub fn cancel(
) -> Result<HttpResponse, BigNeonError> {
let connection = connection.get();
let event = Event::find(parameters.id, connection)?;
if !user.has_scope(
Scopes::EventWrite,
Some(&event.organization(connection)?),
connection,
)? {
return application::unauthorized();
}
let organization = event.organization(connection)?;
user.requires_scope_for_organization(Scopes::EventWrite, &organization, connection)?;

//Doing this in the DB layer so it can use the DB time as now.
let updated_event = event.cancel(connection)?;
Expand All @@ -355,11 +338,9 @@ pub fn list_interested_users(
User,
),
) -> Result<HttpResponse, BigNeonError> {
let connection = connection.get();
if !user.has_scope(Scopes::EventInterest, None, connection)? {
return application::unauthorized();
}
user.requires_scope(Scopes::EventInterest)?;

let connection = connection.get();
let paging: Paging = query.clone().into();

let payload = EventInterest::list_interested_users(
Expand All @@ -375,23 +356,19 @@ pub fn list_interested_users(
pub fn add_interest(
(connection, parameters, user): (Connection, Path<PathParameters>, User),
) -> Result<HttpResponse, BigNeonError> {
let connection = connection.get();
if !user.has_scope(Scopes::EventInterest, None, connection)? {
return application::unauthorized();
}
user.requires_scope(Scopes::EventInterest)?;

let connection = connection.get();
let event_interest = EventInterest::create(parameters.id, user.id()).commit(connection)?;
Ok(HttpResponse::Created().json(&event_interest))
}

pub fn remove_interest(
(connection, parameters, user): (Connection, Path<PathParameters>, User),
) -> Result<HttpResponse, BigNeonError> {
let connection = connection.get();
if !user.has_scope(Scopes::EventInterest, None, connection)? {
return application::unauthorized();
}
user.requires_scope(Scopes::EventInterest)?;

let connection = connection.get();
let event_interest = EventInterest::remove(parameters.id, user.id(), connection)?;
Ok(HttpResponse::Ok().json(&event_interest))
}
Expand All @@ -406,13 +383,8 @@ pub fn add_artist(
) -> Result<HttpResponse, BigNeonError> {
let connection = connection.get();
let event = Event::find(parameters.id, connection)?;
if !user.has_scope(
Scopes::EventWrite,
Some(&event.organization(connection)?),
connection,
)? {
return application::unauthorized();
}
let organization = event.organization(connection)?;
user.requires_scope_for_organization(Scopes::EventWrite, &organization, connection)?;

let event_artist = EventArtist::create(
parameters.id,
Expand All @@ -433,13 +405,8 @@ pub fn update_artists(
) -> Result<HttpResponse, BigNeonError> {
let connection = connection.get();
let event = Event::find(parameters.id, connection)?;
if !user.has_scope(
Scopes::EventWrite,
Some(&event.organization(connection)?),
connection,
)? {
return application::unauthorized();
}
let organization = event.organization(connection)?;
user.requires_scope_for_organization(Scopes::EventWrite, &organization, connection)?;

EventArtist::clear_all_from_event(parameters.id, connection)?;

Expand Down
Loading

0 comments on commit 9693be0

Please sign in to comment.