Skip to content

Commit

Permalink
Merge pull request #3172 from artbear/PrivilegedModuleMethodCall
Browse files Browse the repository at this point in the history
Правило "Обращение к методам привилегированных модулей"
  • Loading branch information
theshadowco authored Dec 18, 2023
2 parents 4c20e63 + e6b2d8e commit 58dd84b
Show file tree
Hide file tree
Showing 16 changed files with 765 additions and 0 deletions.
26 changes: 26 additions & 0 deletions docs/diagnostics/PrivilegedModuleMethodCall.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Обращение к методам привилегированных модулей (PrivilegedModuleMethodCall)

<!-- Блоки выше заполняются автоматически, не трогать -->
## Описание диагностики
<!-- Описание диагностики заполняется вручную. Необходимо понятным языком описать смысл и схему работу -->
При обращении к публичным процедурам и функциям привилегированных общих модулей могут нарушаться ограничения конфигурации по правам и ролям конфигурации, решения 1С.
Необходимо провалидировать подобные обращения для исключения обхода ограничений.

## Примеры
<!-- В данном разделе приводятся примеры, на которые диагностика срабатывает, а также можно привести пример, как можно исправить ситуацию -->
Например, в конфигурации существует привилегированный модуль выполнения заданий с именем `Задания`.
В этом модуле есть публичная функция `Функция ДобавитьЗадание(Знач ИмяМетода, Знач Параметры) Экспорт`.
Какой-то код конфигурации или расширения обращается к этому методу `Задания.ДобавитьЗадание("МетодДляВыполнения", Параметры);`

Необходимо проанализировать код и убедиться в том, что:
- указан правильный метод для выполнения задания - `МетодДляВыполнения`
- метод `МетодДляВыполнения` для выполнения задания не выполняет деструктивных действий
- и не выдает пользователям данные, запрещенные ограничениями конфигурации

## Источники
<!-- Необходимо указывать ссылки на все источники, из которых почерпнута информация для создания диагностики -->
<!-- Примеры источников
* Источник: [Стандарт: Тексты модулей](https://its.1c.ru/db/v8std#content:456:hdoc)
* Полезная информация: [Отказ от использования модальных окон](https://its.1c.ru/db/metod8dev#content:5272:hdoc)
* Источник: [Cognitive complexity, ver. 1.4](https://www.sonarsource.com/docs/CognitiveComplexity.pdf) -->
16 changes: 16 additions & 0 deletions docs/en/diagnostics/PrivilegedModuleMethodCall.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Accessing privileged module methods (PrivilegedModuleMethodCall)

<!-- Блоки выше заполняются автоматически, не трогать -->
## Description
<!-- Описание диагностики заполняется вручную. Необходимо понятным языком описать смысл и схему работу -->

## Examples
<!-- В данном разделе приводятся примеры, на которые диагностика срабатывает, а также можно привести пример, как можно исправить ситуацию -->

## Sources
<!-- Необходимо указывать ссылки на все источники, из которых почерпнута информация для создания диагностики -->
<!-- Примеры источников
* Источник: [Стандарт: Тексты модулей](https://its.1c.ru/db/v8std#content:456:hdoc)
* Полезная информация: [Отказ от использования модальных окон](https://its.1c.ru/db/metod8dev#content:5272:hdoc)
* Источник: [Cognitive complexity, ver. 1.4](https://www.sonarsource.com/docs/CognitiveComplexity.pdf) -->
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
/*
* This file is a part of BSL Language Server.
*
* Copyright (c) 2018-2023
* Alexey Sosnoviy <labotamy@gmail.com>, Nikita Fedkin <nixel2007@gmail.com> and contributors
*
* SPDX-License-Identifier: LGPL-3.0-or-later
*
* BSL Language Server is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3.0 of the License, or (at your option) any later version.
*
* BSL Language Server is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with BSL Language Server.
*/
package com.github._1c_syntax.bsl.languageserver.diagnostics;

import com.github._1c_syntax.bsl.languageserver.context.symbol.ModuleSymbol;
import com.github._1c_syntax.bsl.languageserver.diagnostics.metadata.DiagnosticMetadata;
import com.github._1c_syntax.bsl.languageserver.diagnostics.metadata.DiagnosticParameter;
import com.github._1c_syntax.bsl.languageserver.diagnostics.metadata.DiagnosticScope;
import com.github._1c_syntax.bsl.languageserver.diagnostics.metadata.DiagnosticSeverity;
import com.github._1c_syntax.bsl.languageserver.diagnostics.metadata.DiagnosticTag;
import com.github._1c_syntax.bsl.languageserver.diagnostics.metadata.DiagnosticType;
import com.github._1c_syntax.bsl.languageserver.references.ReferenceIndex;
import com.github._1c_syntax.bsl.languageserver.references.model.Reference;
import com.github._1c_syntax.bsl.types.ModuleType;
import com.github._1c_syntax.mdclasses.mdo.MDCommonModule;
import lombok.RequiredArgsConstructor;
import org.eclipse.lsp4j.SymbolKind;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

@DiagnosticMetadata(
type = DiagnosticType.SECURITY_HOTSPOT,
severity = DiagnosticSeverity.MAJOR,
minutesToFix = 60,
tags = {
DiagnosticTag.SUSPICIOUS
},
scope = DiagnosticScope.BSL
)
@RequiredArgsConstructor
public class PrivilegedModuleMethodCallDiagnostic extends AbstractDiagnostic {

private static final boolean VALIDATE_NESTED_CALLS = true;

@DiagnosticParameter(
type = Boolean.class,
defaultValue = "" + VALIDATE_NESTED_CALLS
)
private boolean validateNestedCalls = VALIDATE_NESTED_CALLS;

private final ReferenceIndex referenceIndex;
private List<ModuleSymbol> privilegedModuleSymbols = new ArrayList<>();

@Override
protected void check() {
if (privilegedModuleSymbols.isEmpty()){
privilegedModuleSymbols = getPrivilegedModuleSymbols();
}
if (privilegedModuleSymbols.isEmpty()){
return;
}

referenceIndex.getReferencesFrom(documentContext.getUri(), SymbolKind.Method).stream()
.filter(this::isReferenceToModules)
.forEach(this::fireIssue);
}

private List<ModuleSymbol> getPrivilegedModuleSymbols() {
return documentContext.getServerContext().getConfiguration().getCommonModules()
.values().stream()
.filter(MDCommonModule::isPrivileged)
.flatMap(mdCommonModule -> getPrivilegedModuleSymbol(mdCommonModule).stream())
.toList();
}

private Optional<ModuleSymbol> getPrivilegedModuleSymbol(MDCommonModule mdCommonModule) {
return documentContext.getServerContext().getDocument(
mdCommonModule.getMdoReference().getMdoRef(), ModuleType.CommonModule)
.map(documentContext1 -> documentContext1.getSymbolTree().getModule());
}

private boolean isReferenceToModules(Reference reference) {
if (!validateNestedCalls && reference.getUri().equals(documentContext.getUri())){
return false;
}
return reference.getSourceDefinedSymbol()
.flatMap(sourceDefinedSymbol -> sourceDefinedSymbol.getRootParent(SymbolKind.Module))
.filter(ModuleSymbol.class::isInstance)
.map(ModuleSymbol.class::cast)
.filter(privilegedModuleSymbols::contains)
.isPresent();
}

private void fireIssue(Reference reference) {
diagnosticStorage.addDiagnostic(reference.getSelectionRange(),
info.getMessage(reference.getSymbol().getName()));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1496,6 +1496,24 @@
"title": "Source code parse error",
"$id": "#/definitions/ParseError"
},
"PrivilegedModuleMethodCall": {
"description": "Accessing privileged module methods",
"default": true,
"type": [
"boolean",
"object"
],
"title": "Accessing privileged module methods",
"properties": {
"validateNestedCalls": {
"description": "Validate nested method calls from privileged modules",
"default": true,
"type": "boolean",
"title": "Validate nested method calls from privileged modules"
}
},
"$id": "#/definitions/PrivilegedModuleMethodCall"
},
"ProcedureReturnsValue": {
"description": "Procedure should not return Value",
"default": true,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
diagnosticMessage=Check the %s method access of the privileged module
diagnosticName=Accessing privileged module methods
validateNestedCalls=Validate nested method calls from privileged modules
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
diagnosticMessage=Проверьте обращение к методу %s привилегированного модуля
diagnosticName=Обращение к методам привилегированных модулей
validateNestedCalls=Проверять вложенные вызовы методов из привилегированных модулей
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/*
* This file is a part of BSL Language Server.
*
* Copyright (c) 2018-2023
* Alexey Sosnoviy <labotamy@gmail.com>, Nikita Fedkin <nixel2007@gmail.com> and contributors
*
* SPDX-License-Identifier: LGPL-3.0-or-later
*
* BSL Language Server is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3.0 of the License, or (at your option) any later version.
*
* BSL Language Server is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with BSL Language Server.
*/
package com.github._1c_syntax.bsl.languageserver.diagnostics;

import org.eclipse.lsp4j.Diagnostic;
import org.junit.jupiter.api.Test;

import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.Map;

import static com.github._1c_syntax.bsl.languageserver.util.Assertions.assertThat;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;

class PrivilegedModuleMethodCallDiagnosticTest extends AbstractDiagnosticTest<PrivilegedModuleMethodCallDiagnostic> {
private static final String PATH_TO_METADATA = "src/test/resources/metadata/privilegedModules";
private static final String PATH_TO_MODULE_FILE = PATH_TO_METADATA + "/CommonModules/ПривилегированныйМодуль1/Ext/Module.bsl";

PrivilegedModuleMethodCallDiagnosticTest() {
super(PrivilegedModuleMethodCallDiagnostic.class);
}

@Test
void testWithoutMetadata() {
var diagnostics = getDiagnostics();
assertThat(diagnostics).isEmpty();
}

@Test
void test() {
initServerContext(PATH_TO_METADATA);

var diagnostics = getDiagnostics();

assertThat(diagnostics).hasSize(2);
assertThat(diagnostics, true)
.hasMessageOnRange("Проверьте обращение к методу ПубличнаяФункция привилегированного модуля", 3, 40, 56)
.hasMessageOnRange("Проверьте обращение к методу ПубличнаяПроцедура привилегированного модуля", 4, 29, 47);
}

@Test
void getNestedCalls() {
var diagnostics = getDiagnosticsAsCommonModule();
assertThat(diagnostics).hasSize(2);
assertThat(diagnostics, true)
.hasMessageOnRange("Проверьте обращение к методу ПубличнаяФункция привилегированного модуля", 15, 15, 31)
.hasMessageOnRange("Проверьте обращение к методу ПубличнаяПроцедура привилегированного модуля", 19, 4, 22);
}

@Test
void testParameterValidateNestedCalls() {
Map<String, Object> configuration = diagnosticInstance.getInfo().getDefaultConfiguration();
configuration.put("validateNestedCalls", false);
diagnosticInstance.configure(configuration);

var diagnostics = getDiagnosticsAsCommonModule();
assertThat(diagnostics).isEmpty();
}

private List<Diagnostic> getDiagnosticsAsCommonModule() {
Path moduleFile = Paths.get(PATH_TO_MODULE_FILE).toAbsolutePath();

initServerContext(PATH_TO_METADATA);

var documentContext = spy(getDocumentContext(diagnosticInstance.getClass().getSimpleName()));
when(documentContext.getUri()).thenReturn(moduleFile.toUri());

return getDiagnostics(documentContext);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#Область ПрограммныйИнтерфейс

Функция Тест1()
Значение = ПривилегированныйМодуль1.ПубличнаяФункция(); // ошибка
ПривилегированныйМодуль1.ПубличнаяПроцедура(); // ошибка
КонецФункции

Процедура Тест2()
Значение = ПривилегированныйМодуль1.ПриватнаяФункция(); // не ошибка в данном правиле
ПривилегированныйМодуль1.ПриватнаяПроцедура(); // не ошибка в данном правиле
КонецПроцедуры

#КонецОбласти

#Область СлужебныеПроцедурыИФункции

#КонецОбласти
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.11">
<CommonModule uuid="8bec7488-90ab-484a-bdc9-ce1584d95925">
<Properties>
<Name>ОбщийМодуль2</Name>
<Synonym/>
<Comment/>
<Global>false</Global>
<ClientManagedApplication>false</ClientManagedApplication>
<Server>true</Server>
<ExternalConnection>false</ExternalConnection>
<ClientOrdinaryApplication>false</ClientOrdinaryApplication>
<ServerCall>false</ServerCall>
<Privileged>false</Privileged>
<ReturnValuesReuse>DontUse</ReturnValuesReuse>
</Properties>
</CommonModule>
</MetaDataObject>
Loading

0 comments on commit 58dd84b

Please sign in to comment.