Compare commits

..

10 Commits

Author SHA1 Message Date
congs
8c1977e0aa fix(permission): EE-3772 Team leaders are able to see all environments (#7330)
Some checks failed
/ triage (push) Has been cancelled
Lint / Run linters (push) Has been cancelled
Test / test-client (push) Has been cancelled
2022-07-26 11:02:33 +12:00
Matt Hook
c47fd9f9ed bump version to 2.14.2 (#7304) 2022-07-22 10:55:17 +12:00
LP B
767d1d1970 fix(edge): pagination on create/edit edge jobs/groups [EE-3219] (#7297) 2022-07-21 18:47:20 +02:00
itsconquest
ef81e5c0e0 fix(auth): correctly calculate LDAP teamsync [EE-3704] (#7292) 2022-07-21 21:29:27 +12:00
Prabhat Khera
234b7a3d5e fix(kubeconfig): fix kubeconfig url EE-3455 (#7283) 2022-07-21 16:59:48 +12:00
itsconquest
af49305e64 fix(TLS): remove file type validation [EE-3672] (#7279) 2022-07-21 16:25:19 +12:00
LP B
d181d1251c fix(app/mustache): reuse mustache variables in templates [EE-3689] (#7287) 2022-07-19 15:38:10 +02:00
LP B
5f7db66e95 fix(app/templates): handle special characters in mustache templates [EE-3708] (#7289) 2022-07-19 14:05:31 +02:00
Dmitry Salakhov
17378bdef6 fix(users): admin can change password with any auth method (#7269) [EE-3671] 2022-07-19 11:26:43 +12:00
Oscar Zhou
010542ac1e fix(setting): update the por switch field component property (#7258) 2022-07-15 08:27:53 +12:00
20 changed files with 53 additions and 77 deletions

View File

@@ -910,7 +910,7 @@
],
"version": {
"DB_UPDATING": "false",
"DB_VERSION": "51",
"DB_VERSION": "52",
"INSTANCE_ID": "null"
}
}

View File

@@ -80,7 +80,7 @@ type Handler struct {
}
// @title PortainerCE API
// @version 2.14.1
// @version 2.14.2
// @description.markdown api-description.md
// @termsOfService

View File

@@ -2,7 +2,6 @@ package helm
import (
"net/http"
"strings"
"github.com/gorilla/mux"
"github.com/portainer/libhelm"
@@ -108,7 +107,7 @@ func (handler *Handler) getHelmClusterAccess(r *http.Request) (*options.Kubernet
hostURL := "localhost"
if !sslSettings.SelfSigned {
hostURL = strings.Split(r.Host, ":")[0]
hostURL = r.Host
}
kubeConfigInternal := handler.kubeClusterAccessService.GetData(hostURL, endpoint.ID)

View File

@@ -4,7 +4,6 @@ import (
"errors"
"fmt"
"net/http"
"strings"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
@@ -145,8 +144,7 @@ func (handler *Handler) buildConfig(r *http.Request, tokenData *portainer.TokenD
}
func (handler *Handler) buildCluster(r *http.Request, endpoint portainer.Endpoint) clientV1.NamedCluster {
hostURL := strings.Split(r.Host, ":")[0]
kubeConfigInternal := handler.kubeClusterAccessService.GetData(hostURL, endpoint.ID)
kubeConfigInternal := handler.kubeClusterAccessService.GetData(r.Host, endpoint.ID)
return clientV1.NamedCluster{
Name: buildClusterName(endpoint.Name),
Cluster: clientV1.Cluster{

View File

@@ -95,8 +95,10 @@ func generatePublicSettings(appSettings *portainer.Settings) *publicSettingsResp
}
}
//if LDAP authentication is on, compose the related fields from application settings
if publicSettings.AuthenticationMethod == portainer.AuthenticationLDAP {
publicSettings.TeamSync = len(appSettings.LDAPSettings.GroupSearchSettings) > 0
if publicSettings.AuthenticationMethod == portainer.AuthenticationLDAP && appSettings.LDAPSettings.GroupSearchSettings != nil {
if len(appSettings.LDAPSettings.GroupSearchSettings) > 0 {
publicSettings.TeamSync = len(appSettings.LDAPSettings.GroupSearchSettings[0].GroupBaseDN) > 0
}
}
return publicSettings
}

View File

@@ -81,11 +81,11 @@ func FilterRegistries(registries []portainer.Registry, user *portainer.User, tea
}
// FilterEndpoints filters environments(endpoints) based on user role and team memberships.
// Non administrator and non-team-leader only have access to authorized environments(endpoints) (can be inherited via endpoint groups).
// Non administrator only have access to authorized environments(endpoints) (can be inherited via endpoint groups).
func FilterEndpoints(endpoints []portainer.Endpoint, groups []portainer.EndpointGroup, context *RestrictedRequestContext) []portainer.Endpoint {
filteredEndpoints := endpoints
if !context.IsAdmin && !context.IsTeamLeader {
if !context.IsAdmin {
filteredEndpoints = make([]portainer.Endpoint, 0)
for _, endpoint := range endpoints {
@@ -101,11 +101,11 @@ func FilterEndpoints(endpoints []portainer.Endpoint, groups []portainer.Endpoint
}
// FilterEndpointGroups filters environment(endpoint) groups based on user role and team memberships.
// Non administrator users and Non-team-leaders only have access to authorized environment(endpoint) groups.
// Non administrator users only have access to authorized environment(endpoint) groups.
func FilterEndpointGroups(endpointGroups []portainer.EndpointGroup, context *RestrictedRequestContext) []portainer.EndpointGroup {
filteredEndpointGroups := endpointGroups
if !context.IsAdmin && !context.IsTeamLeader {
if !context.IsAdmin {
filteredEndpointGroups = make([]portainer.EndpointGroup, 0)
for _, group := range endpointGroups {

View File

@@ -11,6 +11,7 @@ import (
"github.com/pkg/errors"
portainer "github.com/portainer/portainer/api"
"github.com/sirupsen/logrus"
)
// KubeClusterAccessService represents a service that is responsible for centralizing kube cluster access data
@@ -94,11 +95,20 @@ func (service *kubeClusterAccessService) IsSecure() bool {
// - pass down params to binaries
func (service *kubeClusterAccessService) GetData(hostURL string, endpointID portainer.EndpointID) kubernetesClusterAccessData {
baseURL := service.baseURL
// When the api call is internal, the baseURL should not be used.
if hostURL == "localhost" {
hostURL = hostURL + service.httpsBindAddr
baseURL = "/"
}
if baseURL != "/" {
baseURL = fmt.Sprintf("/%s/", strings.Trim(baseURL, "/"))
}
clusterURL := hostURL + service.httpsBindAddr + baseURL
logrus.Infof("[kubeconfig] [hostURL: %s, httpsBindAddr: %s, baseURL: %s]", hostURL, service.httpsBindAddr, baseURL)
clusterURL := hostURL + baseURL
clusterServerURL := fmt.Sprintf("https://%sapi/endpoints/%d/kubernetes", clusterURL, endpointID)

View File

@@ -115,7 +115,7 @@ func TestKubeClusterAccessService_GetKubeConfigInternal(t *testing.T) {
clusterAccessDetails := kcs.GetData("mysite.com", 1)
wantClusterAccessDetails := kubernetesClusterAccessData{
ClusterServerURL: "https://mysite.com:9443/api/endpoints/1/kubernetes",
ClusterServerURL: "https://mysite.com/api/endpoints/1/kubernetes",
CertificateAuthorityFile: "",
CertificateAuthorityData: "",
}

View File

@@ -1385,9 +1385,9 @@ type (
const (
// APIVersion is the version number of the Portainer API
APIVersion = "2.14.1"
APIVersion = "2.14.2"
// DBVersion is the version number of the Portainer database
DBVersion = 51
DBVersion = 52
// ComposeSyntaxMaxVersion is a maximum supported version of the docker compose syntax
ComposeSyntaxMaxVersion = "3.9"
// AssetsServerURL represents the URL of the Portainer asset server

View File

@@ -5,7 +5,7 @@ angular.module('portainer.kubernetes').factory('HelmFactory', HelmFactory);
/* @ngInject */
function HelmFactory($resource, API_ENDPOINT_ENDPOINTS) {
const helmUrl = API_ENDPOINT_ENDPOINTS + '/:endpointId/kubernetes/helm';
const templatesUrl = '/api/templates/helm';
const templatesUrl = 'api/templates/helm';
return $resource(
helmUrl,

View File

@@ -11,7 +11,7 @@
loaded="$ctrl.loaded"
page-type="$ctrl.pageType"
table-type="available"
retrieve-page="$ctrl.getPaginatedEndpoints"
retrieve-page="$ctrl.getAvailableEndpoints"
dataset="$ctrl.endpoints.available"
entry-click="$ctrl.associateEndpoint"
pagination-state="$ctrl.state.available"
@@ -34,7 +34,7 @@
loaded="$ctrl.loaded"
page-type="$ctrl.pageType"
table-type="associated"
retrieve-page="$ctrl.getPaginatedEndpoints"
retrieve-page="$ctrl.getAssociatedEndpoints"
dataset="$ctrl.endpoints.associated"
entry-click="$ctrl.dissociateEndpoint"
pagination-state="$ctrl.state.associated"

View File

@@ -27,7 +27,7 @@ class AssoicatedEndpointsSelectorController {
available: null,
};
this.getEndpoints = this.getEndpoints.bind(this);
this.getAvailableEndpoints = this.getAvailableEndpoints.bind(this);
this.getEndpointsAsync = this.getEndpointsAsync.bind(this);
this.getAssociatedEndpoints = this.getAssociatedEndpoints.bind(this);
this.getAssociatedEndpointsAsync = this.getAssociatedEndpointsAsync.bind(this);
@@ -47,10 +47,10 @@ class AssoicatedEndpointsSelectorController {
loadData() {
this.getAssociatedEndpoints();
this.getEndpoints();
this.getAvailableEndpoints();
}
getEndpoints() {
getAvailableEndpoints() {
return this.$async(this.getEndpointsAsync);
}

View File

@@ -22,22 +22,3 @@ export function withFileSize(fileValidation: FileSchema, maxSize: number) {
return file.size <= maxSize;
}
}
export function withFileType(
fileValidation: FileSchema,
fileTypes: File['type'][]
) {
return fileValidation.test(
'file-type',
'Selected file has unsupported format.',
validateFileType
);
function validateFileType(file?: File) {
if (!file) {
return true;
}
return fileTypes.includes(file.type);
}
}

View File

@@ -42,14 +42,7 @@
<span class="input-group-addon"><i class="fa fa-lock" aria-hidden="true"></i></span>
<input type="password" class="form-control" ng-model="formValues.confirmPassword" id="confirm_password" />
<span class="input-group-addon"
><i
ng-class="
{ true: 'fa fa-check green-icon', false: 'fa fa-times red-icon' }[
form.new_password.$viewValue !== '' && form.new_password.$viewValue === formValues.confirmPassword
]
"
aria-hidden="true"
></i
><i ng-class="{ true: 'fa fa-check green-icon', false: 'fa fa-times red-icon' }[formValues.newPassword === formValues.confirmPassword]" aria-hidden="true"></i
></span>
</div>
</div>
@@ -67,7 +60,7 @@
<button
type="submit"
class="btn btn-primary btn-sm"
ng-disabled="isDemoUser || (AuthenticationMethod !== 1 && !initialUser) || !formValues.currentPassword || !formValues.newPassword || !formValues.confirmPassword || form.$invalid || form.new_password.$viewValue !== formValues.confirmPassword"
ng-disabled="isDemoUser || (AuthenticationMethod !== 1 && !isInitialAdmin) || !formValues.currentPassword || !formValues.newPassword || form.$invalid || formValues.newPassword !== formValues.confirmPassword"
ng-click="updatePassword()"
>
Update password
@@ -75,11 +68,11 @@
<button type="submit" class="btn btn-primary btn-sm" ng-click="skipPasswordChange()" ng-if="forceChangePassword && timesPasswordChangeSkipped < 2"
>Remind me later</button
>
<span class="text-muted small" style="margin-left: 5px" ng-if="AuthenticationMethod === 2 && !initialUser">
<span class="text-muted small" style="margin-left: 5px" ng-if="AuthenticationMethod === 2 && !isInitialAdmin">
<i class="fa fa-exclamation-triangle" aria-hidden="true"></i>
You cannot change your password when using LDAP authentication.
</span>
<span class="text-muted small" style="margin-left: 5px" ng-if="AuthenticationMethod === 3 && !initialUser">
<span class="text-muted small" style="margin-left: 5px" ng-if="AuthenticationMethod === 3 && !isInitialAdmin">
<i class="fa fa-exclamation-triangle" aria-hidden="true"></i>
You cannot change your password when using OAuth authentication.
</span>

View File

@@ -118,6 +118,7 @@ angular.module('portainer.app').controller('AccountController', [
$scope.userID = userDetails.ID;
$scope.userRole = Authentication.getUserDetails().role;
$scope.forceChangePassword = userDetails.forceChangePassword;
$scope.isInitialAdmin = userDetails.ID === 1;
if (state.application.demoEnvironment.enabled) {
$scope.isDemoUser = state.application.demoEnvironment.users.includes($scope.userID);

View File

@@ -22,7 +22,7 @@
<div class="col-sm-12">
<por-switch-field
label="'Use custom logo'"
value="formValues.customLogo"
checked="formValues.customLogo"
name="'toggle_logo'"
disabled="state.isDemo"
on-change="(onToggleCustomLogo)"
@@ -50,7 +50,7 @@
<div class="col-sm-12">
<por-switch-field
label="'Allow the collection of anonymous statistics'"
value="formValues.enableTelemetry"
checked="formValues.enableTelemetry"
name="'toggle_enableTelemetry'"
on-change="(onToggleEnableTelemetry)"
disabled="state.isDemo"

View File

@@ -64,7 +64,7 @@
children-paths="['portainer.users.user' ,'portainer.teams' ,'portainer.teams.team' ,'portainer.roles' ,'portainer.roles.role' ,'portainer.roles.new']"
>
<sidebar-menu-item path="portainer.teams" class-name="sidebar-sublist" data-cy="portainerSidebar-teams" title="Teams">Teams</sidebar-menu-item>
<sidebar-menu-item path="portainer.roles" class-name="sidebar-sublist" data-cy="portainerSidebar-roles" title="Roles">Roles</sidebar-menu-item>
<sidebar-menu-item ng-if="isAdmin" path="portainer.roles" class-name="sidebar-sublist" data-cy="portainerSidebar-roles" title="Roles">Roles</sidebar-menu-item>
</sidebar-menu>
<div ng-if="isAdmin">

View File

@@ -38,12 +38,15 @@ export function intersectVariables(
) {
const oldVariablesWithLabel = oldVariables.filter((v) => !!v.label);
return [
...oldVariablesWithLabel,
...newVariables.filter(
(v) => !oldVariablesWithLabel.find(({ name }) => name === v.name)
),
];
return _.uniqBy(
[
...oldVariablesWithLabel,
...newVariables.filter(
(v) => !oldVariablesWithLabel.find(({ name }) => name === v.name)
),
],
'name'
);
}
export function renderTemplate(
@@ -68,5 +71,5 @@ export function renderTemplate(
)
);
return Mustache.render(template, state);
return Mustache.render(template, state, undefined, { escape: (t) => t });
}

View File

@@ -3,11 +3,7 @@ import { useFormikContext } from 'formik';
import { FileUploadField } from '@/portainer/components/form-components/FileUpload';
import { SwitchField } from '@/portainer/components/form-components/SwitchField';
import { FormControl } from '@/portainer/components/form-components/FormControl';
import {
file,
withFileSize,
withFileType,
} from '@/portainer/helpers/yup-file-validation';
import { file, withFileSize } from '@/portainer/helpers/yup-file-validation';
import { FormValues } from './types';
@@ -82,16 +78,9 @@ export function TLSFieldset() {
}
const MAX_FILE_SIZE = 5_242_880; // 5MB
const ALLOWED_FILE_TYPES = [
'application/x-x509-ca-cert',
'application/x-pem-file',
];
function certValidation() {
return withFileType(
withFileSize(file(), MAX_FILE_SIZE),
ALLOWED_FILE_TYPES
).when(['tls', 'skipVerify'], {
return withFileSize(file(), MAX_FILE_SIZE).when(['tls', 'skipVerify'], {
is: (tls: boolean, skipVerify: boolean) => tls && !skipVerify,
then: (schema) => schema.required('File is required'),
});

View File

@@ -2,7 +2,7 @@
"author": "Portainer.io",
"name": "portainer",
"homepage": "http://portainer.io",
"version": "2.14.1",
"version": "2.14.2",
"repository": {
"type": "git",
"url": "git@github.com:portainer/portainer.git"