Compare commits
No commits in common. "b9e41f6d6e2450ef42986967a1c6a8a5c86fca58" and "b09757d382dd891b8acf8a92d1fca1e90a4943ba" have entirely different histories.
b9e41f6d6e
...
b09757d382
|
@ -1,7 +0,0 @@
|
||||||
--- Creates a regular user with the password 'test'
|
|
||||||
INSERT OR IGNORE INTO users (username, email, login_source) VALUES ('test', 'test@example.com', 0);
|
|
||||||
INSERT OR IGNORE INTO user_logins (email, password_hash, password_salt) VALUES ('test@example.com', '$2y$05$k3gn.RxGxh59NhtyyiWPeeQ2J9kqVaImiL3GPuBjMsiJ51Bn3js.K', 'x5ECk0jUmOSfBWxW52wsyO');
|
|
||||||
INSERT OR IGNORE INTO user_registry_permissions (email, user_type) VALUES ('test@example.com', 0);
|
|
||||||
|
|
||||||
-- example of giving this user pull access to a repository
|
|
||||||
--INSERT OR IGNORE INTO user_repo_permissions (email, repository_name, repository_permissions) VALUES ('test@example.com', 'admin/alpine', 1);
|
|
|
@ -3,11 +3,10 @@
|
||||||
- [x] ldap auth
|
- [x] ldap auth
|
||||||
- [ ] permission stuff
|
- [ ] permission stuff
|
||||||
- [ ] simple way to define users and their permissions through a "users.toml"
|
- [ ] simple way to define users and their permissions through a "users.toml"
|
||||||
- [x] Only allow users to create repositories if its the same name as their username, or if they're an admin
|
- [ ] Only allow users to create repositories if its the same name as their username, or if they're an admin
|
||||||
- [x] Only allow users to pull from their own repositories
|
- [ ] Only allow users to pull from their own repositories
|
||||||
- [ ] postgresql
|
- [ ] postgresql
|
||||||
- [ ] prometheus metrics
|
- [ ] prometheus metrics
|
||||||
- [ ] simple webui for managing the registry
|
- [ ] simple webui for managing the registry
|
||||||
- [x] streaming layer bytes into providers
|
- [x] streaming layer bytes into providers
|
||||||
- [x] streaming layer bytes from providers
|
- [x] streaming layer bytes from providers
|
||||||
- [ ] better client error messages
|
|
|
@ -187,11 +187,7 @@ pub async fn auth_basic_get(basic_auth: Option<AuthBasic>, state: State<Arc<AppS
|
||||||
|
|
||||||
let now = SystemTime::now();
|
let now = SystemTime::now();
|
||||||
let token = create_jwt_token(account)
|
let token = create_jwt_token(account)
|
||||||
.map_err(|_| {
|
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||||
error!("Failed to create jwt token!");
|
|
||||||
|
|
||||||
StatusCode::INTERNAL_SERVER_ERROR
|
|
||||||
})?;
|
|
||||||
let token_str = token.token;
|
let token_str = token.token;
|
||||||
|
|
||||||
debug!("Created jwt token");
|
debug!("Created jwt token");
|
||||||
|
@ -212,11 +208,7 @@ pub async fn auth_basic_get(basic_auth: Option<AuthBasic>, state: State<Arc<AppS
|
||||||
|
|
||||||
let database = &state.database;
|
let database = &state.database;
|
||||||
database.store_user_token(token_str.clone(), account.clone(), token.expiry, token.created_at).await
|
database.store_user_token(token_str.clone(), account.clone(), token.expiry, token.created_at).await
|
||||||
.map_err(|_| {
|
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||||
error!("Failed to store user token in database!");
|
|
||||||
|
|
||||||
StatusCode::INTERNAL_SERVER_ERROR
|
|
||||||
})?;
|
|
||||||
drop(database);
|
drop(database);
|
||||||
|
|
||||||
return Ok((
|
return Ok((
|
||||||
|
|
|
@ -8,7 +8,7 @@ use axum::response::{IntoResponse, Response};
|
||||||
use tokio_util::io::ReaderStream;
|
use tokio_util::io::ReaderStream;
|
||||||
|
|
||||||
use crate::app_state::AppState;
|
use crate::app_state::AppState;
|
||||||
use crate::auth::access_denied_response;
|
use crate::auth::unauthenticated_response;
|
||||||
use crate::dto::RepositoryVisibility;
|
use crate::dto::RepositoryVisibility;
|
||||||
use crate::dto::user::{Permission, UserAuth};
|
use crate::dto::user::{Permission, UserAuth};
|
||||||
use crate::error::AppError;
|
use crate::error::AppError;
|
||||||
|
@ -17,7 +17,7 @@ pub async fn digest_exists_head(Path((name, layer_digest)): Path<(String, String
|
||||||
// Check if the user has permission to pull, or that the repository is public
|
// Check if the user has permission to pull, or that the repository is public
|
||||||
let mut auth_driver = state.auth_checker.lock().await;
|
let mut auth_driver = state.auth_checker.lock().await;
|
||||||
if !auth_driver.user_has_permission(auth.user.username, name.clone(), Permission::PULL, Some(RepositoryVisibility::Public)).await? {
|
if !auth_driver.user_has_permission(auth.user.username, name.clone(), Permission::PULL, Some(RepositoryVisibility::Public)).await? {
|
||||||
return Ok(access_denied_response(&state.config));
|
return Ok(unauthenticated_response(&state.config));
|
||||||
}
|
}
|
||||||
drop(auth_driver);
|
drop(auth_driver);
|
||||||
|
|
||||||
|
@ -42,7 +42,7 @@ pub async fn pull_digest_get(Path((name, layer_digest)): Path<(String, String)>,
|
||||||
// Check if the user has permission to pull, or that the repository is public
|
// Check if the user has permission to pull, or that the repository is public
|
||||||
let mut auth_driver = state.auth_checker.lock().await;
|
let mut auth_driver = state.auth_checker.lock().await;
|
||||||
if !auth_driver.user_has_permission(auth.user.username, name.clone(), Permission::PULL, Some(RepositoryVisibility::Public)).await? {
|
if !auth_driver.user_has_permission(auth.user.username, name.clone(), Permission::PULL, Some(RepositoryVisibility::Public)).await? {
|
||||||
return Ok(access_denied_response(&state.config));
|
return Ok(unauthenticated_response(&state.config));
|
||||||
}
|
}
|
||||||
drop(auth_driver);
|
drop(auth_driver);
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ use axum::http::{StatusCode, HeaderName, header};
|
||||||
use tracing::log::warn;
|
use tracing::log::warn;
|
||||||
use tracing::{debug, info};
|
use tracing::{debug, info};
|
||||||
|
|
||||||
use crate::auth::access_denied_response;
|
use crate::auth::unauthenticated_response;
|
||||||
use crate::app_state::AppState;
|
use crate::app_state::AppState;
|
||||||
use crate::database::Database;
|
use crate::database::Database;
|
||||||
use crate::dto::RepositoryVisibility;
|
use crate::dto::RepositoryVisibility;
|
||||||
|
@ -19,7 +19,7 @@ use crate::error::AppError;
|
||||||
pub async fn upload_manifest_put(Path((name, reference)): Path<(String, String)>, state: State<Arc<AppState>>, Extension(auth): Extension<UserAuth>, body: String) -> Result<Response, AppError> {
|
pub async fn upload_manifest_put(Path((name, reference)): Path<(String, String)>, state: State<Arc<AppState>>, Extension(auth): Extension<UserAuth>, body: String) -> Result<Response, AppError> {
|
||||||
let mut auth_driver = state.auth_checker.lock().await;
|
let mut auth_driver = state.auth_checker.lock().await;
|
||||||
if !auth_driver.user_has_permission(auth.user.username, name.clone(), Permission::PUSH, None).await? {
|
if !auth_driver.user_has_permission(auth.user.username, name.clone(), Permission::PUSH, None).await? {
|
||||||
return Ok(access_denied_response(&state.config));
|
return Ok(unauthenticated_response(&state.config));
|
||||||
}
|
}
|
||||||
drop(auth_driver);
|
drop(auth_driver);
|
||||||
|
|
||||||
|
@ -30,7 +30,7 @@ pub async fn upload_manifest_put(Path((name, reference)): Path<(String, String)>
|
||||||
let database = &state.database;
|
let database = &state.database;
|
||||||
|
|
||||||
// Create the image repository and save the image manifest. This repository will be private by default
|
// Create the image repository and save the image manifest. This repository will be private by default
|
||||||
database.save_repository(&name, RepositoryVisibility::Private, Some(auth.user.email), None).await?;
|
database.save_repository(&name, RepositoryVisibility::Private, None).await?;
|
||||||
database.save_manifest(&name, &calculated_digest, &body).await?;
|
database.save_manifest(&name, &calculated_digest, &body).await?;
|
||||||
|
|
||||||
// If the reference is not a digest, then it must be a tag name.
|
// If the reference is not a digest, then it must be a tag name.
|
||||||
|
@ -68,7 +68,7 @@ pub async fn pull_manifest_get(Path((name, reference)): Path<(String, String)>,
|
||||||
// Check if the user has permission to pull, or that the repository is public
|
// Check if the user has permission to pull, or that the repository is public
|
||||||
let mut auth_driver = state.auth_checker.lock().await;
|
let mut auth_driver = state.auth_checker.lock().await;
|
||||||
if !auth_driver.user_has_permission(auth.user.username, name.clone(), Permission::PULL, Some(RepositoryVisibility::Public)).await? {
|
if !auth_driver.user_has_permission(auth.user.username, name.clone(), Permission::PULL, Some(RepositoryVisibility::Public)).await? {
|
||||||
return Ok(access_denied_response(&state.config));
|
return Ok(unauthenticated_response(&state.config));
|
||||||
}
|
}
|
||||||
drop(auth_driver);
|
drop(auth_driver);
|
||||||
|
|
||||||
|
@ -111,7 +111,7 @@ pub async fn manifest_exists_head(Path((name, reference)): Path<(String, String)
|
||||||
// Check if the user has permission to pull, or that the repository is public
|
// Check if the user has permission to pull, or that the repository is public
|
||||||
let mut auth_driver = state.auth_checker.lock().await;
|
let mut auth_driver = state.auth_checker.lock().await;
|
||||||
if !auth_driver.user_has_permission(auth.user.username, name.clone(), Permission::PULL, Some(RepositoryVisibility::Public)).await? {
|
if !auth_driver.user_has_permission(auth.user.username, name.clone(), Permission::PULL, Some(RepositoryVisibility::Public)).await? {
|
||||||
return Ok(access_denied_response(&state.config));
|
return Ok(unauthenticated_response(&state.config));
|
||||||
}
|
}
|
||||||
drop(auth_driver);
|
drop(auth_driver);
|
||||||
|
|
||||||
|
@ -151,7 +151,7 @@ pub async fn manifest_exists_head(Path((name, reference)): Path<(String, String)
|
||||||
pub async fn delete_manifest(Path((name, reference)): Path<(String, String)>, state: State<Arc<AppState>>, Extension(auth): Extension<UserAuth>) -> Result<Response, AppError> {
|
pub async fn delete_manifest(Path((name, reference)): Path<(String, String)>, state: State<Arc<AppState>>, Extension(auth): Extension<UserAuth>) -> Result<Response, AppError> {
|
||||||
let mut auth_driver = state.auth_checker.lock().await;
|
let mut auth_driver = state.auth_checker.lock().await;
|
||||||
if !auth_driver.user_has_permission(auth.user.username, name.clone(), Permission::PUSH, None).await? {
|
if !auth_driver.user_has_permission(auth.user.username, name.clone(), Permission::PUSH, None).await? {
|
||||||
return Ok(access_denied_response(&state.config));
|
return Ok(unauthenticated_response(&state.config));
|
||||||
}
|
}
|
||||||
drop(auth_driver);
|
drop(auth_driver);
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,7 @@ use futures::StreamExt;
|
||||||
use tracing::{debug, warn};
|
use tracing::{debug, warn};
|
||||||
|
|
||||||
use crate::app_state::AppState;
|
use crate::app_state::AppState;
|
||||||
use crate::auth::access_denied_response;
|
use crate::auth::unauthenticated_response;
|
||||||
use crate::byte_stream::ByteStream;
|
use crate::byte_stream::ByteStream;
|
||||||
use crate::dto::user::{UserAuth, Permission};
|
use crate::dto::user::{UserAuth, Permission};
|
||||||
use crate::error::AppError;
|
use crate::error::AppError;
|
||||||
|
@ -35,13 +35,13 @@ pub async fn start_upload_post(Path((name, )): Path<(String, )>, Extension(auth)
|
||||||
).into_response());
|
).into_response());
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(access_denied_response(&state.config))
|
Ok(unauthenticated_response(&state.config))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn chunked_upload_layer_patch(Path((name, layer_uuid)): Path<(String, String)>, Extension(auth): Extension<UserAuth>, state: State<Arc<AppState>>, mut body: BodyStream) -> Result<Response, AppError> {
|
pub async fn chunked_upload_layer_patch(Path((name, layer_uuid)): Path<(String, String)>, Extension(auth): Extension<UserAuth>, state: State<Arc<AppState>>, mut body: BodyStream) -> Result<Response, AppError> {
|
||||||
let mut auth_driver = state.auth_checker.lock().await;
|
let mut auth_driver = state.auth_checker.lock().await;
|
||||||
if !auth_driver.user_has_permission(auth.user.username, name.clone(), Permission::PUSH, None).await? {
|
if !auth_driver.user_has_permission(auth.user.username, name.clone(), Permission::PUSH, None).await? {
|
||||||
return Ok(access_denied_response(&state.config));
|
return Ok(unauthenticated_response(&state.config));
|
||||||
}
|
}
|
||||||
drop(auth_driver);
|
drop(auth_driver);
|
||||||
|
|
||||||
|
@ -100,7 +100,7 @@ pub async fn chunked_upload_layer_patch(Path((name, layer_uuid)): Path<(String,
|
||||||
pub async fn finish_chunked_upload_put(Path((name, layer_uuid)): Path<(String, String)>, Query(query): Query<HashMap<String, String>>, Extension(auth): Extension<UserAuth>, state: State<Arc<AppState>>, body: Bytes) -> Result<Response, AppError> {
|
pub async fn finish_chunked_upload_put(Path((name, layer_uuid)): Path<(String, String)>, Query(query): Query<HashMap<String, String>>, Extension(auth): Extension<UserAuth>, state: State<Arc<AppState>>, body: Bytes) -> Result<Response, AppError> {
|
||||||
let mut auth_driver = state.auth_checker.lock().await;
|
let mut auth_driver = state.auth_checker.lock().await;
|
||||||
if !auth_driver.user_has_permission(auth.user.username, name.clone(), Permission::PUSH, None).await? {
|
if !auth_driver.user_has_permission(auth.user.username, name.clone(), Permission::PUSH, None).await? {
|
||||||
return Ok(access_denied_response(&state.config));
|
return Ok(unauthenticated_response(&state.config));
|
||||||
}
|
}
|
||||||
drop(auth_driver);
|
drop(auth_driver);
|
||||||
|
|
||||||
|
@ -129,7 +129,7 @@ pub async fn finish_chunked_upload_put(Path((name, layer_uuid)): Path<(String, S
|
||||||
pub async fn cancel_upload_delete(Path((name, layer_uuid)): Path<(String, String)>, state: State<Arc<AppState>>, Extension(auth): Extension<UserAuth>) -> Result<Response, AppError> {
|
pub async fn cancel_upload_delete(Path((name, layer_uuid)): Path<(String, String)>, state: State<Arc<AppState>>, Extension(auth): Extension<UserAuth>) -> Result<Response, AppError> {
|
||||||
let mut auth_driver = state.auth_checker.lock().await;
|
let mut auth_driver = state.auth_checker.lock().await;
|
||||||
if !auth_driver.user_has_permission(auth.user.username, name.clone(), Permission::PUSH, None).await? {
|
if !auth_driver.user_has_permission(auth.user.username, name.clone(), Permission::PUSH, None).await? {
|
||||||
return Ok(access_denied_response(&state.config));
|
return Ok(unauthenticated_response(&state.config));
|
||||||
}
|
}
|
||||||
drop(auth_driver);
|
drop(auth_driver);
|
||||||
|
|
||||||
|
@ -143,7 +143,7 @@ pub async fn cancel_upload_delete(Path((name, layer_uuid)): Path<(String, String
|
||||||
pub async fn check_upload_status_get(Path((name, layer_uuid)): Path<(String, String)>, state: State<Arc<AppState>>, Extension(auth): Extension<UserAuth>) -> Result<Response, AppError> {
|
pub async fn check_upload_status_get(Path((name, layer_uuid)): Path<(String, String)>, state: State<Arc<AppState>>, Extension(auth): Extension<UserAuth>) -> Result<Response, AppError> {
|
||||||
let mut auth_driver = state.auth_checker.lock().await;
|
let mut auth_driver = state.auth_checker.lock().await;
|
||||||
if !auth_driver.user_has_permission(auth.user.username, name.clone(), Permission::PUSH, None).await? {
|
if !auth_driver.user_has_permission(auth.user.username, name.clone(), Permission::PUSH, None).await? {
|
||||||
return Ok(access_denied_response(&state.config));
|
return Ok(unauthenticated_response(&state.config));
|
||||||
}
|
}
|
||||||
drop(auth_driver);
|
drop(auth_driver);
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@ use ldap3::{LdapConnAsync, Ldap, Scope, SearchEntry};
|
||||||
use sqlx::{Pool, Sqlite};
|
use sqlx::{Pool, Sqlite};
|
||||||
use tracing::{debug, warn};
|
use tracing::{debug, warn};
|
||||||
|
|
||||||
use crate::{config::LdapConnectionConfig, dto::{user::{Permission, LoginSource, RegistryUserType, self}, RepositoryVisibility}, database::Database};
|
use crate::{config::LdapConnectionConfig, dto::{user::{Permission, LoginSource, RegistryUserType}, RepositoryVisibility}, database::Database};
|
||||||
|
|
||||||
use super::AuthDriver;
|
use super::AuthDriver;
|
||||||
|
|
||||||
|
@ -60,7 +60,6 @@ impl AuthDriver for LdapAuthDriver {
|
||||||
Ok(true)
|
Ok(true)
|
||||||
} else {
|
} else {
|
||||||
debug!("LDAP is falling back to database");
|
debug!("LDAP is falling back to database");
|
||||||
|
|
||||||
// fall back to database auth since this user might be local
|
// fall back to database auth since this user might be local
|
||||||
self.database.user_has_permission(email, repository, permission, required_visibility).await
|
self.database.user_has_permission(email, repository, permission, required_visibility).await
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,25 +24,14 @@ pub trait AuthDriver: Send + Sync {
|
||||||
async fn verify_user_login(&mut self, email: String, password: String) -> anyhow::Result<bool>;
|
async fn verify_user_login(&mut self, email: String, password: String) -> anyhow::Result<bool>;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Implement AuthDriver for anything the implements Database
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl<T> AuthDriver for T
|
impl AuthDriver for Pool<Sqlite> {
|
||||||
where
|
|
||||||
T: Database + Send + Sync
|
|
||||||
{
|
|
||||||
async fn user_has_permission(&mut self, email: String, repository: String, permission: Permission, required_visibility: Option<RepositoryVisibility>) -> anyhow::Result<bool> {
|
async fn user_has_permission(&mut self, email: String, repository: String, permission: Permission, required_visibility: Option<RepositoryVisibility>) -> anyhow::Result<bool> {
|
||||||
let allowed_to: bool = {
|
let allowed_to = {
|
||||||
if self.get_repository_owner(&repository).await?
|
match self.get_user_registry_type(email.clone()).await? {
|
||||||
.map_or(false, |owner| owner == email) {
|
Some(RegistryUserType::Admin) => true,
|
||||||
|
_ => {
|
||||||
debug!("Allowing request, user is owner of repository");
|
check_user_permissions(self, email, repository, permission, required_visibility).await?
|
||||||
true
|
|
||||||
} else {
|
|
||||||
match self.get_user_registry_type(email.clone()).await? {
|
|
||||||
Some(RegistryUserType::Admin) => true,
|
|
||||||
_ => {
|
|
||||||
check_user_permissions(self, email, repository, permission, required_visibility).await?
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -130,8 +119,6 @@ pub async fn require_auth<B>(State(state): State<Arc<AppState>>, mut request: Re
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a response with an Unauthorized (401) status code.
|
|
||||||
/// The www-authenticate header is set to notify the client of where to authorize with.
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn unauthenticated_response(config: &Config) -> Response {
|
pub fn unauthenticated_response(config: &Config) -> Response {
|
||||||
let bearer = format!("Bearer realm=\"{}/auth\"", config.get_url());
|
let bearer = format!("Bearer realm=\"{}/auth\"", config.get_url());
|
||||||
|
@ -142,16 +129,4 @@ pub fn unauthenticated_response(config: &Config) -> Response {
|
||||||
( HeaderName::from_static("docker-distribution-api-version"), "registry/2.0".to_string() )
|
( HeaderName::from_static("docker-distribution-api-version"), "registry/2.0".to_string() )
|
||||||
]
|
]
|
||||||
).into_response()
|
).into_response()
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates a response with a Forbidden (403) status code.
|
|
||||||
/// No other headers are set.
|
|
||||||
#[inline(always)]
|
|
||||||
pub fn access_denied_response(config: &Config) -> Response {
|
|
||||||
(
|
|
||||||
StatusCode::FORBIDDEN,
|
|
||||||
[
|
|
||||||
( HeaderName::from_static("docker-distribution-api-version"), "registry/2.0".to_string() )
|
|
||||||
]
|
|
||||||
).into_response()
|
|
||||||
}
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use sqlx::{Sqlite, Pool};
|
use sqlx::{Sqlite, Pool};
|
||||||
use tracing::{debug, warn};
|
use tracing::debug;
|
||||||
|
|
||||||
use chrono::{DateTime, Utc, NaiveDateTime, TimeZone};
|
use chrono::{DateTime, Utc, NaiveDateTime, TimeZone};
|
||||||
|
|
||||||
|
@ -42,9 +42,8 @@ pub trait Database {
|
||||||
|
|
||||||
async fn has_repository(&self, repository: &str) -> anyhow::Result<bool>;
|
async fn has_repository(&self, repository: &str) -> anyhow::Result<bool>;
|
||||||
async fn get_repository_visibility(&self, repository: &str) -> anyhow::Result<Option<RepositoryVisibility>>;
|
async fn get_repository_visibility(&self, repository: &str) -> anyhow::Result<Option<RepositoryVisibility>>;
|
||||||
async fn get_repository_owner(&self, repository: &str) -> anyhow::Result<Option<String>>;
|
|
||||||
/// Create a repository
|
/// Create a repository
|
||||||
async fn save_repository(&self, repository: &str, visibility: RepositoryVisibility, owner_email: Option<String>, owning_project: Option<String>) -> anyhow::Result<()>;
|
async fn save_repository(&self, repository: &str, visibility: RepositoryVisibility, owning_project: Option<String>) -> anyhow::Result<()>;
|
||||||
/// List all repositories.
|
/// List all repositories.
|
||||||
/// If limit is not specified, a default limit of 1000 will be returned.
|
/// If limit is not specified, a default limit of 1000 will be returned.
|
||||||
async fn list_repositories(&self, limit: Option<u32>, last_repo: Option<String>) -> anyhow::Result<Vec<String>>;
|
async fn list_repositories(&self, limit: Option<u32>, last_repo: Option<String>) -> anyhow::Result<Vec<String>>;
|
||||||
|
@ -53,7 +52,6 @@ pub trait Database {
|
||||||
/// User stuff
|
/// User stuff
|
||||||
async fn does_user_exist(&self, email: String) -> anyhow::Result<bool>;
|
async fn does_user_exist(&self, email: String) -> anyhow::Result<bool>;
|
||||||
async fn create_user(&self, email: String, username: String, login_source: LoginSource) -> anyhow::Result<User>;
|
async fn create_user(&self, email: String, username: String, login_source: LoginSource) -> anyhow::Result<User>;
|
||||||
async fn get_user(&self, email: String) -> anyhow::Result<Option<User>>;
|
|
||||||
async fn add_user_auth(&self, email: String, password_hash: String, password_salt: String) -> anyhow::Result<()>;
|
async fn add_user_auth(&self, email: String, password_hash: String, password_salt: String) -> anyhow::Result<()>;
|
||||||
async fn set_user_registry_type(&self, email: String, user_type: RegistryUserType) -> anyhow::Result<()>;
|
async fn set_user_registry_type(&self, email: String, user_type: RegistryUserType) -> anyhow::Result<()>;
|
||||||
async fn verify_user_login(&self, email: String, password: String) -> anyhow::Result<bool>;
|
async fn verify_user_login(&self, email: String, password: String) -> anyhow::Result<bool>;
|
||||||
|
@ -256,7 +254,7 @@ impl Database for Pool<Sqlite> {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_repository_visibility(&self, repository: &str) -> anyhow::Result<Option<RepositoryVisibility>> {
|
async fn get_repository_visibility(&self, repository: &str) -> anyhow::Result<Option<RepositoryVisibility>> {
|
||||||
let row: (u32, ) = match sqlx::query_as("SELECT visibility FROM repositories WHERE name = ?")
|
let row: (u32, ) = match sqlx::query_as("SELECT visibility FROM repositories WHERE 'name' = ?")
|
||||||
.bind(repository)
|
.bind(repository)
|
||||||
.fetch_one(self).await {
|
.fetch_one(self).await {
|
||||||
Ok(row) => row,
|
Ok(row) => row,
|
||||||
|
@ -273,42 +271,29 @@ impl Database for Pool<Sqlite> {
|
||||||
Ok(Some(RepositoryVisibility::try_from(row.0)?))
|
Ok(Some(RepositoryVisibility::try_from(row.0)?))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_repository_owner(&self, repository: &str) -> anyhow::Result<Option<String>> {
|
async fn save_repository(&self, repository: &str, visibility: RepositoryVisibility, owning_project: Option<String>) -> anyhow::Result<()> {
|
||||||
let row: (String, ) = match sqlx::query_as("SELECT owner_email FROM repositories WHERE name = ?")
|
|
||||||
.bind(repository)
|
|
||||||
.fetch_one(self).await {
|
|
||||||
Ok(row) => row,
|
|
||||||
Err(e) => match e {
|
|
||||||
sqlx::Error::RowNotFound => {
|
|
||||||
return Ok(None)
|
|
||||||
},
|
|
||||||
_ => {
|
|
||||||
debug!("here's the error: {:?}", e);
|
|
||||||
return Err(anyhow::Error::new(e));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(Some(row.0))
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn save_repository(&self, repository: &str, visibility: RepositoryVisibility, owner_email: Option<String>, owning_project: Option<String>) -> anyhow::Result<()> {
|
|
||||||
// ensure that the repository was not already created
|
// ensure that the repository was not already created
|
||||||
if self.has_repository(repository).await? {
|
if self.has_repository(repository).await? {
|
||||||
debug!("Skipping creation of repository since it already exists");
|
debug!("repo exists");
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
debug!("repo does not exist");
|
||||||
|
|
||||||
// unwrap None values to empty for inserting into database
|
match owning_project {
|
||||||
let owner_email = owner_email.unwrap_or(String::new());
|
Some(owner) => {
|
||||||
let owning_project = owning_project.unwrap_or(String::new());
|
sqlx::query("INSERT INTO repositories (name, visibility, owning_project) VALUES (?, ?, ?)")
|
||||||
|
.bind(repository)
|
||||||
sqlx::query("INSERT INTO repositories (name, visibility, owner_email, owning_project) VALUES (?, ?, ?, ?)")
|
.bind(visibility as u32)
|
||||||
.bind(repository)
|
.bind(owner)
|
||||||
.bind(visibility as u32)
|
.execute(self).await?;
|
||||||
.bind(owner_email)
|
},
|
||||||
.bind(owning_project)
|
None => {
|
||||||
.execute(self).await?;
|
sqlx::query("INSERT INTO repositories (name, visibility) VALUES (?, ?)")
|
||||||
|
.bind(repository)
|
||||||
|
.bind(visibility as u32)
|
||||||
|
.execute(self).await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -368,25 +353,6 @@ impl Database for Pool<Sqlite> {
|
||||||
Ok(User::new(username, email, login_source))
|
Ok(User::new(username, email, login_source))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_user(&self, email: String) -> anyhow::Result<Option<User>> {
|
|
||||||
let email = email.to_lowercase();
|
|
||||||
let row: (String, u32) = match sqlx::query_as("SELECT username, login_source FROM users WHERE email = ?")
|
|
||||||
.bind(email.clone())
|
|
||||||
.fetch_one(self).await {
|
|
||||||
Ok(row) => row,
|
|
||||||
Err(e) => match e {
|
|
||||||
sqlx::Error::RowNotFound => {
|
|
||||||
return Ok(None)
|
|
||||||
},
|
|
||||||
_ => {
|
|
||||||
return Err(anyhow::Error::new(e));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(Some(User::new(row.0, email, LoginSource::try_from(row.1)?)))
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn add_user_auth(&self, email: String, password_hash: String, password_salt: String) -> anyhow::Result<()> {
|
async fn add_user_auth(&self, email: String, password_hash: String, password_salt: String) -> anyhow::Result<()> {
|
||||||
let email = email.to_lowercase();
|
let email = email.to_lowercase();
|
||||||
sqlx::query("INSERT INTO user_logins (email, password_hash, password_salt) VALUES (?, ?, ?)")
|
sqlx::query("INSERT INTO user_logins (email, password_hash, password_salt) VALUES (?, ?, ?)")
|
||||||
|
@ -410,7 +376,7 @@ impl Database for Pool<Sqlite> {
|
||||||
|
|
||||||
async fn verify_user_login(&self, email: String, password: String) -> anyhow::Result<bool> {
|
async fn verify_user_login(&self, email: String, password: String) -> anyhow::Result<bool> {
|
||||||
let email = email.to_lowercase();
|
let email = email.to_lowercase();
|
||||||
let row: (String, ) = sqlx::query_as("SELECT password_hash FROM user_logins WHERE email = ?")
|
let row: (String, ) = sqlx::query_as("SELECT password_hash FROM users WHERE email = ?")
|
||||||
.bind(email)
|
.bind(email)
|
||||||
.fetch_one(self).await?;
|
.fetch_one(self).await?;
|
||||||
|
|
||||||
|
@ -439,8 +405,6 @@ impl Database for Pool<Sqlite> {
|
||||||
|
|
||||||
async fn get_user_repo_permissions(&self, email: String, repository: String) -> anyhow::Result<Option<RepositoryPermissions>> {
|
async fn get_user_repo_permissions(&self, email: String, repository: String) -> anyhow::Result<Option<RepositoryPermissions>> {
|
||||||
let email = email.to_lowercase();
|
let email = email.to_lowercase();
|
||||||
|
|
||||||
debug!("email: {email}, repo: {repository}");
|
|
||||||
|
|
||||||
let row: (u32, ) = match sqlx::query_as("SELECT repository_permissions FROM user_repo_permissions WHERE email = ? AND repository_name = ?")
|
let row: (u32, ) = match sqlx::query_as("SELECT repository_permissions FROM user_repo_permissions WHERE email = ? AND repository_name = ?")
|
||||||
.bind(email.clone())
|
.bind(email.clone())
|
||||||
|
@ -459,17 +423,13 @@ impl Database for Pool<Sqlite> {
|
||||||
|
|
||||||
let vis = match self.get_repository_visibility(&repository).await? {
|
let vis = match self.get_repository_visibility(&repository).await? {
|
||||||
Some(v) => v,
|
Some(v) => v,
|
||||||
None => {
|
None => return Ok(None),
|
||||||
warn!("Failure to find visibility for repository '{}'", repository);
|
|
||||||
return Ok(None)
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Also get the user type for the registry, if its admin return admin repository permissions
|
// Also get the user type for the registry, if its admin return admin repository permissions
|
||||||
let utype = match self.get_user_registry_usertype(email).await? {
|
let utype = match self.get_user_registry_usertype(email).await? {
|
||||||
Some(t) => t,
|
Some(t) => t,
|
||||||
// assume a regular user is their type is not found
|
None => return Ok(None),
|
||||||
None => RegistryUserType::Regular,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if utype == RegistryUserType::Admin {
|
if utype == RegistryUserType::Admin {
|
||||||
|
|
|
@ -7,7 +7,6 @@ CREATE TABLE IF NOT EXISTS projects (
|
||||||
CREATE TABLE IF NOT EXISTS repositories (
|
CREATE TABLE IF NOT EXISTS repositories (
|
||||||
name TEXT NOT NULL UNIQUE PRIMARY KEY,
|
name TEXT NOT NULL UNIQUE PRIMARY KEY,
|
||||||
owning_project TEXT,
|
owning_project TEXT,
|
||||||
owner_email TEXT,
|
|
||||||
-- 0 = private, 1 = public
|
-- 0 = private, 1 = public
|
||||||
visibility INTEGER NOT NULL
|
visibility INTEGER NOT NULL
|
||||||
);
|
);
|
||||||
|
@ -73,5 +72,4 @@ CREATE TABLE IF NOT EXISTS user_tokens (
|
||||||
|
|
||||||
-- create admin user
|
-- create admin user
|
||||||
INSERT OR IGNORE INTO users (username, email, login_source) VALUES ('admin', 'admin@example.com', 0);
|
INSERT OR IGNORE INTO users (username, email, login_source) VALUES ('admin', 'admin@example.com', 0);
|
||||||
INSERT OR IGNORE INTO user_logins (email, password_hash, password_salt) VALUES ('admin@example.com', '$2y$05$ZBnzGzctboHkUDMr4W02jOaUuPwmRC2OgWKKBxqiQsYv53OkUrfO6', 'x5ECk0jUmOSfBWxW52wsyO');
|
INSERT OR IGNORE INTO user_logins (email, password_hash, password_salt) VALUES ('admin@example.com', '$2b$12$x5ECk0jUmOSfBWxW52wsyOmFxNZkwc2J9FH225if4eBnQYUvYLYYq', 'x5ECk0jUmOSfBWxW52wsyO');
|
||||||
INSERT OR IGNORE INTO user_registry_permissions (email, user_type) VALUES ('admin@example.com', 1);
|
|
|
@ -25,11 +25,6 @@ impl FilesystemDriver {
|
||||||
fn get_digest_path(&self, digest: &str) -> String {
|
fn get_digest_path(&self, digest: &str) -> String {
|
||||||
format!("{}/{}", self.storage_path, digest)
|
format!("{}/{}", self.storage_path, digest)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ensure_storage_path(&self) -> std::io::Result<()>
|
|
||||||
{
|
|
||||||
std::fs::create_dir_all(&self.storage_path)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
|
@ -45,8 +40,6 @@ impl StorageDriver for FilesystemDriver {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn save_digest_stream(&self, digest: &str, mut stream: ByteStream, append: bool) -> anyhow::Result<usize> {
|
async fn save_digest_stream(&self, digest: &str, mut stream: ByteStream, append: bool) -> anyhow::Result<usize> {
|
||||||
self.ensure_storage_path()?;
|
|
||||||
|
|
||||||
let path = self.get_digest_path(digest);
|
let path = self.get_digest_path(digest);
|
||||||
let mut file = fs::OpenOptions::new()
|
let mut file = fs::OpenOptions::new()
|
||||||
.write(true)
|
.write(true)
|
||||||
|
|
Loading…
Reference in New Issue