Esta página descreve as regras de qualidade de código personalizadas executadas pelo Cloud Manager criadas com base nas práticas recomendadas da AEM Engineering.
As amostras de código fornecidas aqui são apenas para fins ilustrativos. Consulte Conceitos para saber mais sobre os conceitos e as regras de qualidade do SonarQube.
A seção a seguir destaca as regras do SonarQube:
Chave: CQRules:CWE-676
Tipo: Vulnerabilidade
Gravidade: Major
Desde: Versão 2018.4.0
Os métodos Thread.stop() e Thread.interrupt() podem gerar problemas de difícil reprodução e, em alguns casos, vulnerabilidades de segurança. A utilização deve ser rigorosamente monitorizada e validada. Em geral, a transmissão de mensagens é uma forma mais segura de atingir objetivos semelhantes.
public class DontDoThis implements Runnable {
private Thread thread;
public void start() {
thread = new Thread(this);
thread.start();
}
public void stop() {
thread.stop(); // UNSAFE!
}
public void run() {
while (true) {
somethingWhichTakesAWhileToDo();
}
}
}
public class DoThis implements Runnable {
private Thread thread;
private boolean keepGoing = true;
public void start() {
thread = new Thread(this);
thread.start();
}
public void stop() {
keepGoing = false;
}
public void run() {
while (this.keepGoing) {
somethingWhichTakesAWhileToDo();
}
}
}
Chave: CQRules:CWE-134
Tipo: Vulnerabilidade
Gravidade: Major
Desde: Versão 2018.4.0
O uso de uma string de formato de uma fonte externa (como um parâmetro de solicitação ou conteúdo gerado pelo usuário) pode produzir pode expor um aplicativo a ataques de negação de serviço. Há circunstâncias em que uma string de formato pode ser controlada externamente, mas só é permitida de fontes confiáveis.
protected void doPost(SlingHttpServletRequest request, SlingHttpServletResponse response) {
String messageFormat = request.getParameter("messageFormat");
request.getResource().getValueMap().put("some property", String.format(messageFormat, "some text"));
response.sendStatus(HttpServletResponse.SC_OK);
}
Chave: CQRules:ConnectionTimeoutMechanism
Tipo: Bug
Gravidade: Crítico
Desde: Versão 2018.6.0
Ao executar solicitações HTTP de dentro de um aplicativo AEM, é essencial garantir que os tempos limite apropriados sejam configurados para evitar o consumo desnecessário de thread. Infelizmente, o comportamento padrão do cliente HTTP padrão do Java (java.net.HttpUrlConnection) e do cliente Apache HTTP Components comumente usado é nunca expirar, portanto, os tempos limite devem ser definidos explicitamente. Além disso, como prática recomendada, esses tempos limite não devem exceder 60 segundos.
@Reference
private HttpClientBuilderFactory httpClientBuilderFactory;
public void dontDoThis() {
HttpClientBuilder builder = httpClientBuilderFactory.newBuilder();
HttpClient httpClient = builder.build();
// do something with the client
}
public void dontDoThisEither() {
URL url = new URL("http://www.google.com");
URLConnection urlConnection = url.openConnection();
BufferedReader in = new BufferedReader(new InputStreamReader(
urlConnection.getInputStream()));
String inputLine;
while ((inputLine = in.readLine()) != null) {
logger.info(inputLine);
}
in.close();
}
@Reference
private HttpClientBuilderFactory httpClientBuilderFactory;
public void doThis() {
HttpClientBuilder builder = httpClientBuilderFactory.newBuilder();
RequestConfig requestConfig = RequestConfig.custom()
.setConnectTimeout(5000)
.setSocketTimeout(5000)
.build();
builder.setDefaultRequestConfig(requestConfig);
HttpClient httpClient = builder.build();
// do something with the client
}
public void orDoThis() {
URL url = new URL("http://www.google.com");
URLConnection urlConnection = url.openConnection();
urlConnection.setConnectTimeout(5000);
urlConnection.setReadTimeout(5000);
BufferedReader in = new BufferedReader(new InputStreamReader(
urlConnection.getInputStream()));
String inputLine;
while ((inputLine = in.readLine()) != null) {
logger.info(inputLine);
}
in.close();
}
Chave: CQBP-84, CQBP-84-dependências
Tipo: Bug
Gravidade: Crítico
Desde: Versão 2018.7.0
A API do AEM contém interfaces e classes do Java que devem ser usadas, mas não implementadas, apenas pelo código personalizado. Por exemplo, a interface com.day.cq.wcm.api.Page foi projetada para ser implementada somente pelo AEM.
Quando novos métodos são adicionados a essas interfaces, esses métodos adicionais não afetam o código existente que usa essas interfaces e, como resultado, a adição de novos métodos a essas interfaces é considerada compatível com versões anteriores. No entanto, se o código personalizado implementa uma dessas interfaces, ele apresenta um risco de compatibilidade com versões anteriores para o cliente.
As interfaces (e classes) que só devem ser implementadas por AEM são anotadas com org.osgi.annotation.versioning.ProviderType (ou, em alguns casos, uma anotação herdada similar Qute.bnd.annotation.ProviderType). Essa regra identifica os casos em que tal interface é implementada (ou uma classe é estendida) pelo código personalizado.
import com.day.cq.wcm.api.Page;
public class DontDoThis implements Page {
// implementation here
}
Chave: CQRules:CQBP-72
Tipo: Cheiro de código
Gravidade: Major
Desde: Versão 2018.4.0
Os objetos ResourceResolver obtidos a partir do ResourceResolverFactory consomem recursos do sistema. Embora existam medidas para recuperar esses recursos quando um ResourceResolver não estiver mais em uso, é mais eficiente fechar explicitamente quaisquer objetos ResourceResolver abertos chamando o método close().
Um equívoco relativamente comum é que os objetos ResourceResolver criados usando uma Sessão JCR existente não devem ser explicitamente fechados ou que isso fechará a Sessão JCR subjacente. Esse não é o caso - independentemente de como um ResourceResolver é aberto, ele deve ser fechado quando não for mais usado. Como o ResourceResolver implementa a interface Closeable, também é possível usar a sintaxe try-with-resources em vez de chamar explicitamente close().
public void dontDoThis(Session session) throws Exception {
ResourceResolver resolver = factory.getResourceResolver(Collections.singletonMap("user.jcr.session", (Object)session));
// do some stuff with the resolver
}
public void doThis(Session session) throws Exception {
ResourceResolver resolver = null;
try {
resolver = factory.getResourceResolver(Collections.singletonMap("user.jcr.session", (Object)session));
// do something with the resolver
} finally {
if (resolver != null) {
resolver.close();
}
}
}
public void orDoThis(Session session) throws Exception {
try (ResourceResolver resolver = factory.getResourceResolver(Collections.singletonMap("user.jcr.session", (Object) session))){
// do something with the resolver
}
}
Chave: CQRules:CQBP-75
Tipo: Cheiro de código
Gravidade: Major
Desde: Versão 2018.4.0
Conforme descrito na Documentação Sling, os servlets de vinculação por caminhos são desencorajados. Servlets com caminho não podem usar controles de acesso JCR padrão e, como resultado, exigem rigor de segurança adicional. Em vez de usar servlets vinculados a caminho, é recomendável criar nós no repositório e registrar servlets por tipo de recurso.
@Component(property = {
"sling.servlet.paths=/apps/myco/endpoint"
})
public class DontDoThis extends SlingAllMethodsServlet {
// implementation
}
Chave: CQRules:CQBP-44—CatchAndOrThrow
Tipo: Cheiro de código
Gravidade: Menor
Desde: Versão 2018.4.0
Em geral, uma exceção deve ser registrada exatamente uma vez. O registro de exceções várias vezes pode causar confusão, pois não está claro quantas vezes uma exceção ocorreu. O padrão mais comum que leva a isso é cortar e lançar uma exceção.
public void dontDoThis() throws Exception {
try {
someOperation();
} catch (Exception e) {
logger.error("something went wrong", e);
throw e;
}
}
public void doThis() {
try {
someOperation();
} catch (Exception e) {
logger.error("something went wrong", e);
}
}
public void orDoThis() throws MyCustomException {
try {
someOperation();
} catch (Exception e) {
throw new MyCustomException(e);
}
}
Chave: CQRules:CQBP-44—ConsecutivelyLogAndThrow
Tipo: Cheiro de código
Gravidade: Menor
Desde: Versão 2018.4.0
Outro padrão comum a ser evitado é registrar uma mensagem e imediatamente lançar uma exceção. Isso geralmente indica que a mensagem de exceção acabará duplicada nos arquivos de registro.
public void dontDoThis() throws Exception {
logger.error("something went wrong");
throw new RuntimeException("something went wrong");
}
public void doThis() throws Exception {
throw new RuntimeException("something went wrong");
}
Chave: CQRules:CQBP-44—LogInfoInGetOrHeadRequests
Tipo: Cheiro de código
Gravidade: Menor
Em geral, o nível de log INFO deve ser usado para demarcar ações importantes e, por padrão, AEM configurado para fazer logon no nível INFO ou acima. Os métodos de GET e de HEAD só devem ser operações só de leitura e, por conseguinte, não constituem ações importantes. É provável que o registro no nível INFO em resposta a solicitações de GET ou HEAD crie um ruído significativo no registro, dificultando a identificação de informações úteis em arquivos de registro. O registro ao lidar com solicitações de GET ou HEAD deve estar nos níveis de WARN ou ERROR quando algo deu errado ou nos níveis de DEBUG ou TRACE se informações mais detalhadas sobre solução de problemas forem úteis.
Isso não se aplica ao log access.log-type para cada solicitação.
public void doGet() throws Exception {
logger.info("handling a request from the user");
}
public void doGet() throws Exception {
logger.debug("handling a request from the user.");
}
Chave: CQRules:CQBP-44—ExceptionGetMessageIsFirstLogParam
Tipo: Cheiro de código
Gravidade: Menor
Desde: Versão 2018.4.0
Como prática recomendada, as mensagens de log devem fornecer informações contextuais sobre onde ocorreu uma exceção no aplicativo. Embora o contexto também possa ser determinado pelo uso de rastreamentos de pilha, em geral a mensagem de registro será mais fácil de ler e entender. Como resultado, ao registrar uma exceção, é uma prática ruim usar a mensagem de exceção como a mensagem de registro - a mensagem de exceção conterá o que deu errado, enquanto a mensagem de registro deve ser usada para informar ao leitor de log o que o aplicativo estava fazendo quando a exceção aconteceu. A mensagem de exceção continuará sendo registrada; ao especificar sua própria mensagem, os registros serão mais fáceis de entender.
public void dontDoThis() {
try {
someMethodThrowingAnException();
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
}
public void doThis() {
try {
someMethodThrowingAnException();
} catch (Exception e) {
logger.error("Unable to do something", e);
}
}
Chave: CQRules:CQBP-44—WrongLogLevelInCatchBlock
Tipo: Cheiro de código
Gravidade: Menor
Desde: Versão 2018.4.0
Como o nome sugere, as exceções Java devem ser sempre usadas em excepcionais circunstâncias. Como resultado, quando uma exceção é detectada, é importante garantir que as mensagens de log sejam registradas no nível apropriado - WARN ou ERROR. Isso garante que essas mensagens sejam exibidas corretamente nos registros.
public void dontDoThis() {
try {
someMethodThrowingAnException();
} catch (Exception e) {
logger.debug(e.getMessage(), e);
}
}
public void doThis() {
try {
someMethodThrowingAnException();
} catch (Exception e) {
logger.error("Unable to do something", e);
}
}
Chave: CQRules:CQBP-44—ExceptionPrintStackTrace
Tipo: Cheiro de código
Gravidade: Menor
Desde: Versão 2018.4.0
Como mencionado, o contexto é crítico ao entender as mensagens de log. O uso de Exception.printStackTrace() faz com que only o rastreamento da pilha seja enviado para o fluxo de erro padrão, perdendo todo o contexto. Além disso, em um aplicativo de vários processos, como AEM se várias exceções forem impressas usando esse método em paralelo, seus traços de pilha podem se sobrepor, o que gera confusão significativa. As exceções devem ser registradas somente pela estrutura de registro.
public void dontDoThis() {
try {
someMethodThrowingAnException();
} catch (Exception e) {
e.printStackTrace();
}
}
public void doThis() {
try {
someMethodThrowingAnException();
} catch (Exception e) {
logger.error("Unable to do something", e);
}
}
Chave: CQRules:CQBP-44—LogLevelConsolePrinters
Tipo: Cheiro de código
Gravidade: Menor
Desde: Versão 2018.4.0
O logon do AEM deve ser sempre feito por meio da estrutura de registro (SLF4J). A saída diretamente para a saída padrão ou fluxos de erro padrão perde as informações estruturais e contextuais fornecidas pela estrutura de registro e pode, em alguns casos, causar problemas de desempenho.
public void dontDoThis() {
try {
someMethodThrowingAnException();
} catch (Exception e) {
System.err.println("Unable to do something");
}
}
public void doThis() {
try {
someMethodThrowingAnException();
} catch (Exception e) {
logger.error("Unable to do something", e);
}
}
Chave: CQRules:CQBP-71
Tipo: Cheiro de código
Gravidade: Menor
Desde: Versão 2018.4.0
Em geral, os caminhos que são start com /libs e /apps não devem ser codificados, pois os caminhos a que se referem são mais comumente armazenados como caminhos relativos ao caminho de pesquisa Sling (que é definido como /libs,/apps por padrão). O uso do caminho absoluto pode apresentar defeitos sutis que só apareceriam posteriormente no ciclo de vida do projeto.
public boolean dontDoThis(Resource resource) {
return resource.isResourceType("/libs/foundation/components/text");
}
public void doThis(Resource resource) {
return resource.isResourceType("foundation/components/text");
}
Chave: CQRules:AMSCORE-554
Tipo: Compatibilidade entre código e Cloud Service
Gravidade: Menor
Desde: Versão 2020.5.0
O Scheduler Sling não deve ser usado para tarefas que exigem uma execução garantida. As Tarefas Agendadas de Varejo garantem a execução e são mais adequadas para ambientes clusterizados e não clusterizados.
Consulte Apache Sling Event and Job Handling para saber mais sobre como os Sling Jobs são tratados em ambientes agrupados.
Chave: AMSCORE-553
Tipo: Compatibilidade entre código e Cloud Service
Gravidade: Menor
Desde: Versão 2020.5.0
A superfície da API AEM está em revisão constante para identificar APIs para as quais o uso é desencorajado e, portanto, considerado obsoleto.
Em muitos casos, essas APIs são descontinuadas usando a anotação padrão do Java @Deprecated e, como tal, como identificado por squid:CallToDeprecatedMethod
.
No entanto, há casos em que uma API é obsoleta no contexto de AEM, mas pode não ser obsoleta em outros contextos. Essa regra identifica essa segunda classe.
Encontre abaixo as verificações do OakPAL executadas pelo Cloud Manager.
OakPAL é uma estrutura desenvolvida por um parceiro AEM (e vencedor de 2019 AEM Rockstar North America) que valida pacotes de conteúdo usando um repositório Oak independente.
Chave: Caminhos Banidos
Tipo: Bug
Gravidade: Bloqueador
Desde: Versão 2019.6.0
É uma prática recomendada antiga que a árvore de conteúdo /libs no repositório de conteúdo AEM seja considerada somente leitura pelos clientes. Modificar nós e propriedades em /libs cria riscos significativos para atualizações principais e secundárias. As modificações em /libs só devem ser feitas por Adobe através de canais oficiais.
Chave: DuplicateOsgiConfigurations
Tipo: Bug
Gravidade: Major
Desde: Versão 2019.6.0
Um problema comum que ocorre em projetos complexos é onde o mesmo componente OSGi é configurado várias vezes. Isso cria uma ambiguidade sobre qual configuração será operável. Essa regra é "sensível ao modo de execução", pois identificará apenas problemas em que o mesmo componente é configurado várias vezes no mesmo modo de execução (ou combinação de modos de execução).
+ projectA
+ config
+ com.day.cq.commons.impl.ExternalizerImpl
+ projectB
+ config
+ com.day.cq.commons.impl.ExternalizerImpl
+ shared-config
+ config
+ com.day.cq.commons.impl.ExternalizerImpl
Chave: ConfigAndInstallshouldOnlyContainOsgiNodes
Tipo: Bug
Gravidade: Major
Desde: Versão 2019.6.0
Por motivos de segurança, os caminhos que contêm /config/ e /install/ só podem ser lidos por usuários administrativos no AEM e devem ser usados apenas para configuração OSGi e pacotes OSGi. Colocar outros tipos de conteúdo em caminhos que contêm esses segmentos resulta em comportamento de aplicativo que varia involuntariamente entre usuários administrativos e não administrativos.
Um problema comum é o uso de nós chamados config
nas caixas de diálogo do componente ou ao especificar a configuração do editor de rich text para edição em linha. Para resolver isso, o nó que está causando a ofensa deve ser renomeado para um nome compatível. Para a configuração do editor de rich text, use a propriedade configPath
no nó cq:inplaceEditing
para especificar o novo local.
+ cq:editConfig [cq:EditConfig]
+ cq:inplaceEditing [cq:InplaceEditConfig]
+ config [nt:unstructured]
+ rtePlugins [nt:unstructured]
+ cq:editConfig [cq:EditConfig]
+ cq:inplaceEditing [cq:InplaceEditConfig]
./configPath = inplaceEditingConfig (String)
+ inplaceEditingConfig [nt:unstructured]
+ rtePlugins [nt:unstructured]
Chave: PackageOverlaps
Tipo: Bug
Gravidade: Major
Desde: Versão 2019.6.0
Semelhante aos pacotes não devem conter configurações OSGi de Duplicado, esse é um problema comum em projetos complexos nos quais o mesmo caminho de nó é gravado por vários pacotes de conteúdo separados. Embora seja possível usar dependências de pacote de conteúdo para garantir um resultado consistente, é melhor evitar sobreposições completamente.
Chave: ClassicUIAuthoringMode
Tipo: Compatibilidade entre código e Cloud Service
Gravidade: Menor
Desde: Versão 2020.5.0
A configuração do OSGi com.day.cq.wcm.core.impl.AuthoringUIModeServiceImpl
define o modo de criação padrão no AEM. Como a interface clássica está obsoleta desde o AEM 6.4, um problema agora será gerado quando o modo de criação padrão estiver configurado para a interface clássica.
Chave: ComponentWithOnlyClassicUIDialog
Tipo: Compatibilidade entre código e Cloud Service
Gravidade: Menor
Desde: Versão 2020.5.0
AEM Os componentes que têm uma caixa de diálogo de interface clássica devem ter sempre uma caixa de diálogo de interface de usuário de toque correspondente, tanto para fornecer uma experiência de criação ideal quanto para serem compatíveis com o modelo de implantação de Cloud Service, onde a interface de usuário clássica não é suportada. Essa regra verifica os seguintes cenários:
cq:dialog
).cq:design_dialog
nó filho).A documentação das Ferramentas de modernização AEM fornece documentação e ferramentas para converter componentes da interface clássica para a interface de usuário de toque. Consulte As Ferramentas de Modernização AEM para obter mais detalhes.
Chave: ImmutableMutableMixedPackage
Tipo: Compatibilidade entre código e Cloud Service
Gravidade: Menor
Desde: Versão 2020.5.0
Para serem compatíveis com o modelo de implantação do Cloud Service, os pacotes de conteúdo individuais devem conter conteúdo para as áreas imutáveis do repositório (ou seja, /apps and /libs, although /libs
não deve ser modificado pelo código do cliente e causará uma violação separada) ou a área mutável (ou seja, tudo o mais), mas não ambos. Por exemplo, um pacote que inclui /apps/myco/components/text and /etc/clientlibs/myco
não é compatível com o Cloud Service e fará com que um problema seja relatado.
Consulte AEM Estrutura do Projeto para obter mais detalhes.
Chave: ReverseReplication
Tipo: Compatibilidade entre código e Cloud Service
Gravidade: Menor
Desde: Versão 2020.5.0
O suporte para replicação reversa não está disponível em implantações de Cloud Service, conforme descrito em Notas de versão: Remoção dos Agentes de Replicação.
Os clientes que usam replicação reversa devem entrar em contato com a Adobe para obter soluções alternativas.