You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by ma...@apache.org on 2024/03/06 02:32:12 UTC
(camel-karavan) branch main updated: Basic authentication
This is an automated email from the ASF dual-hosted git repository.
marat pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel-karavan.git
The following commit(s) were added to refs/heads/main by this push:
new 3b17180f Basic authentication
3b17180f is described below
commit 3b17180f9838c00695f21aa6a24db17cfe4b2293
Author: Marat Gubaidullin <ma...@talismancloud.io>
AuthorDate: Tue Mar 5 21:32:05 2024 -0500
Basic authentication
---
.../org/apache/camel/karavan/api/AuthResource.java | 50 ++++++++++++++----
.../apache/camel/karavan/api/UsersResource.java | 2 +-
karavan-app/src/main/webui/src/api/KaravanApi.tsx | 44 +++++++++++++++-
karavan-app/src/main/webui/src/api/LogWatchApi.tsx | 59 ++++++++++++----------
.../src/main/webui/src/api/NotificationApi.tsx | 20 +++++---
karavan-app/src/main/webui/src/api/ProjectStore.ts | 6 +++
6 files changed, 137 insertions(+), 44 deletions(-)
diff --git a/karavan-app/src/main/java/org/apache/camel/karavan/api/AuthResource.java b/karavan-app/src/main/java/org/apache/camel/karavan/api/AuthResource.java
index 00751702..a277360d 100644
--- a/karavan-app/src/main/java/org/apache/camel/karavan/api/AuthResource.java
+++ b/karavan-app/src/main/java/org/apache/camel/karavan/api/AuthResource.java
@@ -17,20 +17,18 @@
package org.apache.camel.karavan.api;
import jakarta.inject.Inject;
-import jakarta.ws.rs.GET;
-import jakarta.ws.rs.Path;
-import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
-import org.apache.camel.karavan.service.KaravanCacheService;
import org.apache.camel.karavan.kubernetes.KubernetesService;
import org.apache.camel.karavan.service.AuthService;
import org.apache.camel.karavan.service.ProjectService;
+import org.eclipse.microprofile.config.inject.ConfigProperty;
import org.eclipse.microprofile.health.HealthCheckResponse;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.*;
@Path("/public")
public class AuthResource {
@@ -44,8 +42,42 @@ public class AuthResource {
@Inject
KubernetesService kubernetesService;
- @Inject
- KaravanCacheService karavanCacheService;
+ @ConfigProperty(name = "quarkus.security.users.embedded.realm-name", defaultValue = "")
+ Optional<String> realm;
+
+ @ConfigProperty(name = "quarkus.security.users.embedded.users")
+ Optional<Map<String,String>> users;
+
+ public static String getMd5Hash(String input) throws NoSuchAlgorithmException {
+ MessageDigest md = MessageDigest.getInstance("MD5");
+ byte[] digest = md.digest(input.getBytes());
+ StringBuilder sb = new StringBuilder();
+ for (byte b : digest) {
+ sb.append(String.format("%02x", b));
+ }
+ return sb.toString();
+ }
+
+ @Path("/auth")
+ @POST
+ @Produces(MediaType.APPLICATION_JSON)
+ @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
+ public Response authenticateUser(@FormParam("username") String username, @FormParam("password") String password) {
+ try {
+ if (users.isPresent() && users.get().containsKey(username)) {
+ var pwdStored = users.get().get(username);
+ var pwdReceived = new String(Base64.getDecoder().decode(password));
+ var pwdString = username + ":" + realm.orElse("") + ":" + pwdReceived;
+ String pwdToCheck = getMd5Hash(pwdString);
+ if (Objects.equals(pwdToCheck, pwdStored)) {
+ return Response.ok().build();
+ }
+ }
+ return Response.status(Response.Status.FORBIDDEN).entity("Incorrect Username and/or Password!").build();
+ } catch (Exception e) {
+ return Response.status(Response.Status.FORBIDDEN).entity(e.getMessage()).build();
+ }
+ }
@GET
@Path("/auth")
diff --git a/karavan-app/src/main/java/org/apache/camel/karavan/api/UsersResource.java b/karavan-app/src/main/java/org/apache/camel/karavan/api/UsersResource.java
index f63c1eb0..36d0a12f 100644
--- a/karavan-app/src/main/java/org/apache/camel/karavan/api/UsersResource.java
+++ b/karavan-app/src/main/java/org/apache/camel/karavan/api/UsersResource.java
@@ -53,7 +53,7 @@ public class UsersResource {
UserInfo userInfo = (UserInfo) securityIdentity.getAttributes().get("userinfo");
this.userName = securityIdentity.getPrincipal().getName();
this.roles = securityIdentity.getRoles();
- this.displayName = userInfo.getName();
+ this.displayName = userInfo != null ? userInfo.getName() : userName;
}
public String getDisplayName() {
diff --git a/karavan-app/src/main/webui/src/api/KaravanApi.tsx b/karavan-app/src/main/webui/src/api/KaravanApi.tsx
index 8ef409dd..1738290f 100644
--- a/karavan-app/src/main/webui/src/api/KaravanApi.tsx
+++ b/karavan-app/src/main/webui/src/api/KaravanApi.tsx
@@ -27,6 +27,7 @@ import {
import {Buffer} from 'buffer';
import {SsoApi} from "./SsoApi";
import {v4 as uuidv4} from "uuid";
+import {useAppConfigStore} from "./ProjectStore";
const USER_ID_KEY = 'KARAVAN_USER_ID';
axios.defaults.headers.common['Accept'] = 'application/json';
@@ -38,6 +39,7 @@ export class KaravanApi {
static me?: any;
static authType?: string = undefined;
static isAuthorized: boolean = false;
+ static basicToken: string = '';
static getInstance() {
return instance;
@@ -59,6 +61,7 @@ export class KaravanApi {
}
static setAuthType(authType: string) {
+ console.log("setAuthType", authType)
KaravanApi.authType = authType;
switch (authType){
case "public": {
@@ -69,12 +72,26 @@ export class KaravanApi {
KaravanApi.setOidcAuthentication();
break;
}
+ case "basic": {
+ KaravanApi.setBasicAuthentication();
+ break;
+ }
}
}
static setPublicAuthentication() {
}
+ static setBasicAuthentication() {
+ instance.interceptors.request.use(async config => {
+ config.headers.Authorization = 'Basic ' + KaravanApi.basicToken;
+ return config;
+ },
+ error => {
+ Promise.reject(error)
+ });
+ }
+
static setOidcAuthentication() {
instance.interceptors.request.use(async config => {
config.headers.Authorization = 'Bearer ' + SsoApi.keycloak?.token;
@@ -106,6 +123,31 @@ export class KaravanApi {
});
}
+ static async auth(username: string, password: string, after: (ok: boolean, res: any) => void) {
+ instance.post('/public/auth',
+ {username: username, password: Buffer.from(password).toString('base64')},
+ {headers: {'content-type': 'application/x-www-form-urlencoded'}})
+ .then(res => {
+ if (res.status === 200) {
+ KaravanApi.isAuthorized = true;
+ KaravanApi.basicToken = Buffer.from(username + ":" + password).toString('base64');
+ KaravanApi.setBasicAuthentication();
+ KaravanApi.getMe(user => {
+ after(true, res);
+ useAppConfigStore.setState({isAuthorized: true})
+ })
+ } else if (res.status === 401) {
+ useAppConfigStore.setState({isAuthorized: false})
+ KaravanApi.basicToken = '';
+ after(false, res);
+ }
+ }).catch(err => {
+ KaravanApi.basicToken = '';
+ useAppConfigStore.setState({isAuthorized: false})
+ after(false, err);
+ });
+ }
+
static async getReadiness(after: (readiness: any) => void) {
axios.get('/public/readiness', {headers: {'Accept': 'application/json'}})
.then(res => {
@@ -291,7 +333,7 @@ export class KaravanApi {
.then(res => {
after(res);
}).catch(err => {
- after(err);
+ after(err);
});
}
diff --git a/karavan-app/src/main/webui/src/api/LogWatchApi.tsx b/karavan-app/src/main/webui/src/api/LogWatchApi.tsx
index 49f4b134..f761f7b3 100644
--- a/karavan-app/src/main/webui/src/api/LogWatchApi.tsx
+++ b/karavan-app/src/main/webui/src/api/LogWatchApi.tsx
@@ -25,33 +25,40 @@ export class LogWatchApi {
static async fetchData(type: 'container' | 'build' | 'none', podName: string, controller: AbortController) {
const fetchData = async () => {
const headers: any = { Accept: "text/event-stream" };
- if (KaravanApi.authType === 'oidc') {
- headers.Authorization = "Bearer " + SsoApi.keycloak?.token
+ let ready = false;
+ if (KaravanApi.authType === 'oidc' && SsoApi.keycloak?.token && SsoApi.keycloak?.token?.length > 0) {
+ headers.Authorization = "Bearer " + SsoApi.keycloak?.token;
+ ready = true;
+ } else if (KaravanApi.authType === 'basic' && KaravanApi.basicToken?.length > 0) {
+ headers.Authorization = "Basic " + KaravanApi.basicToken
+ ready = true;
+ }
+ if (ready) {
+ await fetchEventSource("/api/logwatch/" + type + "/" + podName, {
+ method: "GET",
+ headers: headers,
+ signal: controller.signal,
+ async onopen(response) {
+ if (response.ok && response.headers.get('content-type') === EventStreamContentType) {
+ return; // everything's good
+ } else if (response.status >= 400 && response.status < 500 && response.status !== 429) {
+ // client-side errors are usually non-retriable:
+ console.log("Server side error ", response);
+ } else {
+ console.log("Error ", response);
+ }
+ },
+ onmessage(event) {
+ ProjectEventBus.sendLog('add', event.data);
+ },
+ onclose() {
+ console.log("Connection closed by the server");
+ },
+ onerror(err) {
+ console.log("There was an error from server", err);
+ },
+ });
}
- await fetchEventSource("/api/logwatch/" + type + "/" + podName, {
- method: "GET",
- headers: headers,
- signal: controller.signal,
- async onopen(response) {
- if (response.ok && response.headers.get('content-type') === EventStreamContentType) {
- return; // everything's good
- } else if (response.status >= 400 && response.status < 500 && response.status !== 429) {
- // client-side errors are usually non-retriable:
- console.log("Server side error ", response);
- } else {
- console.log("Error ", response);
- }
- },
- onmessage(event) {
- ProjectEventBus.sendLog('add', event.data);
- },
- onclose() {
- console.log("Connection closed by the server");
- },
- onerror(err) {
- console.log("There was an error from server", err);
- },
- });
};
return fetchData();
}
diff --git a/karavan-app/src/main/webui/src/api/NotificationApi.tsx b/karavan-app/src/main/webui/src/api/NotificationApi.tsx
index fd41bc65..c4971e17 100644
--- a/karavan-app/src/main/webui/src/api/NotificationApi.tsx
+++ b/karavan-app/src/main/webui/src/api/NotificationApi.tsx
@@ -18,13 +18,12 @@
import {SsoApi} from "./SsoApi";
import {EventStreamContentType, fetchEventSource} from "@microsoft/fetch-event-source";
import {KaravanApi} from "./KaravanApi";
-import {EventBus} from "../designer/utils/EventBus";
import {EventSourceMessage} from "@microsoft/fetch-event-source/lib/cjs/parse";
import {KaravanEvent, NotificationEventBus} from "./NotificationService";
export class NotificationApi {
- static getKaravanEvent (ev: EventSourceMessage, type: 'system' | 'user') {
+ static getKaravanEvent (ev: EventSourceMessage, type: 'system' | 'user') {
const eventParts = ev.event?.split(':');
const event = eventParts?.length > 1 ? eventParts[0] : undefined;
const className = eventParts?.length > 1 ? eventParts[1] : undefined;
@@ -44,13 +43,20 @@ export class NotificationApi {
static async notification(controller: AbortController) {
const fetchData = async () => {
const headers: any = { Accept: "text/event-stream" };
- if (KaravanApi.authType === 'oidc') {
- headers.Authorization = "Bearer " + SsoApi.keycloak?.token
+ let ready = false;
+ if (KaravanApi.authType === 'oidc' && SsoApi.keycloak?.token && SsoApi.keycloak?.token?.length > 0) {
+ headers.Authorization = "Bearer " + SsoApi.keycloak?.token;
+ ready = true;
+ } else if (KaravanApi.authType === 'basic' && KaravanApi.basicToken?.length > 0) {
+ headers.Authorization = "Basic " + KaravanApi.basicToken
+ ready = true;
}
- NotificationApi.fetch('/api/notification/system', controller, headers,
+ if (ready) {
+ NotificationApi.fetch('/api/notification/system', controller, headers,
ev => NotificationApi.onSystemMessage(ev));
- NotificationApi.fetch('/api/notification/user/' + KaravanApi.getUserId(), controller, headers,
- ev => NotificationApi.onUserMessage(ev));
+ NotificationApi.fetch('/api/notification/user/' + KaravanApi.getUserId(), controller, headers,
+ ev => NotificationApi.onUserMessage(ev));
+ }
};
return fetchData();
};
diff --git a/karavan-app/src/main/webui/src/api/ProjectStore.ts b/karavan-app/src/main/webui/src/api/ProjectStore.ts
index 861a0c06..5bf4a103 100644
--- a/karavan-app/src/main/webui/src/api/ProjectStore.ts
+++ b/karavan-app/src/main/webui/src/api/ProjectStore.ts
@@ -30,6 +30,8 @@ import {createWithEqualityFn} from "zustand/traditional";
import {shallow} from "zustand/shallow";
interface AppConfigState {
+ isAuthorized: boolean;
+ setAuthorized: (isAuthorized: boolean) => void;
loading: boolean;
setLoading: (loading: boolean) => void;
config: AppConfig;
@@ -42,6 +44,10 @@ interface AppConfigState {
}
export const useAppConfigStore = createWithEqualityFn<AppConfigState>((set) => ({
+ isAuthorized: false,
+ setAuthorized: (isAuthorized: boolean) => {
+ set({isAuthorized: isAuthorized})
+ },
loading: false,
setLoading: (loading: boolean) => {
set({loading: loading})