diff --git a/Makefile b/Makefile index 67224de8e11..7f59e399a3d 100644 --- a/Makefile +++ b/Makefile @@ -14,7 +14,7 @@ OPENAPI_RUST_GENERATOR_IMAGE=openapitools/openapi-generator-cli:v7.5.0 OPENAPI_RUST_GENERATOR=$(DOCKER) run --user $(UID_GID) --rm -v $(shell pwd):/mnt $(OPENAPI_RUST_GENERATOR_IMAGE) PY_OPENAPI_GENERATOR=$(DOCKER) run -e PYTHON_POST_PROCESS_FILE="/mnt/clients/python/scripts/pydantic.sh" --user $(UID_GID) --rm -v $(shell pwd):/mnt $(OPENAPI_GENERATOR_IMAGE) -GOLANGCI_LINT_VERSION=v1.63.1 +GOLANGCI_LINT_VERSION=v1.63.4 BUF_CLI_VERSION=v1.28.1 ifndef PACKAGE_VERSION diff --git a/api/swagger.yml b/api/swagger.yml index b8afd4c3a23..db42e9a91aa 100644 --- a/api/swagger.yml +++ b/api/swagger.yml @@ -923,6 +923,14 @@ components: with an external auth service. type: string enum: [none, simplified, external] + username_ui_placeholder: + description: | + Placeholder text to display in the username field of the login form. + type: string + password_ui_placeholder: + description: | + Placeholder text to display in the password field of the login form. + type: string login_url: description: primary URL to use for login. type: string diff --git a/clients/java/api/openapi.yaml b/clients/java/api/openapi.yaml index 2936c8aef3b..e331c142298 100644 --- a/clients/java/api/openapi.yaml +++ b/clients/java/api/openapi.yaml @@ -8532,10 +8532,12 @@ components: type: object LoginConfig: example: + username_ui_placeholder: username_ui_placeholder login_failed_message: login_failed_message logout_url: logout_url login_url: login_url RBAC: none + password_ui_placeholder: password_ui_placeholder fallback_login_url: fallback_login_url login_cookie_names: - login_cookie_names @@ -8551,6 +8553,14 @@ components: - simplified - external type: string + username_ui_placeholder: + description: | + Placeholder text to display in the username field of the login form. + type: string + password_ui_placeholder: + description: | + Placeholder text to display in the password field of the login form. + type: string login_url: description: primary URL to use for login. type: string @@ -8581,10 +8591,12 @@ components: SetupState: example: login_config: + username_ui_placeholder: username_ui_placeholder login_failed_message: login_failed_message logout_url: logout_url login_url: login_url RBAC: none + password_ui_placeholder: password_ui_placeholder fallback_login_url: fallback_login_url login_cookie_names: - login_cookie_names diff --git a/clients/java/docs/LoginConfig.md b/clients/java/docs/LoginConfig.md index 0cadc12b5d2..f60a08e25ed 100644 --- a/clients/java/docs/LoginConfig.md +++ b/clients/java/docs/LoginConfig.md @@ -8,6 +8,8 @@ | Name | Type | Description | Notes | |------------ | ------------- | ------------- | -------------| |**RBAC** | [**RBACEnum**](#RBACEnum) | RBAC will remain enabled on GUI if \"external\". That only works with an external auth service. | [optional] | +|**usernameUiPlaceholder** | **String** | Placeholder text to display in the username field of the login form. | [optional] | +|**passwordUiPlaceholder** | **String** | Placeholder text to display in the password field of the login form. | [optional] | |**loginUrl** | **String** | primary URL to use for login. | | |**loginFailedMessage** | **String** | message to display to users who fail to login; a full sentence that is rendered in HTML and may contain a link to a secondary login method | [optional] | |**fallbackLoginUrl** | **String** | secondary URL to offer users to use for login. | [optional] | diff --git a/clients/java/src/main/java/io/lakefs/clients/sdk/model/LoginConfig.java b/clients/java/src/main/java/io/lakefs/clients/sdk/model/LoginConfig.java index e6860d0e294..8d74ef325ec 100644 --- a/clients/java/src/main/java/io/lakefs/clients/sdk/model/LoginConfig.java +++ b/clients/java/src/main/java/io/lakefs/clients/sdk/model/LoginConfig.java @@ -107,6 +107,14 @@ public RBACEnum read(final JsonReader jsonReader) throws IOException { @SerializedName(SERIALIZED_NAME_R_B_A_C) private RBACEnum RBAC; + public static final String SERIALIZED_NAME_USERNAME_UI_PLACEHOLDER = "username_ui_placeholder"; + @SerializedName(SERIALIZED_NAME_USERNAME_UI_PLACEHOLDER) + private String usernameUiPlaceholder; + + public static final String SERIALIZED_NAME_PASSWORD_UI_PLACEHOLDER = "password_ui_placeholder"; + @SerializedName(SERIALIZED_NAME_PASSWORD_UI_PLACEHOLDER) + private String passwordUiPlaceholder; + public static final String SERIALIZED_NAME_LOGIN_URL = "login_url"; @SerializedName(SERIALIZED_NAME_LOGIN_URL) private String loginUrl; @@ -155,6 +163,48 @@ public void setRBAC(RBACEnum RBAC) { } + public LoginConfig usernameUiPlaceholder(String usernameUiPlaceholder) { + + this.usernameUiPlaceholder = usernameUiPlaceholder; + return this; + } + + /** + * Placeholder text to display in the username field of the login form. + * @return usernameUiPlaceholder + **/ + @javax.annotation.Nullable + public String getUsernameUiPlaceholder() { + return usernameUiPlaceholder; + } + + + public void setUsernameUiPlaceholder(String usernameUiPlaceholder) { + this.usernameUiPlaceholder = usernameUiPlaceholder; + } + + + public LoginConfig passwordUiPlaceholder(String passwordUiPlaceholder) { + + this.passwordUiPlaceholder = passwordUiPlaceholder; + return this; + } + + /** + * Placeholder text to display in the password field of the login form. + * @return passwordUiPlaceholder + **/ + @javax.annotation.Nullable + public String getPasswordUiPlaceholder() { + return passwordUiPlaceholder; + } + + + public void setPasswordUiPlaceholder(String passwordUiPlaceholder) { + this.passwordUiPlaceholder = passwordUiPlaceholder; + } + + public LoginConfig loginUrl(String loginUrl) { this.loginUrl = loginUrl; @@ -344,6 +394,8 @@ public boolean equals(Object o) { } LoginConfig loginConfig = (LoginConfig) o; return Objects.equals(this.RBAC, loginConfig.RBAC) && + Objects.equals(this.usernameUiPlaceholder, loginConfig.usernameUiPlaceholder) && + Objects.equals(this.passwordUiPlaceholder, loginConfig.passwordUiPlaceholder) && Objects.equals(this.loginUrl, loginConfig.loginUrl) && Objects.equals(this.loginFailedMessage, loginConfig.loginFailedMessage) && Objects.equals(this.fallbackLoginUrl, loginConfig.fallbackLoginUrl) && @@ -355,7 +407,7 @@ public boolean equals(Object o) { @Override public int hashCode() { - return Objects.hash(RBAC, loginUrl, loginFailedMessage, fallbackLoginUrl, fallbackLoginLabel, loginCookieNames, logoutUrl, additionalProperties); + return Objects.hash(RBAC, usernameUiPlaceholder, passwordUiPlaceholder, loginUrl, loginFailedMessage, fallbackLoginUrl, fallbackLoginLabel, loginCookieNames, logoutUrl, additionalProperties); } @Override @@ -363,6 +415,8 @@ public String toString() { StringBuilder sb = new StringBuilder(); sb.append("class LoginConfig {\n"); sb.append(" RBAC: ").append(toIndentedString(RBAC)).append("\n"); + sb.append(" usernameUiPlaceholder: ").append(toIndentedString(usernameUiPlaceholder)).append("\n"); + sb.append(" passwordUiPlaceholder: ").append(toIndentedString(passwordUiPlaceholder)).append("\n"); sb.append(" loginUrl: ").append(toIndentedString(loginUrl)).append("\n"); sb.append(" loginFailedMessage: ").append(toIndentedString(loginFailedMessage)).append("\n"); sb.append(" fallbackLoginUrl: ").append(toIndentedString(fallbackLoginUrl)).append("\n"); @@ -393,6 +447,8 @@ private String toIndentedString(Object o) { // a set of all properties/fields (JSON key names) openapiFields = new HashSet(); openapiFields.add("RBAC"); + openapiFields.add("username_ui_placeholder"); + openapiFields.add("password_ui_placeholder"); openapiFields.add("login_url"); openapiFields.add("login_failed_message"); openapiFields.add("fallback_login_url"); @@ -430,6 +486,12 @@ public static void validateJsonElement(JsonElement jsonElement) throws IOExcepti if ((jsonObj.get("RBAC") != null && !jsonObj.get("RBAC").isJsonNull()) && !jsonObj.get("RBAC").isJsonPrimitive()) { throw new IllegalArgumentException(String.format("Expected the field `RBAC` to be a primitive type in the JSON string but got `%s`", jsonObj.get("RBAC").toString())); } + if ((jsonObj.get("username_ui_placeholder") != null && !jsonObj.get("username_ui_placeholder").isJsonNull()) && !jsonObj.get("username_ui_placeholder").isJsonPrimitive()) { + throw new IllegalArgumentException(String.format("Expected the field `username_ui_placeholder` to be a primitive type in the JSON string but got `%s`", jsonObj.get("username_ui_placeholder").toString())); + } + if ((jsonObj.get("password_ui_placeholder") != null && !jsonObj.get("password_ui_placeholder").isJsonNull()) && !jsonObj.get("password_ui_placeholder").isJsonPrimitive()) { + throw new IllegalArgumentException(String.format("Expected the field `password_ui_placeholder` to be a primitive type in the JSON string but got `%s`", jsonObj.get("password_ui_placeholder").toString())); + } if (!jsonObj.get("login_url").isJsonPrimitive()) { throw new IllegalArgumentException(String.format("Expected the field `login_url` to be a primitive type in the JSON string but got `%s`", jsonObj.get("login_url").toString())); } diff --git a/clients/java/src/test/java/io/lakefs/clients/sdk/model/LoginConfigTest.java b/clients/java/src/test/java/io/lakefs/clients/sdk/model/LoginConfigTest.java index 27f353a9daf..52fe34be1d3 100644 --- a/clients/java/src/test/java/io/lakefs/clients/sdk/model/LoginConfigTest.java +++ b/clients/java/src/test/java/io/lakefs/clients/sdk/model/LoginConfigTest.java @@ -47,6 +47,22 @@ public void RBACTest() { // TODO: test RBAC } + /** + * Test the property 'usernameUiPlaceholder' + */ + @Test + public void usernameUiPlaceholderTest() { + // TODO: test usernameUiPlaceholder + } + + /** + * Test the property 'passwordUiPlaceholder' + */ + @Test + public void passwordUiPlaceholderTest() { + // TODO: test passwordUiPlaceholder + } + /** * Test the property 'loginUrl' */ diff --git a/clients/python/docs/LoginConfig.md b/clients/python/docs/LoginConfig.md index c154bfd3a7e..887f3970e1e 100644 --- a/clients/python/docs/LoginConfig.md +++ b/clients/python/docs/LoginConfig.md @@ -6,6 +6,8 @@ Name | Type | Description | Notes ------------ | ------------- | ------------- | ------------- **rbac** | **str** | RBAC will remain enabled on GUI if \"external\". That only works with an external auth service. | [optional] +**username_ui_placeholder** | **str** | Placeholder text to display in the username field of the login form. | [optional] +**password_ui_placeholder** | **str** | Placeholder text to display in the password field of the login form. | [optional] **login_url** | **str** | primary URL to use for login. | **login_failed_message** | **str** | message to display to users who fail to login; a full sentence that is rendered in HTML and may contain a link to a secondary login method | [optional] **fallback_login_url** | **str** | secondary URL to offer users to use for login. | [optional] diff --git a/clients/python/lakefs_sdk/models/login_config.py b/clients/python/lakefs_sdk/models/login_config.py index 0e952baf33e..90bd2c7c838 100644 --- a/clients/python/lakefs_sdk/models/login_config.py +++ b/clients/python/lakefs_sdk/models/login_config.py @@ -30,13 +30,15 @@ class LoginConfig(BaseModel): LoginConfig """ rbac: Optional[StrictStr] = Field(None, alias="RBAC", description="RBAC will remain enabled on GUI if \"external\". That only works with an external auth service. ") + username_ui_placeholder: Optional[StrictStr] = Field(None, description="Placeholder text to display in the username field of the login form. ") + password_ui_placeholder: Optional[StrictStr] = Field(None, description="Placeholder text to display in the password field of the login form. ") login_url: StrictStr = Field(..., description="primary URL to use for login.") login_failed_message: Optional[StrictStr] = Field(None, description="message to display to users who fail to login; a full sentence that is rendered in HTML and may contain a link to a secondary login method ") fallback_login_url: Optional[StrictStr] = Field(None, description="secondary URL to offer users to use for login.") fallback_login_label: Optional[StrictStr] = Field(None, description="label to place on fallback_login_url.") login_cookie_names: conlist(StrictStr) = Field(..., description="cookie names used to store JWT") logout_url: StrictStr = Field(..., description="URL to use for logging out.") - __properties = ["RBAC", "login_url", "login_failed_message", "fallback_login_url", "fallback_login_label", "login_cookie_names", "logout_url"] + __properties = ["RBAC", "username_ui_placeholder", "password_ui_placeholder", "login_url", "login_failed_message", "fallback_login_url", "fallback_login_label", "login_cookie_names", "logout_url"] @validator('rbac') def rbac_validate_enum(cls, value): @@ -85,6 +87,8 @@ def from_dict(cls, obj: dict) -> LoginConfig: _obj = LoginConfig.parse_obj({ "rbac": obj.get("RBAC"), + "username_ui_placeholder": obj.get("username_ui_placeholder"), + "password_ui_placeholder": obj.get("password_ui_placeholder"), "login_url": obj.get("login_url"), "login_failed_message": obj.get("login_failed_message"), "fallback_login_url": obj.get("fallback_login_url"), diff --git a/clients/python/test/test_login_config.py b/clients/python/test/test_login_config.py index b5ed5003400..77f3c8a4cac 100644 --- a/clients/python/test/test_login_config.py +++ b/clients/python/test/test_login_config.py @@ -40,6 +40,8 @@ def make_instance(self, include_optional): if include_optional : return LoginConfig( rbac = 'none', + username_ui_placeholder = '', + password_ui_placeholder = '', login_url = '', login_failed_message = '', fallback_login_url = '', diff --git a/clients/python/test/test_setup_state.py b/clients/python/test/test_setup_state.py index a7a89d41af5..80d8836570e 100644 --- a/clients/python/test/test_setup_state.py +++ b/clients/python/test/test_setup_state.py @@ -43,6 +43,8 @@ def make_instance(self, include_optional): comm_prefs_missing = True, login_config = lakefs_sdk.models.login_config.LoginConfig( rbac = 'none', + username_ui_placeholder = '', + password_ui_placeholder = '', login_url = '', login_failed_message = '', fallback_login_url = '', diff --git a/clients/rust/docs/LoginConfig.md b/clients/rust/docs/LoginConfig.md index 9748b8828da..c30af1a66c0 100644 --- a/clients/rust/docs/LoginConfig.md +++ b/clients/rust/docs/LoginConfig.md @@ -5,6 +5,8 @@ Name | Type | Description | Notes ------------ | ------------- | ------------- | ------------- **rbac** | Option<**String**> | RBAC will remain enabled on GUI if \"external\". That only works with an external auth service. | [optional] +**username_ui_placeholder** | Option<**String**> | Placeholder text to display in the username field of the login form. | [optional] +**password_ui_placeholder** | Option<**String**> | Placeholder text to display in the password field of the login form. | [optional] **login_url** | **String** | primary URL to use for login. | **login_failed_message** | Option<**String**> | message to display to users who fail to login; a full sentence that is rendered in HTML and may contain a link to a secondary login method | [optional] **fallback_login_url** | Option<**String**> | secondary URL to offer users to use for login. | [optional] diff --git a/clients/rust/src/models/login_config.rs b/clients/rust/src/models/login_config.rs index 75e0716ff53..57053e2c182 100644 --- a/clients/rust/src/models/login_config.rs +++ b/clients/rust/src/models/login_config.rs @@ -15,6 +15,12 @@ pub struct LoginConfig { /// RBAC will remain enabled on GUI if \"external\". That only works with an external auth service. #[serde(rename = "RBAC", skip_serializing_if = "Option::is_none")] pub rbac: Option, + /// Placeholder text to display in the username field of the login form. + #[serde(rename = "username_ui_placeholder", skip_serializing_if = "Option::is_none")] + pub username_ui_placeholder: Option, + /// Placeholder text to display in the password field of the login form. + #[serde(rename = "password_ui_placeholder", skip_serializing_if = "Option::is_none")] + pub password_ui_placeholder: Option, /// primary URL to use for login. #[serde(rename = "login_url")] pub login_url: String, @@ -39,6 +45,8 @@ impl LoginConfig { pub fn new(login_url: String, login_cookie_names: Vec, logout_url: String) -> LoginConfig { LoginConfig { rbac: None, + username_ui_placeholder: None, + password_ui_placeholder: None, login_url, login_failed_message: None, fallback_login_url: None, diff --git a/docs/assets/js/swagger.yml b/docs/assets/js/swagger.yml index f8a5812c7d7..fa941fb0719 100644 --- a/docs/assets/js/swagger.yml +++ b/docs/assets/js/swagger.yml @@ -923,6 +923,14 @@ components: with an external auth service. type: string enum: [none, simplified, external] + username_ui_placeholder: + description: | + Placeholder text to display in the username field of the login form. + type: string + password_ui_placeholder: + description: | + Placeholder text to display in the password field of the login form. + type: string login_url: description: primary URL to use for login. type: string diff --git a/docs/posts/deprecate-py-legacy.md b/docs/posts/deprecate-py-legacy.md index 4dbef3547e5..3b419885eb8 100644 --- a/docs/posts/deprecate-py-legacy.md +++ b/docs/posts/deprecate-py-legacy.md @@ -71,7 +71,7 @@ That is also going away. However we see much less usage of it. ### Where can I ask another question not covered here? -As always, our Slack htttps://lakefs.io/slack is the best place to interact +As always, our Slack https://lakefs.io/slack is the best place to interact with the lakeFS community! Try asking on our `#dev` channel diff --git a/pkg/api/controller.go b/pkg/api/controller.go index b8327bb2d9e..e42e41fe3a5 100644 --- a/pkg/api/controller.go +++ b/pkg/api/controller.go @@ -74,6 +74,9 @@ const ( pullRequestClosed = "CLOSED" pullRequestOpen = "OPEN" + + usernamePlaceholder = "Username" + passwordPlaceholder = "Password" ) type actionsHandler interface { @@ -4977,7 +4980,7 @@ func (c *Controller) GetTag(w http.ResponseWriter, r *http.Request, repository, } func newLoginConfig(c *config.BaseConfig) *apigen.LoginConfig { - return &apigen.LoginConfig{ + loginConfig := &apigen.LoginConfig{ RBAC: &c.Auth.UIConfig.RBAC, LoginUrl: c.Auth.UIConfig.LoginURL, LoginFailedMessage: &c.Auth.UIConfig.LoginFailedMessage, @@ -4986,6 +4989,11 @@ func newLoginConfig(c *config.BaseConfig) *apigen.LoginConfig { LoginCookieNames: c.Auth.UIConfig.LoginCookieNames, LogoutUrl: c.Auth.UIConfig.LogoutURL, } + if c.UseUILoginPlaceholders() { + loginConfig.UsernameUiPlaceholder = swag.String(usernamePlaceholder) + loginConfig.PasswordUiPlaceholder = swag.String(passwordPlaceholder) + } + return loginConfig } func (c *Controller) GetSetupState(w http.ResponseWriter, r *http.Request) { diff --git a/pkg/config/config.go b/pkg/config/config.go index 40026608555..a2fd73a95a3 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -619,6 +619,12 @@ func (c *BaseConfig) IsExternalPrincipalsEnabled() bool { return c.IsAuthTypeAPI() && c.Auth.AuthenticationAPI.ExternalPrincipalsEnabled } +// UseUILoginPlaceholders returns true if the UI should use placeholders for login +// the UI should use place holders just in case of LDAP, the other auth methods should have their own login page +func (c *BaseConfig) UseUILoginPlaceholders() bool { + return c.Auth.RemoteAuthenticator.Enabled +} + func (c *BaseConfig) IsAdvancedAuth() bool { return c.IsAuthTypeAPI() && (c.Auth.UIConfig.RBAC == AuthRBACExternal || c.Auth.UIConfig.RBAC == AuthRBACInternal) } diff --git a/webui/src/pages/auth/login.tsx b/webui/src/pages/auth/login.tsx index e8c650fb99a..7ecc25374d3 100644 --- a/webui/src/pages/auth/login.tsx +++ b/webui/src/pages/auth/login.tsx @@ -11,6 +11,8 @@ import {useAPI} from "../../lib/hooks/api"; interface LoginConfig { login_url: string; + username_ui_placeholder: string; + password_ui_placeholder: string; login_failed_message?: string; fallback_login_url?: string; fallback_login_label?: string; @@ -22,7 +24,9 @@ const LoginForm = ({loginConfig}: {loginConfig: LoginConfig}) => { const router = useRouter(); const [loginError, setLoginError] = useState(null); const { next } = router.query; - + console.log(loginConfig); + const usernamePlaceholder = loginConfig.username_ui_placeholder || "Access Key ID"; + const passwordPlaceholder = loginConfig.password_ui_placeholder || "Secret Access Key"; return ( @@ -44,11 +48,11 @@ const LoginForm = ({loginConfig}: {loginConfig: LoginConfig}) => { } }}> - + - + {(!!loginError) && }