August 19, 2023

Spring Boot 3 and Keycloak with Oauth2 Resource Server (JWT) for REST integration

Introduction

Spring Security OAuth2 Login does NOT support authentication with Access Token that you might first think.

https://docs.spring.io/spring-security/reference/servlet/oauth2/client/index.html

The JWT Bearer is something different, that I have seen rarely used

     POST /token.oauth2 HTTP/1.1
     Host: as.example.com
     Content-Type: application/x-www-form-urlencoded

     grant_type=authorization_code&
     code=n0esc3NRze7LTCu7iYzS6a5acc3f0ogp4&
     client_assertion_type=urn%3Aietf%3Aparams%3Aoauth%3A
     client-assertion-type%3Ajwt-bearer&
     client_assertion=eyJhbGciOiJSUzI1NiIsImtpZCI6IjIyIn0.
     eyJpc3Mi[...omitted for brevity...].
     cC4hiUPo[...omitted for brevity...]

What you normally do, to access a OAuth2 protected resources is

GET / HTTP/1.1
Authorization: Bearer some-token-value # Resource Server will process this

And to setup backend for that you need Spring Security 6 OAuth2 Resource Server.

Prerequisite

  • Java 17
  • Maven 3.6.3 or later
  • Spring 3.1.2
  • Spring Security 6.1.2 Resource Server
  • Keycloak. I will use the commercial version RH SSO 7.6.0
  • OAuth2 Resource Owner Password Credentials Grant https://datatracker.ietf.org/doc/html/rfc6749#section-4.3
  • Not neccessary but convenient jq - Command-line JSON processor (on Fedora $ sudo dnf install jq)

Application

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.1.2</version>
        <relativePath /> <!-- lookup parent from repository -->
    </parent>
    <groupId>se.mkk</groupId>
    <artifactId>spring-boot-3-oauth2-resource-server-keycloak</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>spring-boot-3-oauth2-resource-server-keycloak</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>17</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

src/main/resources/application.properties

jwk-set-uri is not neccessary since spring security reads http://localhost:8180/auth/realms/demo/.well-known/openid-configuration.

# https://docs.spring.io/spring-security/reference/servlet/oauth2/resource-server/jwt.html
# https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#appendix.application-properties.security
spring.security.oauth2.resourceserver.jwt.issuer-uri = http://localhost:8180/auth/realms/demo
#spring.security.oauth2.resourceserver.jwt.jwk-set-uri = http://localhost:8180/auth/realms/demo/protocol/openid-connect/certs

Java code

package se.mkk.springboot3oauth2resourceserverkeycloak;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

Simple REST endpoint

package se.mkk.springboot3oauth2resourceserverkeycloak;

import java.security.Principal;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

//import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import jakarta.servlet.http.HttpServletRequest;

@RestController
@RequestMapping("/api/users")
public class UserController {

    @GetMapping
    public Map<String, String> getUser(HttpServletRequest request, Principal principal) {
        Map<String, String> rtn = new LinkedHashMap<>();
        rtn.put("request.getRemoteUser()", request.getRemoteUser());
        rtn.put("request.isUserInRole(\"USER\")", Boolean.toString(request.isUserInRole("USER")));
        rtn.put("request.getUserPrincipal().getClass()", request.getUserPrincipal().getClass().getName());
        rtn.put("principal.getClass().getName()", principal.getClass().getName());
        rtn.put("principal.getName()", principal.getName());
        if (principal instanceof JwtAuthenticationToken token) {
            List<String> authorities = token.getAuthorities().stream()
                    .map(grantedAuthority -> grantedAuthority.getAuthority()).toList();
            rtn.put("JwtAuthenticationToken.getAuthorities()", authorities.toString());
        }
        return rtn;
    }
}

The OAuth2 Resource Server code. To make it work with Keycloak we need 2 adjustment.

  1. Change username to preferred_username - jwtAuthenticationConverter.setPrincipalClaimName("preferred_username")
  2. Read Roles Not Scopes - jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(new KeycloakAuthoritiesConverter());
package se.mkk.springboot3oauth2resourceserverkeycloak;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.converter.Converter;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
@EnableWebSecurity
@EnableMethodSecurity
public class OAuth2ResourceServerSecurityConfig {

    // https://docs.spring.io/spring-security/reference/servlet/oauth2/resource-server/jwt.html#oauth2resourceserver-jwt-sansboot
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http //
                .authorizeHttpRequests(authorize -> authorize //
                        .anyRequest().authenticated()) //
                .oauth2ResourceServer(oauth2 -> oauth2 //
                        .jwt(jwt -> jwt //
                                .jwtAuthenticationConverter(this.jwtAuthenticationConverter())));
        return http.build();
    }

    // https://docs.spring.io/spring-security/reference/servlet/oauth2/resource-server/jwt.html#oauth2resourceserver-jwt-authorization-extraction
    private JwtAuthenticationConverter jwtAuthenticationConverter() {
        JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
        jwtAuthenticationConverter.setPrincipalClaimName("preferred_username");
        jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(new KeycloakAuthoritiesConverter());
        return jwtAuthenticationConverter;
    }

    // Spring OAuth2 uses default Scopes Not Roles for Authorization
    // org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter
    private class KeycloakAuthoritiesConverter implements Converter<Jwt, Collection<GrantedAuthority>> {

        @Override
        public Collection<GrantedAuthority> convert(Jwt jwt) {
            return convert(jwt.getClaims());
        }

        public Collection<GrantedAuthority> convert(Map<String, Object> claims) {
            Collection<GrantedAuthority> grantedAuthorities = new ArrayList<>();
            for (String authority : getAuthorities(claims)) {
                grantedAuthorities.add(new SimpleGrantedAuthority("ROLE_" + authority));
            }
            return grantedAuthorities;
        }

        private Collection<String> getAuthorities(Map<String, Object> claims) {
            Object realm_access = claims.get("realm_access");
            if (realm_access instanceof Map) {
                Map<String, Object> map = castAuthoritiesToMap(realm_access);
                Object roles = map.get("roles");
                if (roles instanceof Collection) {
                    return castAuthoritiesToCollection(roles);
                }
            }
            return Collections.emptyList();
        }

        @SuppressWarnings("unchecked")
        private Map<String, Object> castAuthoritiesToMap(Object authorities) {
            return (Map<String, Object>) authorities;
        }

        @SuppressWarnings("unchecked")
        private Collection<String> castAuthoritiesToCollection(Object authorities) {
            return (Collection<String>) authorities;
        }
    }
}

Test

Get Access Token from Keycloak

$ ACCESS_TOKEN=$(curl -s -X POST \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  -u 'spring-boot3-oauth2-login:jO09Uwhi8oxTL3QnTKtYZ20ByQvB2qA0' \
  http://localhost:8180/auth/realms/demo/protocol/openid-connect/token \
  -d "grant_type=password&username=john&password=changeit" | jq -r .access_token)

Call REST api

$ curl -v -X GET -H 'Content-Type: application/json' \
  -H 'Accept: application/json' \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  http://localhost:8080/api/users | jq .
...
{
  "request.getRemoteUser()": "john",
  "request.isUserInRole(\"USER\")": "true",
  "request.getUserPrincipal().getClass()": "org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken",
  "principal.getClass().getName()": "org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken",
  "principal.getName()": "john",
  "JwtAuthenticationToken.getAuthorities()": "[ROLE_offline_access, ROLE_default-roles-demo, ROLE_uma_authorization, ROLE_USER]"
}

Source Code

https://github.com/magnuskkarlsson/spring-boot-3-oauth2-resource-server-keycloak

August 18, 2023

Spring Boot 3 and Keycloak with Oauth2 Log in (Authorization Code Grant)

Prerequisite

RH SSO/Keycloak

Download, unzip and create initial admin user and finally start at http://127.0.0.1:8180/. You could also use a Docker container.

$ ./add-user-keycloak.sh -u admin

$ ./standalone.sh -Djboss.socket.binding.port-offset=100

In Keycloak create

  • New Realm demo
  • Role USER
  • User john with password
  • Assign role USER to user john
  • Create OIDC client with Client ID spring-boot3-oauth2-login and Root URL http://localhost:8080/

Application

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.1.2</version>
        <relativePath /> <!-- lookup parent from repository -->
    </parent>
    <groupId>se.mkk</groupId>
    <artifactId>spring-boot-3-oauth2-login-keycloak</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>spring-boot-3-oauth2-login-keycloak</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>17</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-oauth2-client</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

src/main/resources/application.properties

# OAuth2 Log In Spring Boot 2.x Property Mappings
# https://docs.spring.io/spring-security/reference/servlet/oauth2/login/core.html#oauth2login-boot-property-mappings
spring.security.oauth2.client.registration.keycloak.client-id = spring-boot3-oauth2-login
spring.security.oauth2.client.registration.keycloak.client-secret = CHANGEME!!!
#spring.security.oauth2.client.registration.keycloak.client-authentication-method = 
spring.security.oauth2.client.registration.keycloak.authorization-grant-type = authorization_code
#spring.security.oauth2.client.registration.keycloak.redirect-uri =
spring.security.oauth2.client.registration.keycloak.scope = openid
#spring.security.oauth2.client.registration.keycloak.client-name =

#spring.security.oauth2.client.provider.keycloak.authorization-uri
#spring.security.oauth2.client.provider.keycloak.token-uri
#spring.security.oauth2.client.provider.keycloak.jwk-set-uri
spring.security.oauth2.client.provider.keycloak.issuer-uri = http://localhost:8180/auth/realms/demo
#spring.security.oauth2.client.provider.keycloak.user-info-uri
#spring.security.oauth2.client.provider.keycloak.user-info-authentication-method
spring.security.oauth2.client.provider.keycloak.user-name-attribute = preferred_username

Simple REST API

package se.mkk.springboot3oauth2loginkeycloak;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}
package se.mkk.springboot3oauth2loginkeycloak;

import java.security.Principal;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import jakarta.servlet.http.HttpServletRequest;

@RestController
@RequestMapping("/api/users")
public class UserController {

    @GetMapping
    public Map<String, String> getUser(HttpServletRequest request, Principal principal) {
        Map<String, String> rtn = new LinkedHashMap<>();
        rtn.put("request.getRemoteUser()", request.getRemoteUser());
        rtn.put("request.isUserInRole(\"USER\")", Boolean.toString(request.isUserInRole("USER")));
        rtn.put("request.getUserPrincipal().getClass()", request.getUserPrincipal().getClass().getName());
        rtn.put("principal.getClass().getName()", principal.getClass().getName());
        rtn.put("principal.getName()", principal.getName());
        if (principal instanceof OAuth2AuthenticationToken token) {
            List<String> authorities = token.getAuthorities().stream()
                    .map(grantedAuthority -> grantedAuthority.getAuthority()).toList();
            rtn.put("OAuth2AuthenticationToken.getAuthorities()", authorities.toString());
        }
        return rtn;
    }
}

OAuth2 Log in

ppackage se.mkk.springboot3oauth2loginkeycloak;

import java.util.ArrayList;
import java.util.Base64;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Map;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.converter.Converter;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserRequest;
import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserService;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserService;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
import org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser;
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.web.SecurityFilterChain;

import com.nimbusds.jose.util.JSONObjectUtils;

@Configuration
@EnableWebSecurity
@EnableMethodSecurity
public class OAuth2LoginSecurityConfig {

    // https://docs.spring.io/spring-security/reference/servlet/oauth2/login/core.html#oauth2login-provide-securityfilterchain-bean
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http //
                .authorizeHttpRequests(authorize -> authorize //
                        .anyRequest().authenticated()) //
                .oauth2Login(oauth2 -> oauth2 //
                        .userInfoEndpoint(userInfo -> userInfo //
                                .oidcUserService(this.oidcUserService())));
        return http.build();
    }

    // https://docs.spring.io/spring-security/reference/servlet/oauth2/login/advanced.html#oauth2login-advanced-map-authorities-oauth2userservice
    private OAuth2UserService<OidcUserRequest, OidcUser> oidcUserService() {
        final OidcUserService delegate = new OidcUserService();

        return (userRequest) -> {
            // Delegate to the default implementation for loading a user
            OidcUser oidcUser = delegate.loadUser(userRequest);

            OAuth2AccessToken accessToken = userRequest.getAccessToken();
            Collection<GrantedAuthority> mappedAuthorities = new HashSet<>();

            // 1) Fetch the authority information from the protected resource using accessToken
            // 2) Map the authority information to one or more GrantedAuthority's and add it to mappedAuthorities
            try {
                String[] chunks = accessToken.getTokenValue().split("\\.");
                Base64.Decoder decoder = Base64.getUrlDecoder();
                String header = new String(decoder.decode(chunks[0]));
                String payload = new String(decoder.decode(chunks[1]));

                Map<String, Object> claims = JSONObjectUtils.parse(payload);
                mappedAuthorities = new KeycloakAuthoritiesConverter().convert(claims);
            } catch (Exception e) {
                e.printStackTrace();
            }

            // 3) Create a copy of oidcUser but use the mappedAuthorities instead
            oidcUser = new DefaultOidcUser(mappedAuthorities, oidcUser.getIdToken(), oidcUser.getUserInfo(),
                    "preferred_username");

            return oidcUser;
        };
    }

    // Spring OAuth2 uses default Scopes Not Roles for Authorization
    // org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter
    private class KeycloakAuthoritiesConverter implements Converter<Jwt, Collection<GrantedAuthority>> {

        @Override
        public Collection<GrantedAuthority> convert(Jwt jwt) {
            return convert(jwt.getClaims());
        }

        public Collection<GrantedAuthority> convert(Map<String, Object> claims) {
            Collection<GrantedAuthority> grantedAuthorities = new ArrayList<>();
            for (String authority : getAuthorities(claims)) {
                grantedAuthorities.add(new SimpleGrantedAuthority("ROLE_" + authority));
            }
            return grantedAuthorities;
        }

        private Collection<String> getAuthorities(Map<String, Object> claims) {
            Object realm_access = claims.get("realm_access");
            if (realm_access instanceof Map) {
                Map<String, Object> map = castAuthoritiesToMap(realm_access);
                Object roles = map.get("roles");
                if (roles instanceof Collection) {
                    return castAuthoritiesToCollection(roles);
                }
            }
            return Collections.emptyList();
        }

        @SuppressWarnings("unchecked")
        private Map<String, Object> castAuthoritiesToMap(Object authorities) {
            return (Map<String, Object>) authorities;
        }

        @SuppressWarnings("unchecked")
        private Collection<String> castAuthoritiesToCollection(Object authorities) {
            return (Collection<String>) authorities;
        }
    }
}

And now run

$ mvn clean install spring-boot:run

And login and call REST endpoint

request.getRemoteUser()	"john"
request.isUserInRole("USER")	"true"
request.getUserPrincipal().getClass()	"org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken"
principal.getClass().getName()	"org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken"
principal.getName()	"john"
OAuth2AuthenticationToken.getAuthorities()	"[ROLE_USER, ROLE_default-roles-demo, ROLE_offline_access, ROLE_uma_authorization]"

Summary

You also want to configure Spring Session, Logout and CSRF.

Source code https://github.com/magnuskkarlsson/spring-boot-3-oauth2-login-keycloak

January 14, 2023

Fedora 37 Getting Started with Virtualization (KVM)

$ sudo dnf groupinfo virtualization
Last metadata expiration check: 0:03:45 ago on Sat 14 Jan 2023 10:51:52 AM CET.
Group: Virtualization
 Description: These packages provide a graphical virtualization environment.
 Mandatory Packages:
   virt-install
 Default Packages:
   libvirt-daemon-config-network
   libvirt-daemon-kvm
   qemu-kvm
   virt-manager
   virt-viewer
 Optional Packages:
   guestfs-tools
   libguestfs-tools
   python3-libguestfs
   virt-top

$ sudo dnf install @virtualization

$ sudo systemctl enable --now libvirtd

$ lsmod | grep kvm
kvm_intel             389120  0
kvm                  1122304  1 kvm_intel
irqbypass              16384  1 kvm

https://docs.fedoraproject.org/en-US/quick-docs/getting-started-with-virtualization/

Fedora 37 How to Turn off System Beep / Bell Terminal Sound

$ less /proc/modules | grep pcspkr
pcspkr 16384 0 - Live 0x0000000000000000

$ lsmod | grep pcspkr

$ modinfo pcspkr
filename:       /lib/modules/6.0.18-300.fc37.x86_64/kernel/drivers/input/misc/pcspkr.ko.xz
alias:          platform:pcspkr
license:        GPL
description:    PC Speaker beeper driver
...

$ sudo modprobe -r -v pcspkr

https://www.cyberciti.biz/faq/rhel-fedora-turn-off-bell-beep-sound/

https://www.cyberciti.biz/faq/howto-display-list-of-modules-or-device-drivers-in-the-linux-kernel/

Fedora 37 don't Group Application when Alt + Tab

This describes how to enable classic Windows Alt-Tab or not Group Application when switching windows.

In Fedora 37 the rpm package gnome-shell-extension-alternate-tab is gone.

Open Settings -> Keyboard -> Keyboard Shortcuts: View and Customize ShortCuts

Click on Switch Windows and set new shortcut by pressing Alt-Tab.

Reference: https://blogs.gnome.org/fmuellner/2018/10/11/the-future-of-alternatetab-and-why-you-need-not-worry/

January 12, 2023

Updating BIOS and Firmware for DELL and Fedora 37

Introduction

These are excellent guides

To check BIOS version from Fedora

$ sudo dmidecode -s bios-version
1.23.0
$ sudo dmidecode 
# dmidecode 3.4
Getting SMBIOS data from sysfs.
SMBIOS 2.8 present.
...
BIOS Information
	Vendor: Dell Inc.
	Version: 1.23.0
	Release Date: 07/06/2022
...
	Characteristics:
		PCI is supported
		PNP is supported
		BIOS is upgradeable
		BIOS shadowing is allowed
		Boot from CD is supported
		Selectable boot is supported
		EDD is supported
		5.25"/1.2 MB floppy services are supported (int 13h)
		3.5"/720 kB floppy services are supported (int 13h)
		3.5"/2.88 MB floppy services are supported (int 13h)
		Print screen service is supported (int 5h)
		8042 keyboard services are supported (int 9h)
		Serial services are supported (int 14h)
		Printer services are supported (int 17h)
		ACPI is supported
		USB legacy is supported
		Smart battery is supported
		BIOS boot specification is supported
		Function key-initiated network boot is supported
		Targeted content distribution is supported
		UEFI is supported
	BIOS Revision: 1.23
...

Glossary

  • Basic Input Output System (BIOS)
  • Unified Extensive Firmware Interface (UEFI)
  • System Management BIOS (SMBIOS)

"In 2012 the BIOS was superseded with the much more advanced Unified Extensive Firmware Interface (UEFI)."

"Another term that gets often confused with BIOS is the System Management BIOS. The system management BIOS doesn't change unless you physically upgrade the motherboard or purchase a new computer. It is a reflection of the age of the hardware and the number of technologies made available."

To check Firmware version from Fedora

When updating BIOS, you do not update Firmware (FW), such as Solid State Drive (SSD), Thunderbolt Dock Firmware, etc

$ sudo fwupdmgr get-devices
Dell Inc. XPS 15 9550
│
├─Core™ i7-6700HQ CPU @ 2.60GHz:
│     Device ID:          4bde70ba4e39b28f9eab1628f9dd6e6244c03027
│     Current version:    0x000000f0
│     Vendor:             Intel
│     GUIDs:              b9a2dd81-159e-5537-a7db-e7101d164d3f ← cpu
...
│     Device Flags:       • Internal device
│   
├─GM107M [GeForce GTX 960M] (XPS 15 9550):
...

"The Linux Vendor Firmware Service (LVFS) has been put together by Device Vendors or OEMs as a means for users to easily update their devices firmware using Linux. Dell and Lenovo in particular have been widely using the LVFS."

https://fwupd.org/lvfs/devices/

To Update BIOS and Firmware from Fedora with LVFS

List all devices.

$ sudo fwupdmgr refresh --force
$ sudo fwupdmgr get-devices

Check for updates for all devices.

$ sudo fwupdmgr get-updates

Updates all devices.

$ sudo fwupdmgr update

If your system isn't supported by the Linux Vendor Firmware Service (LVFS), then you must use BIOS Flash Update. Use a blank USB Flash Drive (FAT32 formatted) that contains the UEFI BIOS Update, that you download from Dell Support Website - https://www.dell.com/support/home/

Clear TPM https://www.dell.com/support/kbdoc/en-us/000184894/how-to-successfully-update-the-tpm-firmware-on-your-dell-computer

Update Error:        Updating disabled due to TPM ownership