001/*
002 * Copyright (c) 2023. The BifroMQ Authors. All Rights Reserved.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *    http://www.apache.org/licenses/LICENSE-2.0
008 * Unless required by applicable law or agreed to in writing,
009 * software distributed under the License is distributed on an "AS IS" BASIS,
010 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
011 * See the License for the specific language governing permissions and limitations under the License.
012 */
013
014package com.baidu.bifromq.plugin.authprovider;
015
016
017import com.baidu.bifromq.plugin.authprovider.type.CheckResult;
018import com.baidu.bifromq.plugin.authprovider.type.Denied;
019import com.baidu.bifromq.plugin.authprovider.type.Error;
020import com.baidu.bifromq.plugin.authprovider.type.Failed;
021import com.baidu.bifromq.plugin.authprovider.type.Granted;
022import com.baidu.bifromq.plugin.authprovider.type.MQTT3AuthData;
023import com.baidu.bifromq.plugin.authprovider.type.MQTT3AuthResult;
024import com.baidu.bifromq.plugin.authprovider.type.MQTT5AuthData;
025import com.baidu.bifromq.plugin.authprovider.type.MQTT5AuthResult;
026import com.baidu.bifromq.plugin.authprovider.type.MQTT5ExtendedAuthData;
027import com.baidu.bifromq.plugin.authprovider.type.MQTT5ExtendedAuthResult;
028import com.baidu.bifromq.plugin.authprovider.type.MQTTAction;
029import com.baidu.bifromq.plugin.authprovider.type.Success;
030import com.baidu.bifromq.type.ClientInfo;
031import java.util.concurrent.CompletableFuture;
032import org.pf4j.ExtensionPoint;
033
034public interface IAuthProvider extends ExtensionPoint {
035    /**
036     * Implement this method to hook authentication logic of mqtt3 client into BifroMQ.
037     *
038     * @param authData the authentication data
039     */
040    CompletableFuture<MQTT3AuthResult> auth(MQTT3AuthData authData);
041
042    /**
043     * Implement this method to hook authentication logic of mqtt5 client into BifroMQ. The default implementation will
044     * delegate to the auth method of mqtt3.
045     *
046     * @param authData the authentication data
047     * @return the authentication result
048     */
049    default CompletableFuture<MQTT5AuthResult> auth(MQTT5AuthData authData) {
050        MQTT3AuthData.Builder mqtt3AuthDataBuilder = MQTT3AuthData.newBuilder();
051        if (authData.hasCert()) {
052            mqtt3AuthDataBuilder.setCert(authData.getCert());
053        }
054        if (authData.hasUsername()) {
055            mqtt3AuthDataBuilder.setUsername(authData.getUsername());
056        }
057        if (authData.hasPassword()) {
058            mqtt3AuthDataBuilder.setPassword(authData.getPassword());
059        }
060        if (authData.hasClientId()) {
061            mqtt3AuthDataBuilder.setClientId(authData.getClientId());
062        }
063        mqtt3AuthDataBuilder.setRemoteAddr(authData.getRemoteAddr());
064        mqtt3AuthDataBuilder.setRemotePort(authData.getRemotePort());
065        mqtt3AuthDataBuilder.setClientId(authData.getClientId());
066        return auth(mqtt3AuthDataBuilder.build()).thenApply(mqtt3AuthResult -> {
067            MQTT5AuthResult.Builder mqtt5AuthResultBuilder = MQTT5AuthResult.newBuilder();
068            switch (mqtt3AuthResult.getTypeCase()) {
069                case OK -> {
070                    mqtt5AuthResultBuilder.setSuccess(Success.newBuilder()
071                        .setTenantId(mqtt3AuthResult.getOk().getTenantId())
072                        .setUserId(mqtt3AuthResult.getOk().getUserId())
073                        .putAllAttrs(mqtt3AuthResult.getOk().getAttrsMap())
074                        .build());
075                }
076                case REJECT -> {
077                    Failed.Builder failedBuilder = Failed.newBuilder();
078                    switch (mqtt3AuthResult.getReject().getCode()) {
079                        case BadPass -> failedBuilder.setCode(Failed.Code.BadPass);
080                        case NotAuthorized -> failedBuilder.setCode(Failed.Code.NotAuthorized);
081                        case Error -> failedBuilder.setCode(Failed.Code.Error);
082                    }
083                    if (mqtt3AuthResult.getReject().hasReason()) {
084                        failedBuilder.setReason(mqtt3AuthResult.getReject().getReason());
085                    }
086                    mqtt5AuthResultBuilder.setFailed(failedBuilder.build());
087                }
088            }
089            return mqtt5AuthResultBuilder.build();
090        });
091    }
092
093    /**
094     * Implement this method to hook extended authentication logic of mqtt5 client into BifroMQ.
095     *
096     * @param authData the extended authentication data
097     * @return the authentication result
098     */
099    default CompletableFuture<MQTT5ExtendedAuthResult> extendedAuth(MQTT5ExtendedAuthData authData) {
100        return CompletableFuture.completedFuture(MQTT5ExtendedAuthResult.newBuilder()
101            .setFailed(Failed.newBuilder()
102                .setCode(Failed.Code.NotAuthorized)
103                .setReason("Not supported")
104                .build())
105            .build());
106    }
107
108    /**
109     * Implement this method to hook action permission check logic.
110     *
111     * @param client the client to check permission
112     * @param action the action
113     * @return true if the client is allowed to perform the action, false otherwise
114     */
115    @Deprecated(since = "3.0")
116    CompletableFuture<Boolean> check(ClientInfo client, MQTTAction action);
117
118    /**
119     * Implement this method to hook action permission check logic.
120     *
121     * @param client the client to check permission
122     * @param action the action
123     * @return CheckResult
124     */
125    default CompletableFuture<CheckResult> checkPermission(ClientInfo client, MQTTAction action) {
126        return check(client, action)
127            .handle((granted, e) -> {
128                if (e != null) {
129                    return CheckResult.newBuilder()
130                        .setError(Error.newBuilder()
131                            .setReason(e.getMessage())
132                            .build())
133                        .build();
134                } else if (granted) {
135                    return CheckResult.newBuilder()
136                        .setGranted(Granted.getDefaultInstance())
137                        .build();
138                } else {
139                    return CheckResult.newBuilder()
140                        .setDenied(Denied.getDefaultInstance())
141                        .build();
142                }
143            });
144    }
145
146    /**
147     * This method will be called during broker shutdown
148     */
149    default void close() {
150    }
151}