From be0db58eab12f73b0af97c3aef3d2b9a60b7e28f Mon Sep 17 00:00:00 2001 From: Nuvindu Nirmana <63797478+Nuvindu@users.noreply.github.com> Date: Fri, 31 May 2024 21:28:55 +0530 Subject: [PATCH 01/34] Initial commit --- LICENSE | 201 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 201 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. From d20bf1118c07bbd129d7eba3ab745f08dfa3532b Mon Sep 17 00:00:00 2001 From: Nuvindu Date: Fri, 31 May 2024 22:26:24 +0530 Subject: [PATCH 02/34] [Automated] Update the toml files --- ballerina/Ballerina.toml | 25 ++++++++++++++++++ ballerina/Dependencies.toml | 51 +++++++++++++++++++++++++++++++++++++ 2 files changed, 76 insertions(+) create mode 100644 ballerina/Ballerina.toml create mode 100644 ballerina/Dependencies.toml diff --git a/ballerina/Ballerina.toml b/ballerina/Ballerina.toml new file mode 100644 index 0000000..99eab0c --- /dev/null +++ b/ballerina/Ballerina.toml @@ -0,0 +1,25 @@ +[package] +org = "ballerina" +name = "ldap" +version = "0.1.0" +authors = ["Ballerina"] +export=["ldap"] +keywords = ["ldap"] +repository = "https://github.com/ballerina-platform/module-ballerina-ldap" +license = ["Apache-2.0"] +distribution = "2201.8.6" + +[platform.java17] +graalvmCompatible = true + +[[platform.java17.dependency]] +groupId = "io.ballerina.lib" +artifactId = "ldap-native" +version = "0.1.0" +path = "../native/build/libs/ldap-native-0.1.0-SNAPSHOT.jar" + +[[platform.java17.dependency]] +groupId = "com.unboundid" +artifactId = "unboundid-ldapsdk" +version = "7.0.0" +path = "./lib/unboundid-ldapsdk-7.0.0.jar" diff --git a/ballerina/Dependencies.toml b/ballerina/Dependencies.toml new file mode 100644 index 0000000..4075832 --- /dev/null +++ b/ballerina/Dependencies.toml @@ -0,0 +1,51 @@ +# AUTO-GENERATED FILE. DO NOT MODIFY. + +# This file is auto-generated by Ballerina for managing dependency versions. +# It should not be modified by hand. + +[ballerina] +dependencies-toml-version = "2" +distribution-version = "2201.8.6" + +[[package]] +org = "ballerina" +name = "jballerina.java" +version = "0.0.0" +modules = [ + {org = "ballerina", packageName = "jballerina.java", moduleName = "jballerina.java"} +] + +[[package]] +org = "ballerina" +name = "lang.error" +version = "0.0.0" +scope = "testOnly" +dependencies = [ + {org = "ballerina", name = "jballerina.java"} +] + +[[package]] +org = "ballerina" +name = "ldap" +version = "0.1.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "test"} +] +modules = [ + {org = "ballerina", packageName = "ldap", moduleName = "ldap"} +] + +[[package]] +org = "ballerina" +name = "test" +version = "0.0.0" +scope = "testOnly" +dependencies = [ + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.error"} +] +modules = [ + {org = "ballerina", packageName = "test", moduleName = "test"} +] + From 8e84279aa89616c7989e7b143e964f8fe2a08275 Mon Sep 17 00:00:00 2001 From: Nuvindu Date: Fri, 31 May 2024 22:31:58 +0530 Subject: [PATCH 03/34] Initiate a Gradle project --- .gitattributes | 9 + .gitignore | 48 +++++ gradle/wrapper/gradle-wrapper.properties | 7 + gradlew | 249 +++++++++++++++++++++++ gradlew.bat | 92 +++++++++ issue_template.md | 18 ++ pull_request_template.md | 39 ++++ 7 files changed, 462 insertions(+) create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100755 gradlew create mode 100644 gradlew.bat create mode 100644 issue_template.md create mode 100644 pull_request_template.md diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..097f9f9 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,9 @@ +# +# https://help.github.com/articles/dealing-with-line-endings/ +# +# Linux start script should use lf +/gradlew text eol=lf + +# These are Windows script files and should use crlf +*.bat text eol=crlf + diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..544a77a --- /dev/null +++ b/.gitignore @@ -0,0 +1,48 @@ +# Compiled class file +*.class + +# Log file +*.log + +# Lock file +*.lck + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +!gradle/wrapper/gradle-wrapper.jar +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# System files +.DS_Store + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* + +# IDE-related files +.idea +.code +.project +.settings +.vscode + +# Build Files +.gradle +target +build +bin + +# Ballerina +velocity.log* +*Ballerina.lock +examples/**/Dependencies.toml diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..9f4197d --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.2.1-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100755 index 0000000..0adc8e1 --- /dev/null +++ b/gradlew @@ -0,0 +1,249 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..93e3f59 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,92 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/issue_template.md b/issue_template.md new file mode 100644 index 0000000..757e13e --- /dev/null +++ b/issue_template.md @@ -0,0 +1,18 @@ +**Description:** + + +**Suggested Labels:** + + +**Suggested Assignees:** + + +**Affected Product Version:** + +**OS, DB, other environment details and versions:** + +**Steps to reproduce:** + + +**Related Issues:** + \ No newline at end of file diff --git a/pull_request_template.md b/pull_request_template.md new file mode 100644 index 0000000..731e098 --- /dev/null +++ b/pull_request_template.md @@ -0,0 +1,39 @@ +# Description + +Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. List any dependencies that are required for this change. + +Fixes # (issue) + +Related Pull Requests (remove if not relevant) +- Pull request 1 +- Pull request 2 + +One line release note: +- One line describing the feature/improvement/fix made by this PR + +## Type of change + +Please delete options that are not relevant. + +- [ ] Bug fix (non-breaking change which fixes an issue) +- [ ] New feature (non-breaking change which adds functionality) +- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) +- [ ] This change requires a documentation update + +# How Has This Been Tested? + +Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration + +- [ ] Test A +- [ ] Test B + +**Test Configuration**: +* Ballerina Version: +* Operating System: +* Java SDK: + +# Checklist: + +### Security checks + - [ ] Followed secure coding standards in http://wso2.com/technical-reports/wso2-secure-engineering-guidelines? + - [ ] Confirmed that this PR doesn't commit any keys, passwords, tokens, usernames, or other secrets? From 3d8412390ca599fed9fce488e1a867a8a712b30a Mon Sep 17 00:00:00 2001 From: Nuvindu Date: Fri, 31 May 2024 22:33:45 +0530 Subject: [PATCH 04/34] Add necessary Gradle build files --- ballerina/build.gradle | 103 ++++++++++++++++++++++++++ build-config/checkstyle/build.gradle | 49 +++++++++++++ build.gradle | 95 ++++++++++++++++++++++++ gradle.properties | 21 ++++++ native/build.gradle | 104 +++++++++++++++++++++++++++ settings.gradle | 49 +++++++++++++ 6 files changed, 421 insertions(+) create mode 100644 ballerina/build.gradle create mode 100644 build-config/checkstyle/build.gradle create mode 100644 build.gradle create mode 100644 gradle.properties create mode 100644 native/build.gradle create mode 100644 settings.gradle diff --git a/ballerina/build.gradle b/ballerina/build.gradle new file mode 100644 index 0000000..4a546b6 --- /dev/null +++ b/ballerina/build.gradle @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import org.apache.tools.ant.taskdefs.condition.Os + +plugins { + id 'io.ballerina.plugin' +} + +description = 'LDAP - Ballerina' + +def packageName = "ldap" +def packageOrg = "ballerina" +def tomlVersion = stripBallerinaExtensionVersion("${project.version}") +def ballerinaTomlFilePlaceHolder = new File("${project.rootDir}/build-config/resources/Ballerina.toml") +def ballerinaTomlFile = new File("$project.projectDir/Ballerina.toml") + +def stripBallerinaExtensionVersion(String extVersion) { + if (extVersion.matches(project.ext.timestampedVersionRegex)) { + def splitVersion = extVersion.split('-') + if (splitVersion.length > 3) { + def strippedValues = splitVersion[0..-4] + return strippedValues.join('-') + } else { + return extVersion + } + } else { + return extVersion.replace("${project.ext.snapshotVersion}", "") + } +} + +ballerina { + packageOrganization = packageOrg + module = packageName + testCoverageParam = "--code-coverage --coverage-format=xml" + isConnector = true + langVersion = ballerinaLangVersion + platform = "java17" +} + +configurations { + externalJars + jbalTools + ballerinaStdLibs +} + +dependencies { + jbalTools("org.ballerinalang:jballerina-tools:${ballerinaLangVersion}") { + transitive = false + } + externalJars(group: 'com.unboundid', name: 'unboundid-ldapsdk', version: '7.0.0') { + transitive = false + } +} + +task updateTomlFiles { + doLast { + def newBallerinaToml = ballerinaTomlFilePlaceHolder.text.replace("@project.version@", project.version) + newBallerinaToml = newBallerinaToml.replace("@toml.version@", tomlVersion) + ballerinaTomlFile.text = newBallerinaToml + } +} + +task commitTomlFiles { + doLast { + project.exec { + ignoreExitValue true + if (Os.isFamily(Os.FAMILY_WINDOWS)) { + commandLine 'cmd', '/c', "git commit -m \"[Automated] Update the toml files\" Ballerina.toml Dependencies.toml" + } else { + commandLine 'sh', '-c', "git commit -m '[Automated] Update the toml files' Ballerina.toml Dependencies.toml" + } + } + } +} + +clean { + delete 'build' +} + +updateTomlFiles.dependsOn copyStdlibs +build.dependsOn copyToLib + +test.dependsOn ":${packageName}-native:build" +build.dependsOn ":${packageName}-native:build" + +publishToMavenLocal.dependsOn build +publish.dependsOn build diff --git a/build-config/checkstyle/build.gradle b/build-config/checkstyle/build.gradle new file mode 100644 index 0000000..ac499db --- /dev/null +++ b/build-config/checkstyle/build.gradle @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com) + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +plugins { + id "de.undercouch.download" +} + +apply plugin: 'java' + +task downloadCheckstyleRuleFiles(type: Download) { + src([ + 'https://raw.githubusercontent.com/wso2/code-quality-tools/v1.4/checkstyle/jdk-17/checkstyle.xml', + 'https://raw.githubusercontent.com/wso2/code-quality-tools/v1.4/checkstyle/jdk-17/suppressions.xml' + ]) + overwrite false + onlyIfNewer true + dest buildDir +} + +jar { + enabled = false +} + +clean { + enabled = false +} + +artifacts.add('default', file("$project.buildDir/checkstyle.xml")) { + builtBy('downloadCheckstyleRuleFiles') +} + +artifacts.add('default', file("$project.buildDir/suppressions.xml")) { + builtBy('downloadCheckstyleRuleFiles') +} diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..7391ae5 --- /dev/null +++ b/build.gradle @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com) + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import org.apache.tools.ant.taskdefs.condition.Os + +plugins { + id "com.github.johnrengelman.shadow" + id "de.undercouch.download" + id "net.researchgate.release" +} + +description = 'Ballerina - Schema Registry' + +allprojects { + group = project.group + version = project.version + + apply plugin: 'jacoco' + apply plugin: 'maven-publish' + + repositories { + mavenLocal() + mavenCentral() + maven { + url = 'https://maven.wso2.org/nexus/content/groups/wso2-public/' + } + + maven { + url = 'https://packages.confluent.io/maven/' + } + + maven { + url = 'https://repo.maven.apache.org/maven2' + } + + maven { + url = 'https://maven.pkg.github.com/ballerina-platform/ballerina-lang' + credentials { + username System.getenv("packageUser") + password System.getenv("packagePAT") + } + } + } + + ext { + snapshotVersion = '-SNAPSHOT' + timestampedVersionRegex = '.*-\\d{8}-\\d{6}-\\w.*\$' + } +} + +subprojects { + configurations { + ballerinaStdLibs + jbalTools + } + dependencies { + /* JBallerina Tools */ + jbalTools ("org.ballerinalang:jballerina-tools:${ballerinaLangVersion}") { + transitive = false + } + } +} + +task build { + dependsOn(":ldap-native:build") + dependsOn(":ldap-ballerina:build") +} + +def moduleVersion = project.version.replace("-SNAPSHOT", "") + +release { + buildTasks = ['build'] + failOnSnapshotDependencies = true + versionPropertyFile = 'gradle.properties' + tagTemplate = 'v${version}' + git { + requireBranch = "release-${moduleVersion}" + pushToRemote = 'origin' + } +} diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..8c1b28b --- /dev/null +++ b/gradle.properties @@ -0,0 +1,21 @@ +org.gradle.caching=true +group=io.ballerina.lib +version=0.1.0-SNAPSHOT +ballerinaLangVersion=2201.8.6 + +checkstylePluginVersion=10.12.0 +spotbugsPluginVersion=5.0.14 +shadowJarPluginVersion=8.1.1 +downloadPluginVersion=5.4.0 +releasePluginVersion=2.8.0 +ballerinaGradlePluginVersion=2.2.4 +jacocoVersion=0.8.10 + +slf4jVersion=1.7.21 +jacksonVersion=2.17.0 +avroVersion=1.11.3 +kafkaAvroVersion=5.3.0 +kafkaClientVersion=3.7.0 +kafkaSchemaRegistryVersion=5.3.2 +commonConfigVersion=5.3.0 +commonUtilsVersion=5.3.0 diff --git a/native/build.gradle b/native/build.gradle new file mode 100644 index 0000000..66df565 --- /dev/null +++ b/native/build.gradle @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com) + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +plugins { + id 'java' + id 'checkstyle' + id 'com.github.spotbugs' +} + +description = 'Ballerina - Avro Native' + +configurations { + dist { + transitive true + } +} + +dependencies { + checkstyle project(":checkstyle") + checkstyle "com.puppycrawl.tools:checkstyle:${checkstylePluginVersion}" + + implementation group: 'org.ballerinalang', name: 'ballerina-runtime', version: "${ballerinaLangVersion}" + implementation 'com.unboundid:unboundid-ldapsdk:7.0.0' +} + +tasks.withType(JavaCompile) { + options.encoding = 'UTF-8' +} + +test { + testLogging { + showStackTraces = true + showStandardStreams = true + events "failed" + exceptionFormat "full" + } + jacoco { + enabled = true + destinationFile = file("$buildDir/coverage-reports/jacoco.exec") + includeNoLocationClasses = true + } +} + +spotbugsMain { + ignoreFailures = true + effort = "max" + reportLevel = "low" + reportsDir = file("$project.buildDir/reports/spotbugs") + def excludeFile = file("${rootDir}/build-config/spotbugs-exclude.xml") + if (excludeFile.exists()) { + it.excludeFilter = excludeFile + } + reports { + text.enabled = true + } +} + +spotbugsTest { + enabled = false +} + +tasks.withType(Checkstyle) { + exclude '**/module-info.java' +} + +checkstyle { + toolVersion "${checkstylePluginVersion}" + configFile file("${rootDir}/build-config/checkstyle/build/checkstyle.xml") + configProperties = ["suppressionFile": file("${rootDir}/build-config/checkstyle/build/suppressions.xml")] +} + +checkstyleMain.dependsOn ':checkstyle:downloadCheckstyleRuleFiles' + +jar { + duplicatesStrategy = DuplicatesStrategy.EXCLUDE + dependsOn configurations.dist + from { configurations.dist.collect { it.isDirectory() ? it : zipTree(it) } } { + exclude 'META-INF/*.RSA', 'META-INF/*.SF', 'META-INF/*.DSA' + } +} + +compileJava { + doFirst { + options.compilerArgs = [ + '--module-path', classpath.asPath, + ] + classpath = files() + } +} diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..87a72f2 --- /dev/null +++ b/settings.gradle @@ -0,0 +1,49 @@ +/* + * This file was generated by the Gradle 'init' task. + * + * The settings file is used to specify which projects to include in your build. + * For more detailed information on multi-project builds, please refer to https://docs.gradle.org/8.3/userguide/building_swift_projects.html in the Gradle documentation. + */ + +pluginManagement { + plugins { + id "com.github.spotbugs" version "${spotbugsPluginVersion}" + id "com.github.johnrengelman.shadow" version "${shadowJarPluginVersion}" + id "de.undercouch.download" version "${downloadPluginVersion}" + id "net.researchgate.release" version "${releasePluginVersion}" + id "io.ballerina.plugin" version "${ballerinaGradlePluginVersion}" + } + + repositories { + gradlePluginPortal() + maven { + url = 'https://maven.pkg.github.com/ballerina-platform/*' + credentials { + username System.getenv("packageUser") + password System.getenv("packagePAT") + } + } + } +} + +plugins { + id "com.gradle.enterprise" version "3.13.2" +} + +def projectName = 'ldap' +rootProject.name = 'module-ballerina-ldap' + +include ":checkstyle" +include ":${projectName}-native" +include ":${projectName}-ballerina" + +project(':checkstyle').projectDir = file("build-config${File.separator}checkstyle") +project(":${projectName}-native").projectDir = file('native') +project(":${projectName}-ballerina").projectDir = file('ballerina') + +gradleEnterprise { + buildScan { + termsOfServiceUrl = 'https://gradle.com/terms-of-service' + termsOfServiceAgree = 'yes' + } +} From bd2194133d97de4638dac911e625b23c75cdae30 Mon Sep 17 00:00:00 2001 From: Nuvindu Date: Fri, 31 May 2024 22:39:16 +0530 Subject: [PATCH 05/34] Add native APIs for the package --- .../main/java/io/ballerina/lib/ldap/Ldap.java | 85 +++++++++++++++++++ .../io/ballerina/lib/ldap/ModuleUtils.java | 52 ++++++++++++ .../java/io/ballerina/lib/ldap/Utils.java | 38 +++++++++ native/src/main/java/module-info.java | 22 +++++ 4 files changed, 197 insertions(+) create mode 100644 native/src/main/java/io/ballerina/lib/ldap/Ldap.java create mode 100644 native/src/main/java/io/ballerina/lib/ldap/ModuleUtils.java create mode 100644 native/src/main/java/io/ballerina/lib/ldap/Utils.java create mode 100644 native/src/main/java/module-info.java diff --git a/native/src/main/java/io/ballerina/lib/ldap/Ldap.java b/native/src/main/java/io/ballerina/lib/ldap/Ldap.java new file mode 100644 index 0000000..99508c5 --- /dev/null +++ b/native/src/main/java/io/ballerina/lib/ldap/Ldap.java @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com) + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package io.ballerina.lib.ldap; + +import com.unboundid.ldap.sdk.Attribute; +import com.unboundid.ldap.sdk.LDAPConnection; +import com.unboundid.ldap.sdk.LDAPException; +import com.unboundid.ldap.sdk.Modification; +import com.unboundid.ldap.sdk.ModificationType; +import com.unboundid.ldap.sdk.ModifyRequest; +import com.unboundid.ldap.sdk.SearchResultEntry; +import io.ballerina.runtime.api.creators.ValueCreator; +import io.ballerina.runtime.api.utils.StringUtils; +import io.ballerina.runtime.api.utils.ValueUtils; +import io.ballerina.runtime.api.values.BMap; +import io.ballerina.runtime.api.values.BObject; +import io.ballerina.runtime.api.values.BString; +import io.ballerina.runtime.api.values.BTypedesc; + +import java.util.ArrayList; +import java.util.List; + +public class Ldap { + private Ldap() { + } + + public static void generateLdapClient(BObject ldapClient, BMap config) { + String hostName = ((BString) config.get(ModuleUtils.HOST_NAME)).getValue(); + int port = Math.toIntExact(config.getIntValue(ModuleUtils.PORT)); + String domainName = ((BString) config.get(ModuleUtils.DOMAIN_NAME)).getValue(); + String password = ((BString) config.get(ModuleUtils.PASSWORD)).getValue(); + try { + LDAPConnection ldapConnection = new LDAPConnection(hostName, port, domainName, password); + ldapClient.addNativeData(ModuleUtils.NATIVE_CLIENT, ldapConnection); + } catch (LDAPException e) { + throw new RuntimeException(e); + } + } + + public static Object modify(BObject ldapClient, BString distinguishedName, BMap user) { + try { + LDAPConnection ldapConnection = (LDAPConnection) ldapClient.getNativeData(ModuleUtils.NATIVE_CLIENT); + List modificationList = new ArrayList<>(); + for (BString key: user.getKeys()) { + modificationList + .add(new Modification(ModificationType.REPLACE, key.getValue(), user.get(key).getValue())); + } + ModifyRequest modifyRequest = new ModifyRequest(distinguishedName.getValue(), modificationList); + ldapConnection.modify(modifyRequest); + return null; + } catch (LDAPException e) { + return Utils.createError(e.getMessage(), e); + } + } + + public static Object getEntry(BObject ldapClient, BString distinguishedName, BTypedesc typeParam) { + BMap entry = ValueCreator.createMapValue(); + try { + LDAPConnection ldapConnection = (LDAPConnection) ldapClient.getNativeData(ModuleUtils.NATIVE_CLIENT); + SearchResultEntry userEntry = ldapConnection.getEntry(distinguishedName.getValue()); + for (Attribute attribute: userEntry.getAttributes()) { + entry.put(StringUtils.fromString(attribute.getName()), StringUtils.fromString(attribute.getValue())); + } + return ValueUtils.convert(entry, typeParam.getDescribingType()); + } catch (LDAPException e) { + return Utils.createError(e.getMessage(), e); + } + } +} diff --git a/native/src/main/java/io/ballerina/lib/ldap/ModuleUtils.java b/native/src/main/java/io/ballerina/lib/ldap/ModuleUtils.java new file mode 100644 index 0000000..fb87a30 --- /dev/null +++ b/native/src/main/java/io/ballerina/lib/ldap/ModuleUtils.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com) + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package io.ballerina.lib.ldap; + +import io.ballerina.runtime.api.Environment; +import io.ballerina.runtime.api.Module; +import io.ballerina.runtime.api.utils.StringUtils; +import io.ballerina.runtime.api.values.BString; + +/** + * Utility functions of the Schema Registry module. + * + * @since 0.1.0 + */ +public final class ModuleUtils { + + public static final BString HOST_NAME = StringUtils.fromString("hostName"); + public static final BString PORT = StringUtils.fromString("port"); + public static final BString DOMAIN_NAME = StringUtils.fromString("domainName"); + public static final BString PASSWORD = StringUtils.fromString("password"); + + public static final String NATIVE_CLIENT = "client"; + + private ModuleUtils() {} + + private static Module ldapModule = null; + + public static Module getModule() { + return ldapModule; + } + + public static void setModule(Environment env) { + ldapModule = env.getCurrentModule(); + } + +} diff --git a/native/src/main/java/io/ballerina/lib/ldap/Utils.java b/native/src/main/java/io/ballerina/lib/ldap/Utils.java new file mode 100644 index 0000000..57970ab --- /dev/null +++ b/native/src/main/java/io/ballerina/lib/ldap/Utils.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com) + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package io.ballerina.lib.ldap; + +import io.ballerina.runtime.api.creators.ErrorCreator; +import io.ballerina.runtime.api.values.BError; + +import static io.ballerina.lib.ldap.ModuleUtils.getModule; +import static io.ballerina.runtime.api.utils.StringUtils.fromString; + +public final class Utils { + + private Utils() { + } + + public static final String ERROR_TYPE = "Error"; + + public static BError createError(String message, Throwable throwable) { + BError cause = ErrorCreator.createError(throwable); + return ErrorCreator.createError(getModule(), ERROR_TYPE, fromString(message), cause, null); + } +} diff --git a/native/src/main/java/module-info.java b/native/src/main/java/module-info.java new file mode 100644 index 0000000..3c7e150 --- /dev/null +++ b/native/src/main/java/module-info.java @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com) + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +module io.ballerina.lib.microsoft.ad { + requires io.ballerina.runtime; + requires unboundid.ldapsdk; +} From 8a8a03e2a71b4645841cd84b1a434fb8099d8e10 Mon Sep 17 00:00:00 2001 From: Nuvindu Date: Fri, 31 May 2024 22:39:47 +0530 Subject: [PATCH 06/34] Add Ballerina.toml --- build-config/resources/Ballerina.toml | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 build-config/resources/Ballerina.toml diff --git a/build-config/resources/Ballerina.toml b/build-config/resources/Ballerina.toml new file mode 100644 index 0000000..7395042 --- /dev/null +++ b/build-config/resources/Ballerina.toml @@ -0,0 +1,25 @@ +[package] +org = "ballerina" +name = "ldap" +version = "@toml.version@" +authors = ["Ballerina"] +export=["ldap"] +keywords = ["ldap"] +repository = "https://github.com/ballerina-platform/module-ballerina-ldap" +license = ["Apache-2.0"] +distribution = "2201.8.6" + +[platform.java17] +graalvmCompatible = true + +[[platform.java17.dependency]] +groupId = "io.ballerina.lib" +artifactId = "ldap-native" +version = "0.1.0" +path = "../native/build/libs/ldap-native-0.1.0-SNAPSHOT.jar" + +[[platform.java17.dependency]] +groupId = "com.unboundid" +artifactId = "unboundid-ldapsdk" +version = "7.0.0" +path = "./lib/unboundid-ldapsdk-7.0.0.jar" From 1356b459f683e382ba2abbd9d7eb646dc4c30e08 Mon Sep 17 00:00:00 2001 From: Nuvindu Date: Fri, 31 May 2024 22:40:15 +0530 Subject: [PATCH 07/34] Add client APIs for the ldap package --- ballerina/Module.md | 0 ballerina/Package.md | 0 ballerina/client.bal | 43 ++++++++++++++++++++++++++++++++++++ ballerina/init.bal | 25 +++++++++++++++++++++ ballerina/types.bal | 52 ++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 120 insertions(+) create mode 100644 ballerina/Module.md create mode 100644 ballerina/Package.md create mode 100644 ballerina/client.bal create mode 100644 ballerina/init.bal create mode 100644 ballerina/types.bal diff --git a/ballerina/Module.md b/ballerina/Module.md new file mode 100644 index 0000000..e69de29 diff --git a/ballerina/Package.md b/ballerina/Package.md new file mode 100644 index 0000000..e69de29 diff --git a/ballerina/client.bal b/ballerina/client.bal new file mode 100644 index 0000000..2cc0289 --- /dev/null +++ b/ballerina/client.bal @@ -0,0 +1,43 @@ +// Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com) +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +import ballerina/jballerina.java; + +# Consists of APIs to integrate with Avro Schema Registry. +public isolated client class Client { + + # Gets invoked to initialize the `connector`. + # + # + config - The configurations to be used when initializing the `connector` + # + return - An error if connector initialization failed + public isolated function init(*ConnectionConfig config) returns error? { + self.generateLdapClient(config); + } + + private isolated function generateLdapClient(ConnectionConfig config) = @java:Method { + 'class: "io.ballerina.lib.ldap.Ldap" + } external; + + remote isolated function modify(string distinguishedName, record {|anydata...;|} user) + returns error? = @java:Method { + 'class: "io.ballerina.lib.ldap.Ldap" + } external; + + remote isolated function getEntry(string distinguishedName, typedesc targetType = <>) + returns targetType|error = @java:Method { + 'class: "io.ballerina.lib.ldap.Ldap" + } external; +} diff --git a/ballerina/init.bal b/ballerina/init.bal new file mode 100644 index 0000000..48ef117 --- /dev/null +++ b/ballerina/init.bal @@ -0,0 +1,25 @@ +// Copyright (c) 2024 WSO2 LLC. (http://www.wso2.com). +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +import ballerina/jballerina.java; + +function init() { + setModule(); +} + +function setModule() = @java:Method { + 'class: "io.ballerina.lib.ldap.ModuleUtils" +} external; diff --git a/ballerina/types.bal b/ballerina/types.bal new file mode 100644 index 0000000..20f5b21 --- /dev/null +++ b/ballerina/types.bal @@ -0,0 +1,52 @@ +// Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com) +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +# Provides a set of configurations to control the behaviours when communicating with a schema registry. +# +# + hostName - The host name of the Active Directory server +# + port - The port of the Active Directory server +# + domainName - The domain name of the Active Directory +# + password - The password of the Active Directory +public type ConnectionConfig record {| + string hostName; + int port; + string domainName; + string password; +|}; + +public type UserConfig record { + string userPrincipalName?; + string givenName?; + string sn?; + string company?; + string co?; + string streetAddress?; + string mobile?; + string displayName?; + string middleName?; + string employeeId?; + string extensionAttribute11?; + string extensionAttribute10?; + string postalCode?; + string mail?; + string l?; + string telephoneNumber?; + string department?; + string st?; + string title?; + string distinguishedName?; + string manager?; +}; From 33a6d16abd579fccb0f6cd524f4620ab38ecc15a Mon Sep 17 00:00:00 2001 From: Nuvindu Date: Fri, 31 May 2024 22:41:38 +0530 Subject: [PATCH 08/34] Add github workflow files --- .../workflows/build-with-bal-test-graalvm.yml | 19 ++++++++++++++++ .github/workflows/ci.yml | 18 +++++++++++++++ .github/workflows/daily-build.yml | 14 ++++++++++++ .github/workflows/dev-stg-release.yml | 22 +++++++++++++++++++ .github/workflows/pull-request.yml | 16 ++++++++++++++ .github/workflows/release.yml | 16 ++++++++++++++ .github/workflows/trivy-scan.yml | 15 +++++++++++++ 7 files changed, 120 insertions(+) create mode 100644 .github/workflows/build-with-bal-test-graalvm.yml create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/daily-build.yml create mode 100644 .github/workflows/dev-stg-release.yml create mode 100644 .github/workflows/pull-request.yml create mode 100644 .github/workflows/release.yml create mode 100644 .github/workflows/trivy-scan.yml diff --git a/.github/workflows/build-with-bal-test-graalvm.yml b/.github/workflows/build-with-bal-test-graalvm.yml new file mode 100644 index 0000000..312e1f8 --- /dev/null +++ b/.github/workflows/build-with-bal-test-graalvm.yml @@ -0,0 +1,19 @@ +name: GraalVM Check + +on: + schedule: + - cron: "30 12 * * *" + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }} + cancel-in-progress: true + +jobs: + call_stdlib_workflow: + name: Run StdLib Workflow + if: ${{ github.event_name != 'schedule' || (github.event_name == 'schedule' && github.repository_owner == 'ballerina-platform') }} + uses: ballerina-platform/ballerina-library/.github/workflows/build-with-bal-test-graalvm-connector-template.yml@main + secrets: inherit + with: + additional-build-flags: "-x :ldap-examples:build" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..43644b0 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,18 @@ +name: Build + +on: + push: + branches: + - main + - 2201.[0-9]+.x + repository_dispatch: + types: check_connector_for_breaking_changes + +jobs: + call_workflow: + name: Run Connector Build Workflow + if: ${{ github.repository_owner == 'ballerina-platform' }} + uses: ballerina-platform/ballerina-library/.github/workflows/build-connector-template.yml@main + secrets: inherit + with: + repo-name: module-ballerina-ldap diff --git a/.github/workflows/daily-build.yml b/.github/workflows/daily-build.yml new file mode 100644 index 0000000..aa9b28c --- /dev/null +++ b/.github/workflows/daily-build.yml @@ -0,0 +1,14 @@ +name: Daily build + +on: + schedule: + - cron: "30 0 * * *" + +jobs: + call_workflow: + name: Run Daily Build Workflow + if: ${{ github.repository_owner == 'ballerina-platform' }} + uses: ballerina-platform/ballerina-library/.github/workflows/daily-build-connector-template.yml@main + secrets: inherit + with: + repo-name: module-ballerina-ldap diff --git a/.github/workflows/dev-stg-release.yml b/.github/workflows/dev-stg-release.yml new file mode 100644 index 0000000..a42308f --- /dev/null +++ b/.github/workflows/dev-stg-release.yml @@ -0,0 +1,22 @@ +name: Publish to the Ballerina Dev\Stage Central + +on: + workflow_dispatch: + inputs: + environment: + type: choice + description: Select Environment + required: true + options: + - DEV CENTRAL + - STAGE CENTRAL + +jobs: + call_workflow: + name: Run Dev\Stage Central Publish Workflow + if: ${{ github.repository_owner == 'ballerina-platform' }} + uses: ballerina-platform/ballerina-library/.github/workflows/dev-stage-central-publish-connector-template.yml@main + secrets: inherit + with: + environment: ${{ github.event.inputs.environment }} + additional-publish-flags: "-x :ldap-examples:build" diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml new file mode 100644 index 0000000..4d40baf --- /dev/null +++ b/.github/workflows/pull-request.yml @@ -0,0 +1,16 @@ +name: PR Build + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }} + cancel-in-progress: true + +on: pull_request + +jobs: + call_workflow: + name: Run PR Build Workflow + if: ${{ github.repository_owner == 'ballerina-platform' }} + uses: ballerina-platform/ballerina-library/.github/workflows/pr-build-connector-template.yml@main + secrets: inherit + with: + additional-test-flags: ${{ github.event.pull_request.head.repo.full_name != github.repository && '-x test' || ''}} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..1f2acfc --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,16 @@ +name: Publish Release + +on: + workflow_dispatch: + repository_dispatch: + types: [ stdlib-release-pipeline ] + +jobs: + call_workflow: + name: Run Release Workflow + if: ${{ github.repository_owner == 'ballerina-platform' }} + uses: ballerina-platform/ballerina-library/.github/workflows/release-package-connector-template.yml@main + secrets: inherit + with: + package-name: ldap + package-org: ballerina diff --git a/.github/workflows/trivy-scan.yml b/.github/workflows/trivy-scan.yml new file mode 100644 index 0000000..ad139a7 --- /dev/null +++ b/.github/workflows/trivy-scan.yml @@ -0,0 +1,15 @@ +name: Trivy + +on: + workflow_dispatch: + schedule: + - cron: "30 22 * * *" + +jobs: + call_workflow: + name: Run Trivy Scan Workflow + if: ${{ github.repository_owner == 'ballerina-platform' }} + uses: ballerina-platform/ballerina-library/.github/workflows/trivy-scan-template.yml@main + secrets: inherit + with: + additional-build-flags: "-x :ldap-examples:build" From 80823c692d610bcc562225ffce0b628086d994a5 Mon Sep 17 00:00:00 2001 From: Nuvindu Date: Fri, 31 May 2024 22:41:46 +0530 Subject: [PATCH 09/34] Add code owners --- .github/CODEOWNERS | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .github/CODEOWNERS diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..7cf4a91 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,7 @@ +# Lines starting with '#' are comments. +# Each line is a file pattern followed by one or more owners. + +# See: https://help.github.com/articles/about-codeowners/ + +# These owners will be the default owners for everything in the repo. +* @Nuvindu From e7f12b94b6c2a959bbed9e8d44011a8acda51f44 Mon Sep 17 00:00:00 2001 From: Nuvindu Date: Fri, 31 May 2024 22:41:59 +0530 Subject: [PATCH 10/34] Add README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..fa4a804 --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +Ballerina LDAP Connector +=================== From d1f35d0684a2b08e027132ec44ca2fe91cf3956b Mon Sep 17 00:00:00 2001 From: Nuvindu Date: Fri, 31 May 2024 22:42:19 +0530 Subject: [PATCH 11/34] Add Config.toml to .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 544a77a..8b412be 100644 --- a/.gitignore +++ b/.gitignore @@ -46,3 +46,4 @@ bin velocity.log* *Ballerina.lock examples/**/Dependencies.toml +Config.toml From aa1d57711403f16da93e2cd6607b5d176f8ca8d1 Mon Sep 17 00:00:00 2001 From: Nuvindu Date: Fri, 31 May 2024 22:42:36 +0530 Subject: [PATCH 12/34] Add test cases to validate initial LDAP API --- ballerina/tests/test.bal | 48 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 ballerina/tests/test.bal diff --git a/ballerina/tests/test.bal b/ballerina/tests/test.bal new file mode 100644 index 0000000..3dab2c4 --- /dev/null +++ b/ballerina/tests/test.bal @@ -0,0 +1,48 @@ +// Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com) +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +import ballerina/test; + +configurable string hostName = ?; +configurable int port = ?; +configurable string domainName = ?; +configurable string password = ?; +configurable string userDN = ?; + +Client ldapClient = check new ({ + hostName: hostName, + port: port, + domainName: domainName, + password: password +}); + +@test:Config {} +public function testUpdateUser() returns error? { + UserConfig user = { + sn: "User", + givenName: "Updated User", + displayName: "Updated User" + }; + _ = check ldapClient->modify(userDN, user); +} + +@test:Config { + dependsOn: [testUpdateUser] +} +public function testGetUser() returns error? { + UserConfig value = check ldapClient->getEntry(userDN); + test:assertEquals(value?.givenName, "Updated User"); +} From 71743d66d8a4e3b912e0abd34b79b6c97225118b Mon Sep 17 00:00:00 2001 From: Nuvindu Date: Mon, 3 Jun 2024 09:56:56 +0530 Subject: [PATCH 13/34] Add API docs --- ballerina/client.bal | 12 +++++++++++- native/src/main/java/io/ballerina/lib/ldap/Ldap.java | 8 ++++---- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/ballerina/client.bal b/ballerina/client.bal index 2cc0289..0ea69aa 100644 --- a/ballerina/client.bal +++ b/ballerina/client.bal @@ -31,11 +31,21 @@ public isolated client class Client { 'class: "io.ballerina.lib.ldap.Ldap" } external; - remote isolated function modify(string distinguishedName, record {|anydata...;|} user) + # Updates information of an entry. + # + # + distinguishedName - The distinguished name of the entry + # + entry - The information to update + # + return - `error` if the operation fails or `()` if successfully updated + remote isolated function modify(string distinguishedName, record {|anydata...;|} entry) returns error? = @java:Method { 'class: "io.ballerina.lib.ldap.Ldap" } external; + # Gets information of an entry. + # + # + distinguishedName - The distinguished name of the entry + # + targetType - Default parameter use to infer the user specified type + # + return - An entry result with the given type or else an error remote isolated function getEntry(string distinguishedName, typedesc targetType = <>) returns targetType|error = @java:Method { 'class: "io.ballerina.lib.ldap.Ldap" diff --git a/native/src/main/java/io/ballerina/lib/ldap/Ldap.java b/native/src/main/java/io/ballerina/lib/ldap/Ldap.java index 99508c5..44d5b8f 100644 --- a/native/src/main/java/io/ballerina/lib/ldap/Ldap.java +++ b/native/src/main/java/io/ballerina/lib/ldap/Ldap.java @@ -53,13 +53,13 @@ public static void generateLdapClient(BObject ldapClient, BMap } } - public static Object modify(BObject ldapClient, BString distinguishedName, BMap user) { + public static Object modify(BObject ldapClient, BString distinguishedName, BMap entry) { try { LDAPConnection ldapConnection = (LDAPConnection) ldapClient.getNativeData(ModuleUtils.NATIVE_CLIENT); List modificationList = new ArrayList<>(); - for (BString key: user.getKeys()) { + for (BString key: entry.getKeys()) { modificationList - .add(new Modification(ModificationType.REPLACE, key.getValue(), user.get(key).getValue())); + .add(new Modification(ModificationType.REPLACE, key.getValue(), entry.get(key).getValue())); } ModifyRequest modifyRequest = new ModifyRequest(distinguishedName.getValue(), modificationList); ldapConnection.modify(modifyRequest); @@ -70,7 +70,7 @@ public static Object modify(BObject ldapClient, BString distinguishedName, BMap< } public static Object getEntry(BObject ldapClient, BString distinguishedName, BTypedesc typeParam) { - BMap entry = ValueCreator.createMapValue(); + BMap entry = ValueCreator.createMapValue(); try { LDAPConnection ldapConnection = (LDAPConnection) ldapClient.getNativeData(ModuleUtils.NATIVE_CLIENT); SearchResultEntry userEntry = ldapConnection.getEntry(distinguishedName.getValue()); From 2d0bad40aab1212c66bfb5f0351c6e71df39cbcd Mon Sep 17 00:00:00 2001 From: Nuvindu Date: Mon, 3 Jun 2024 10:35:02 +0530 Subject: [PATCH 14/34] Add github workflow files for library modules --- .../workflows/build-timestamped-master.yml | 19 ++++++++ .../workflows/build-with-bal-test-graalvm.yml | 42 +++++++++++----- .github/workflows/central-publish.yml | 21 ++++++++ .github/workflows/ci.yml | 18 ------- .github/workflows/daily-build.yml | 14 ------ .github/workflows/dev-stg-release.yml | 22 --------- .github/workflows/fossa_scan.yml | 16 +++++++ .github/workflows/publish-release.yml | 16 +++++++ .github/workflows/pull-request.yml | 18 ++++--- .github/workflows/release.yml | 16 ------- .github/workflows/stale_check.yml | 19 ++++++++ .github/workflows/trivy-scan.yml | 28 +++++------ .github/workflows/update-spec.yml | 48 +++++++++++++++++++ 13 files changed, 190 insertions(+), 107 deletions(-) create mode 100644 .github/workflows/build-timestamped-master.yml create mode 100644 .github/workflows/central-publish.yml delete mode 100644 .github/workflows/ci.yml delete mode 100644 .github/workflows/daily-build.yml delete mode 100644 .github/workflows/dev-stg-release.yml create mode 100644 .github/workflows/fossa_scan.yml create mode 100644 .github/workflows/publish-release.yml delete mode 100644 .github/workflows/release.yml create mode 100644 .github/workflows/stale_check.yml create mode 100644 .github/workflows/update-spec.yml diff --git a/.github/workflows/build-timestamped-master.yml b/.github/workflows/build-timestamped-master.yml new file mode 100644 index 0000000..176bab1 --- /dev/null +++ b/.github/workflows/build-timestamped-master.yml @@ -0,0 +1,19 @@ +name: Build + +on: + push: + branches: + - main + paths-ignore: + - '*.md' + - 'docs/**' + - 'load-tests/**' + + workflow_dispatch: + +jobs: + call_workflow: + name: Run Build Workflow + if: ${{ github.repository_owner == 'ballerina-platform' }} + uses: ballerina-platform/ballerina-library/.github/workflows/build-timestamp-master-template.yml@main + secrets: inherit diff --git a/.github/workflows/build-with-bal-test-graalvm.yml b/.github/workflows/build-with-bal-test-graalvm.yml index 312e1f8..d8736ac 100644 --- a/.github/workflows/build-with-bal-test-graalvm.yml +++ b/.github/workflows/build-with-bal-test-graalvm.yml @@ -1,19 +1,37 @@ name: GraalVM Check on: - schedule: - - cron: "30 12 * * *" - workflow_dispatch: + workflow_dispatch: + inputs: + lang_tag: + description: Branch/Release Tag of the Ballerina Lang + required: true + default: main + lang_version: + description: Ballerina Lang Version (If given ballerina lang build will be skipped) + required: false + default: '' + native_image_options: + description: Default native-image options + required: false + default: '' + schedule: + - cron: '30 18 * * *' + pull_request: + branches: + - main + types: [ opened, synchronize, reopened, labeled, unlabeled ] concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }} - cancel-in-progress: true + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }} + cancel-in-progress: true jobs: - call_stdlib_workflow: - name: Run StdLib Workflow - if: ${{ github.event_name != 'schedule' || (github.event_name == 'schedule' && github.repository_owner == 'ballerina-platform') }} - uses: ballerina-platform/ballerina-library/.github/workflows/build-with-bal-test-graalvm-connector-template.yml@main - secrets: inherit - with: - additional-build-flags: "-x :ldap-examples:build" + call_stdlib_workflow: + name: Run StdLib Workflow + if: ${{ github.event_name != 'schedule' || (github.event_name == 'schedule' && github.repository_owner == 'ballerina-platform') }} + uses: ballerina-platform/ballerina-library/.github/workflows/build-with-bal-test-graalvm-template.yml@main + with: + lang_tag: ${{ inputs.lang_tag }} + lang_version: ${{ inputs.lang_version }} + native_image_options: '-J-Xmx7G ${{ inputs.native_image_options }}' diff --git a/.github/workflows/central-publish.yml b/.github/workflows/central-publish.yml new file mode 100644 index 0000000..cca0723 --- /dev/null +++ b/.github/workflows/central-publish.yml @@ -0,0 +1,21 @@ +name: Publish to the Ballerina central + +on: + workflow_dispatch: + inputs: + environment: + type: choice + description: Select Environment + required: true + options: + - DEV CENTRAL + - STAGE CENTRAL + +jobs: + call_workflow: + name: Run Central Publish Workflow + if: ${{ github.repository_owner == 'ballerina-platform' }} + uses: ballerina-platform/ballerina-library/.github/workflows/central-publish-template.yml@main + secrets: inherit + with: + environment: ${{ github.event.inputs.environment }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index 43644b0..0000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,18 +0,0 @@ -name: Build - -on: - push: - branches: - - main - - 2201.[0-9]+.x - repository_dispatch: - types: check_connector_for_breaking_changes - -jobs: - call_workflow: - name: Run Connector Build Workflow - if: ${{ github.repository_owner == 'ballerina-platform' }} - uses: ballerina-platform/ballerina-library/.github/workflows/build-connector-template.yml@main - secrets: inherit - with: - repo-name: module-ballerina-ldap diff --git a/.github/workflows/daily-build.yml b/.github/workflows/daily-build.yml deleted file mode 100644 index aa9b28c..0000000 --- a/.github/workflows/daily-build.yml +++ /dev/null @@ -1,14 +0,0 @@ -name: Daily build - -on: - schedule: - - cron: "30 0 * * *" - -jobs: - call_workflow: - name: Run Daily Build Workflow - if: ${{ github.repository_owner == 'ballerina-platform' }} - uses: ballerina-platform/ballerina-library/.github/workflows/daily-build-connector-template.yml@main - secrets: inherit - with: - repo-name: module-ballerina-ldap diff --git a/.github/workflows/dev-stg-release.yml b/.github/workflows/dev-stg-release.yml deleted file mode 100644 index a42308f..0000000 --- a/.github/workflows/dev-stg-release.yml +++ /dev/null @@ -1,22 +0,0 @@ -name: Publish to the Ballerina Dev\Stage Central - -on: - workflow_dispatch: - inputs: - environment: - type: choice - description: Select Environment - required: true - options: - - DEV CENTRAL - - STAGE CENTRAL - -jobs: - call_workflow: - name: Run Dev\Stage Central Publish Workflow - if: ${{ github.repository_owner == 'ballerina-platform' }} - uses: ballerina-platform/ballerina-library/.github/workflows/dev-stage-central-publish-connector-template.yml@main - secrets: inherit - with: - environment: ${{ github.event.inputs.environment }} - additional-publish-flags: "-x :ldap-examples:build" diff --git a/.github/workflows/fossa_scan.yml b/.github/workflows/fossa_scan.yml new file mode 100644 index 0000000..651f73a --- /dev/null +++ b/.github/workflows/fossa_scan.yml @@ -0,0 +1,16 @@ +name: Fossa Scan +on: + workflow_dispatch: + schedule: + - cron: '30 18 * * *' # 00:00 in LK time (GMT+5:30) +jobs: + fossa-scan: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: fossas/fossa-action@main + env: + packageUser: ${{ secrets.BALLERINA_BOT_USERNAME }} + packagePAT: ${{ secrets.BALLERINA_BOT_TOKEN }} + with: + api-key: ${{secrets.FOSSA_APIKEY}} diff --git a/.github/workflows/publish-release.yml b/.github/workflows/publish-release.yml new file mode 100644 index 0000000..d886096 --- /dev/null +++ b/.github/workflows/publish-release.yml @@ -0,0 +1,16 @@ +name: Publish Release + +on: + workflow_dispatch: + repository_dispatch: + types: [ stdlib-release-pipeline ] + +jobs: + call_workflow: + name: Run Release Workflow + if: ${{ github.repository_owner == 'ballerina-platform' }} + uses: ballerina-platform/ballerina-library/.github/workflows/release-package-template.yml@main + secrets: inherit + with: + package-name: ldap + package-org: ballerina diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index 4d40baf..d8c58cb 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -1,16 +1,14 @@ -name: PR Build +name: Pull Request concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }} - cancel-in-progress: true + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }} + cancel-in-progress: true on: pull_request jobs: - call_workflow: - name: Run PR Build Workflow - if: ${{ github.repository_owner == 'ballerina-platform' }} - uses: ballerina-platform/ballerina-library/.github/workflows/pr-build-connector-template.yml@main - secrets: inherit - with: - additional-test-flags: ${{ github.event.pull_request.head.repo.full_name != github.repository && '-x test' || ''}} + call_workflow: + name: Run PR Build Workflow + if: ${{ github.repository_owner == 'ballerina-platform' }} + uses: ballerina-platform/ballerina-library/.github/workflows/pull-request-build-template.yml@main + secrets: inherit diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index 1f2acfc..0000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,16 +0,0 @@ -name: Publish Release - -on: - workflow_dispatch: - repository_dispatch: - types: [ stdlib-release-pipeline ] - -jobs: - call_workflow: - name: Run Release Workflow - if: ${{ github.repository_owner == 'ballerina-platform' }} - uses: ballerina-platform/ballerina-library/.github/workflows/release-package-connector-template.yml@main - secrets: inherit - with: - package-name: ldap - package-org: ballerina diff --git a/.github/workflows/stale_check.yml b/.github/workflows/stale_check.yml new file mode 100644 index 0000000..8763360 --- /dev/null +++ b/.github/workflows/stale_check.yml @@ -0,0 +1,19 @@ +name: 'Close stale pull requests' + +on: + schedule: + - cron: '30 19 * * *' + workflow_dispatch: + +jobs: + stale: + runs-on: ubuntu-latest + steps: + - uses: actions/stale@v3 + with: + stale-pr-message: 'This PR has been open for more than 15 days with no activity. This will be closed in 3 days unless the `stale` label is removed or commented.' + close-pr-message: 'Closed PR due to inactivity for more than 18 days.' + days-before-pr-stale: 15 + days-before-pr-close: 3 + days-before-issue-stale: -1 + days-before-issue-close: -1 diff --git a/.github/workflows/trivy-scan.yml b/.github/workflows/trivy-scan.yml index ad139a7..12c95cc 100644 --- a/.github/workflows/trivy-scan.yml +++ b/.github/workflows/trivy-scan.yml @@ -1,15 +1,13 @@ -name: Trivy - -on: - workflow_dispatch: - schedule: - - cron: "30 22 * * *" - -jobs: - call_workflow: - name: Run Trivy Scan Workflow - if: ${{ github.repository_owner == 'ballerina-platform' }} - uses: ballerina-platform/ballerina-library/.github/workflows/trivy-scan-template.yml@main - secrets: inherit - with: - additional-build-flags: "-x :ldap-examples:build" +name: Trivy + +on: + workflow_dispatch: + schedule: + - cron: "30 20 * * *" + +jobs: + call_workflow: + name: Run Trivy Scan Workflow + if: ${{ github.repository_owner == 'ballerina-platform' }} + uses: ballerina-platform/ballerina-library/.github/workflows/trivy-scan-template.yml@main + secrets: inherit diff --git a/.github/workflows/update-spec.yml b/.github/workflows/update-spec.yml new file mode 100644 index 0000000..f9358ec --- /dev/null +++ b/.github/workflows/update-spec.yml @@ -0,0 +1,48 @@ +name: Update Specifications + +env: + SPEC_FOLDER_PATH: 'docs/spec' + +on: + workflow_dispatch: + push: + branches: + - main + paths: + - 'docs/spec/**' + +jobs: + update_specs: + name: Update Specifications + if: github.repository_owner == 'ballerina-platform' + runs-on: ubuntu-latest + + steps: + - name: Checkout Repository + uses: actions/checkout@v3 + + - name: Get current date + id: date + run: echo "::set-output name=date::$(date +'%Y-%m-%d')" + + - name: Get Repo Name + id: repo_name + run: | + MODULE=${{ github.event.repository.name }} + echo "::set-output name=short_name::${MODULE##*-}" + + - name: Trigger Workflow + run: | + curl --request POST \ + 'https://api.github.com/repos/ballerina-platform/ballerina-dev-website/dispatches' \ + -H 'Accept: application/vnd.github.v3+json' \ + -H 'Authorization: Bearer ${{ secrets.BALLERINA_BOT_TOKEN }}' \ + --data "{ + \"event_type\": \"update-stdlib-specs\", + \"client_payload\": { + \"module_name\": \"${{ github.event.repository.name }}\", + \"short_name\": \"${{ steps.repo_name.outputs.short_name }}\", + \"file_dir\": \"${{ github.event.repository.name }}/${{ env.SPEC_FOLDER_PATH }}\", + \"release_date\": \"${{ steps.date.outputs.date }}\" + } + }" From 1d16488803a7cfdd09407b1c3bc8b789a9ccb958 Mon Sep 17 00:00:00 2001 From: Nuvindu Date: Mon, 3 Jun 2024 10:35:39 +0530 Subject: [PATCH 15/34] Fix typos having incorrect module names --- ballerina/client.bal | 2 +- gradle.properties | 2 -- native/build.gradle | 2 +- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/ballerina/client.bal b/ballerina/client.bal index 0ea69aa..f1db8a9 100644 --- a/ballerina/client.bal +++ b/ballerina/client.bal @@ -16,7 +16,7 @@ import ballerina/jballerina.java; -# Consists of APIs to integrate with Avro Schema Registry. +# Consists of APIs to integrate with LDAP. public isolated client class Client { # Gets invoked to initialize the `connector`. diff --git a/gradle.properties b/gradle.properties index 8c1b28b..d13925c 100644 --- a/gradle.properties +++ b/gradle.properties @@ -13,8 +13,6 @@ jacocoVersion=0.8.10 slf4jVersion=1.7.21 jacksonVersion=2.17.0 -avroVersion=1.11.3 -kafkaAvroVersion=5.3.0 kafkaClientVersion=3.7.0 kafkaSchemaRegistryVersion=5.3.2 commonConfigVersion=5.3.0 diff --git a/native/build.gradle b/native/build.gradle index 66df565..565ca24 100644 --- a/native/build.gradle +++ b/native/build.gradle @@ -22,7 +22,7 @@ plugins { id 'com.github.spotbugs' } -description = 'Ballerina - Avro Native' +description = 'Ballerina - LDAP Native' configurations { dist { From 514605fc9c3bdc13079a0bdca0d71825ce25edb8 Mon Sep 17 00:00:00 2001 From: Nuvindu Date: Mon, 3 Jun 2024 10:53:03 +0530 Subject: [PATCH 16/34] Add missing gradle-wrapper.jar --- gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 63721 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 gradle/wrapper/gradle-wrapper.jar diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..7f93135c49b765f8051ef9d0a6055ff8e46073d8 GIT binary patch literal 63721 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鯊%1Et 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⁢ zv5o9#E7!G0XmdzfsS{efPNi`1b44~SZ4Z8fuX!I}#8g+(wxzQwUT#Xb2(tbY1+EUhGKoT@KEU9Ktl>_0 z%bjDJg;#*gtJZv!-Zs`?^}v5eKmnbjqlvnSzE@_SP|LG_PJ6CYU+6zY6>92%E+ z=j@TZf-iW4(%U{lnYxQA;7Q!b;^brF8n0D>)`q5>|WDDXLrqYU_tKN2>=#@~OE7grMnNh?UOz-O~6 z6%rHy{#h9K0AT+lDC7q4{hw^|q6*Ry;;L%Q@)Ga}$60_q%D)rv(CtS$CQbpq9|y1e zRSrN4;$Jyl{m5bZw`$8TGvb}(LpY{-cQ)fcyJv7l3S52TLXVDsphtv&aPuDk1OzCA z4A^QtC(!11`IsNx_HnSy?>EKpHJWT^wmS~hc^p^zIIh@9f6U@I2 zC=Mve{j2^)mS#U$e{@Q?SO6%LDsXz@SY+=cK_QMmXBIU)j!$ajc-zLx3V60EXJ!qC zi<%2x8Q24YN+&8U@CIlN zrZkcT9yh%LrlGS9`G)KdP(@9Eo-AQz@8GEFWcb7U=a0H^ZVbLmz{+&M7W(nXJ4sN8 zJLR7eeK(K8`2-}j(T7JsO`L!+CvbueT%izanm-^A1Dn{`1Nw`9P?cq;7no+XfC`K(GO9?O^5zNIt4M+M8LM0=7Gz8UA@Z0N+lg+cX)NfazRu z5D)~HA^(u%w^cz+@2@_#S|u>GpB+j4KzQ^&Wcl9f z&hG#bCA(Yk0D&t&aJE^xME^&E-&xGHhXn%}psEIj641H+Nl-}boj;)Zt*t(4wZ5DN z@GXF$bL=&pBq-#vkTkh>7hl%K5|3 z{`Vn9b$iR-SoGENp}bn4;fR3>9sA%X2@1L3aE9yTra;Wb#_`xWwLSLdfu+PAu+o3| zGVnpzPr=ch{uuoHjtw7+_!L_2;knQ!DuDl0R`|%jr+}jFzXtrHIKc323?JO{l&;VF z*L1+}JU7%QJOg|5|Tc|D8fN zJORAg=_vsy{ak|o);@)Yh8Lkcg@$FG3k@ep36BRa^>~UmnRPziS>Z=`Jb2x*Q#`%A zU*i3&Vg?TluO@X0O;r2Jl6LKLUOVhSqg1*qOt^|8*c7 zo(298@+r$k_wQNGHv{|$tW(T8L+4_`FQ{kEW5Jgg{yf7ey4ss_(SNKfz(N9lx&a;< je(UuV8hP?p&}TPdm1I$XmG#(RzlD&B2izSj9sl%y5~4qc literal 0 HcmV?d00001 From 8f44c583c59f3e234453da8b7ba40504aedec1d9 Mon Sep 17 00:00:00 2001 From: Nuvindu Date: Mon, 3 Jun 2024 11:12:21 +0530 Subject: [PATCH 17/34] Add documentation to the package --- README.md | 38 ++++++++++++++++++++++++++++++++++++++ ballerina/Module.md | 39 +++++++++++++++++++++++++++++++++++++++ ballerina/Package.md | 39 +++++++++++++++++++++++++++++++++++++++ ballerina/client.bal | 6 +++--- 4 files changed, 119 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index fa4a804..afb885d 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,40 @@ Ballerina LDAP Connector =================== + +LDAP (Lightweight Directory Access Protocol) is an vendor-neutral software protocol for accessing and maintaining distributed directory information services. It allows users to locate organizations, individuals, and other resources such as files and devices in a network. LDAP is used in various applications for directory-based authentication and authorization. + +The Ballerina LDAP module provides the capability to efficiently connect, authenticate, and interact with LDAP servers. It allows users to perform operations such as searching for entries, modifying entries in an LDAP directory, providing better support for directory-based operations. + +### APIs associated with LDAP + +- **modify**: Updates information of an entry in a directory server. +- **getEntry**: Gets information of an entry in a directory server. + +#### `modify` API + +Updates information of an entry. + +```ballerina +import ballerina/ldap; + +public function main() returns error? { + ldap:UserConfig user = { + sn: "User", + givenName: "Updated User", + displayName: "Updated User" + }; + _ = check ldapClient->modify(userDN, user); +} +``` + +#### `getEntry` API + +Gets information of an entry + +```ballerina +import ballerina/ldap; + +public function main() returns error? { + ldap:UserConfig value = check ldapClient->getEntry(userDN); +} +``` diff --git a/ballerina/Module.md b/ballerina/Module.md index e69de29..32829dd 100644 --- a/ballerina/Module.md +++ b/ballerina/Module.md @@ -0,0 +1,39 @@ +## Overview + +LDAP (Lightweight Directory Access Protocol) is an vendor-neutral software protocol for accessing and maintaining distributed directory information services. It allows users to locate organizations, individuals, and other resources such as files and devices in a network. LDAP is used in various applications for directory-based authentication and authorization. + +The Ballerina LDAP module provides the capability to efficiently connect, authenticate, and interact with LDAP servers. It allows users to perform operations such as searching for entries, modifying entries in an LDAP directory, providing better support for directory-based operations. + +### APIs associated with LDAP + +- **modify**: Updates information of an entry in a directory server. +- **getEntry**: Gets information of an entry in a directory server. + +#### `modify` API + +Updates information of an entry. + +```ballerina +import ballerina/ldap; + +public function main() returns error? { + ldap:UserConfig user = { + sn: "User", + givenName: "Updated User", + displayName: "Updated User" + }; + _ = check ldapClient->modify(userDN, user); +} +``` + +#### `getEntry` API + +Gets information of an entry + +```ballerina +import ballerina/ldap; + +public function main() returns error? { + ldap:UserConfig value = check ldapClient->getEntry(userDN); +} +``` diff --git a/ballerina/Package.md b/ballerina/Package.md index e69de29..32829dd 100644 --- a/ballerina/Package.md +++ b/ballerina/Package.md @@ -0,0 +1,39 @@ +## Overview + +LDAP (Lightweight Directory Access Protocol) is an vendor-neutral software protocol for accessing and maintaining distributed directory information services. It allows users to locate organizations, individuals, and other resources such as files and devices in a network. LDAP is used in various applications for directory-based authentication and authorization. + +The Ballerina LDAP module provides the capability to efficiently connect, authenticate, and interact with LDAP servers. It allows users to perform operations such as searching for entries, modifying entries in an LDAP directory, providing better support for directory-based operations. + +### APIs associated with LDAP + +- **modify**: Updates information of an entry in a directory server. +- **getEntry**: Gets information of an entry in a directory server. + +#### `modify` API + +Updates information of an entry. + +```ballerina +import ballerina/ldap; + +public function main() returns error? { + ldap:UserConfig user = { + sn: "User", + givenName: "Updated User", + displayName: "Updated User" + }; + _ = check ldapClient->modify(userDN, user); +} +``` + +#### `getEntry` API + +Gets information of an entry + +```ballerina +import ballerina/ldap; + +public function main() returns error? { + ldap:UserConfig value = check ldapClient->getEntry(userDN); +} +``` diff --git a/ballerina/client.bal b/ballerina/client.bal index f1db8a9..a0cd119 100644 --- a/ballerina/client.bal +++ b/ballerina/client.bal @@ -19,10 +19,10 @@ import ballerina/jballerina.java; # Consists of APIs to integrate with LDAP. public isolated client class Client { - # Gets invoked to initialize the `connector`. + # Gets invoked to initialize the LDAP client. # - # + config - The configurations to be used when initializing the `connector` - # + return - An error if connector initialization failed + # + config - The configurations to be used when initializing the client + # + return - An error if client initialization failed public isolated function init(*ConnectionConfig config) returns error? { self.generateLdapClient(config); } From 9ed07e5ae804fe6a0d431e4623e4af37e2e931dc Mon Sep 17 00:00:00 2001 From: Nuvindu Date: Mon, 3 Jun 2024 16:34:08 +0530 Subject: [PATCH 18/34] Apply suggestions from the review --- ballerina/client.bal | 6 +-- ballerina/tests/test.bal | 3 +- ballerina/types.bal | 48 +++++++++++++++++++ .../main/java/io/ballerina/lib/ldap/Ldap.java | 22 +++++++-- .../io/ballerina/lib/ldap/ModuleUtils.java | 4 ++ native/src/main/java/module-info.java | 2 +- 6 files changed, 77 insertions(+), 8 deletions(-) diff --git a/ballerina/client.bal b/ballerina/client.bal index a0cd119..31831e8 100644 --- a/ballerina/client.bal +++ b/ballerina/client.bal @@ -24,10 +24,10 @@ public isolated client class Client { # + config - The configurations to be used when initializing the client # + return - An error if client initialization failed public isolated function init(*ConnectionConfig config) returns error? { - self.generateLdapClient(config); + self.initLdapConnection(config); } - private isolated function generateLdapClient(ConnectionConfig config) = @java:Method { + private isolated function initLdapConnection(ConnectionConfig config) = @java:Method { 'class: "io.ballerina.lib.ldap.Ldap" } external; @@ -37,7 +37,7 @@ public isolated client class Client { # + entry - The information to update # + return - `error` if the operation fails or `()` if successfully updated remote isolated function modify(string distinguishedName, record {|anydata...;|} entry) - returns error? = @java:Method { + returns LDAPResponse|error = @java:Method { 'class: "io.ballerina.lib.ldap.Ldap" } external; diff --git a/ballerina/tests/test.bal b/ballerina/tests/test.bal index 3dab2c4..449e299 100644 --- a/ballerina/tests/test.bal +++ b/ballerina/tests/test.bal @@ -36,7 +36,8 @@ public function testUpdateUser() returns error? { givenName: "Updated User", displayName: "Updated User" }; - _ = check ldapClient->modify(userDN, user); + LDAPResponse val = check ldapClient->modify(userDN, user); + test:assertEquals(val.resultCode, SUCCESS); } @test:Config { diff --git a/ballerina/types.bal b/ballerina/types.bal index 20f5b21..099fb8f 100644 --- a/ballerina/types.bal +++ b/ballerina/types.bal @@ -50,3 +50,51 @@ public type UserConfig record { string distinguishedName?; string manager?; }; + +public type LDAPResponse record { + string matchedDN; + ResultCode resultCode; + string operationType; +}; + +public enum ResultCode { + SUCCESS, + OPERATIONS_ERROR, + PROTOCOL_ERROR, + TIME_LIMIT_EXCEEDED, + SIZE_LIMIT_EXCEEDED, + COMPARE_FALSE, + COMPARE_TRUE, + AUTH_METHOD_NOT_SUPPORTED, + STRONGER_AUTH_REQUIRED, + REFERRAL, + ADMIN_LIMIT_EXCEEDED, + UNAVAILABLE_CRITICAL_EXTENSION, + CONFIDENTIALITY_REQUIRED, + SASL_BIND_IN_PROGRESS, + NO_SUCH_ATTRIBUTE, + UNDEFINED_ATTRIBUTE_TYPE, + INAPPROPRIATE_MATCHING, + CONSTRAINT_VIOLATION, + ATTRIBUTE_OR_VALUE_EXISTS, + INVALID_ATTRIBUTE_SYNTAX, + NO_SUCH_OBJECT, + ALIAS_PROBLEM, + INVALID_DN_SYNTAX, + ALIAS_DEREFERENCING_PROBLEM, + INAPPROPRIATE_AUTHENTICATION, + INVALID_CREDENTIALS, + INSUFFICIENT_ACCESS_RIGHTS, + BUSY, + UNAVAILABLE, + UNWILLING_TO_PERFORM, + LOOP_DETECT, + NAMING_VIOLATION, + OBJECT_CLASS_VIOLATION, + NOT_ALLOWED_ON_NON_LEAF, + NOT_ALLOWED_ON_RDN, + ENTRY_ALREADY_EXISTS, + OBJECT_CLASS_MODS_PROHIBITED, + AFFECTS_MULTIPLE_DSAS, + OTHER +}; diff --git a/native/src/main/java/io/ballerina/lib/ldap/Ldap.java b/native/src/main/java/io/ballerina/lib/ldap/Ldap.java index 44d5b8f..53858a7 100644 --- a/native/src/main/java/io/ballerina/lib/ldap/Ldap.java +++ b/native/src/main/java/io/ballerina/lib/ldap/Ldap.java @@ -21,6 +21,7 @@ import com.unboundid.ldap.sdk.Attribute; import com.unboundid.ldap.sdk.LDAPConnection; import com.unboundid.ldap.sdk.LDAPException; +import com.unboundid.ldap.sdk.LDAPResult; import com.unboundid.ldap.sdk.Modification; import com.unboundid.ldap.sdk.ModificationType; import com.unboundid.ldap.sdk.ModifyRequest; @@ -36,11 +37,16 @@ import java.util.ArrayList; import java.util.List; +import static io.ballerina.lib.ldap.ModuleUtils.LDAP_RESPONSE; +import static io.ballerina.lib.ldap.ModuleUtils.MATCHED_DN; +import static io.ballerina.lib.ldap.ModuleUtils.OPERATION_TYPE; +import static io.ballerina.lib.ldap.ModuleUtils.RESULT_CODE; + public class Ldap { private Ldap() { } - public static void generateLdapClient(BObject ldapClient, BMap config) { + public static void initLdapConnection(BObject ldapClient, BMap config) { String hostName = ((BString) config.get(ModuleUtils.HOST_NAME)).getValue(); int port = Math.toIntExact(config.getIntValue(ModuleUtils.PORT)); String domainName = ((BString) config.get(ModuleUtils.DOMAIN_NAME)).getValue(); @@ -62,8 +68,8 @@ public static Object modify(BObject ldapClient, BString distinguishedName, BMap< .add(new Modification(ModificationType.REPLACE, key.getValue(), entry.get(key).getValue())); } ModifyRequest modifyRequest = new ModifyRequest(distinguishedName.getValue(), modificationList); - ldapConnection.modify(modifyRequest); - return null; + LDAPResult ldapResult = ldapConnection.modify(modifyRequest); + return generateLdapResponse(ldapResult); } catch (LDAPException e) { return Utils.createError(e.getMessage(), e); } @@ -82,4 +88,14 @@ public static Object getEntry(BObject ldapClient, BString distinguishedName, BTy return Utils.createError(e.getMessage(), e); } } + + private static BMap generateLdapResponse(LDAPResult ldapResult) { + BMap response = ValueCreator.createRecordValue(ModuleUtils.getModule(), LDAP_RESPONSE); + response.put(StringUtils.fromString(MATCHED_DN), StringUtils.fromString(ldapResult.getMatchedDN())); + response.put(StringUtils.fromString(RESULT_CODE), + StringUtils.fromString(ldapResult.getResultCode().getName().toUpperCase())); + response.put(StringUtils.fromString(OPERATION_TYPE), + StringUtils.fromString(ldapResult.getOperationType().name())); + return response; + } } diff --git a/native/src/main/java/io/ballerina/lib/ldap/ModuleUtils.java b/native/src/main/java/io/ballerina/lib/ldap/ModuleUtils.java index fb87a30..21b52f2 100644 --- a/native/src/main/java/io/ballerina/lib/ldap/ModuleUtils.java +++ b/native/src/main/java/io/ballerina/lib/ldap/ModuleUtils.java @@ -36,6 +36,10 @@ public final class ModuleUtils { public static final BString PASSWORD = StringUtils.fromString("password"); public static final String NATIVE_CLIENT = "client"; + public static final String LDAP_RESPONSE = "LDAPResponse"; + public static final String RESULT_CODE = "resultCode"; + public static final String MATCHED_DN = "matchedDN"; + public static final String OPERATION_TYPE = "operationType"; private ModuleUtils() {} diff --git a/native/src/main/java/module-info.java b/native/src/main/java/module-info.java index 3c7e150..c59cbf2 100644 --- a/native/src/main/java/module-info.java +++ b/native/src/main/java/module-info.java @@ -16,7 +16,7 @@ * under the License. */ -module io.ballerina.lib.microsoft.ad { + module io.ballerina.lib.ldap { requires io.ballerina.runtime; requires unboundid.ldapsdk; } From 58b16420d576202e34a2d7beffc8e045bbb772c8 Mon Sep 17 00:00:00 2001 From: Nuvindu Date: Mon, 3 Jun 2024 17:17:42 +0530 Subject: [PATCH 19/34] [Automated] Update the toml files --- ballerina/Dependencies.toml | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/ballerina/Dependencies.toml b/ballerina/Dependencies.toml index 4075832..d6f8c7d 100644 --- a/ballerina/Dependencies.toml +++ b/ballerina/Dependencies.toml @@ -7,6 +7,19 @@ dependencies-toml-version = "2" distribution-version = "2201.8.6" +[[package]] +org = "ballerina" +name = "io" +version = "1.6.0" +scope = "testOnly" +dependencies = [ + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.value"} +] +modules = [ + {org = "ballerina", packageName = "io", moduleName = "io"} +] + [[package]] org = "ballerina" name = "jballerina.java" @@ -24,11 +37,21 @@ dependencies = [ {org = "ballerina", name = "jballerina.java"} ] +[[package]] +org = "ballerina" +name = "lang.value" +version = "0.0.0" +scope = "testOnly" +dependencies = [ + {org = "ballerina", name = "jballerina.java"} +] + [[package]] org = "ballerina" name = "ldap" version = "0.1.0" dependencies = [ + {org = "ballerina", name = "io"}, {org = "ballerina", name = "jballerina.java"}, {org = "ballerina", name = "test"} ] From 63c16983a872cfc393b32b7c08369b2bb372989e Mon Sep 17 00:00:00 2001 From: Nuvindu Date: Mon, 3 Jun 2024 17:44:17 +0530 Subject: [PATCH 20/34] [Automated] Update the toml files --- ballerina/Dependencies.toml | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/ballerina/Dependencies.toml b/ballerina/Dependencies.toml index d6f8c7d..4075832 100644 --- a/ballerina/Dependencies.toml +++ b/ballerina/Dependencies.toml @@ -7,19 +7,6 @@ dependencies-toml-version = "2" distribution-version = "2201.8.6" -[[package]] -org = "ballerina" -name = "io" -version = "1.6.0" -scope = "testOnly" -dependencies = [ - {org = "ballerina", name = "jballerina.java"}, - {org = "ballerina", name = "lang.value"} -] -modules = [ - {org = "ballerina", packageName = "io", moduleName = "io"} -] - [[package]] org = "ballerina" name = "jballerina.java" @@ -37,21 +24,11 @@ dependencies = [ {org = "ballerina", name = "jballerina.java"} ] -[[package]] -org = "ballerina" -name = "lang.value" -version = "0.0.0" -scope = "testOnly" -dependencies = [ - {org = "ballerina", name = "jballerina.java"} -] - [[package]] org = "ballerina" name = "ldap" version = "0.1.0" dependencies = [ - {org = "ballerina", name = "io"}, {org = "ballerina", name = "jballerina.java"}, {org = "ballerina", name = "test"} ] From 450af35784e5b56b3ef82bdb2784216756c566d1 Mon Sep 17 00:00:00 2001 From: Nuvindu Date: Mon, 3 Jun 2024 18:23:50 +0530 Subject: [PATCH 21/34] Add doc comments to native classes --- native/src/main/java/io/ballerina/lib/ldap/Ldap.java | 7 +++++-- .../src/main/java/io/ballerina/lib/ldap/ModuleUtils.java | 4 ++-- native/src/main/java/io/ballerina/lib/ldap/Utils.java | 3 +++ 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/native/src/main/java/io/ballerina/lib/ldap/Ldap.java b/native/src/main/java/io/ballerina/lib/ldap/Ldap.java index 53858a7..8f3c515 100644 --- a/native/src/main/java/io/ballerina/lib/ldap/Ldap.java +++ b/native/src/main/java/io/ballerina/lib/ldap/Ldap.java @@ -40,8 +40,11 @@ import static io.ballerina.lib.ldap.ModuleUtils.LDAP_RESPONSE; import static io.ballerina.lib.ldap.ModuleUtils.MATCHED_DN; import static io.ballerina.lib.ldap.ModuleUtils.OPERATION_TYPE; -import static io.ballerina.lib.ldap.ModuleUtils.RESULT_CODE; +import static io.ballerina.lib.ldap.ModuleUtils.RESULT_STATUS; +/** + * This class handles APIs of the LDAP client. + */ public class Ldap { private Ldap() { } @@ -92,7 +95,7 @@ public static Object getEntry(BObject ldapClient, BString distinguishedName, BTy private static BMap generateLdapResponse(LDAPResult ldapResult) { BMap response = ValueCreator.createRecordValue(ModuleUtils.getModule(), LDAP_RESPONSE); response.put(StringUtils.fromString(MATCHED_DN), StringUtils.fromString(ldapResult.getMatchedDN())); - response.put(StringUtils.fromString(RESULT_CODE), + response.put(StringUtils.fromString(RESULT_STATUS), StringUtils.fromString(ldapResult.getResultCode().getName().toUpperCase())); response.put(StringUtils.fromString(OPERATION_TYPE), StringUtils.fromString(ldapResult.getOperationType().name())); diff --git a/native/src/main/java/io/ballerina/lib/ldap/ModuleUtils.java b/native/src/main/java/io/ballerina/lib/ldap/ModuleUtils.java index 21b52f2..1855d14 100644 --- a/native/src/main/java/io/ballerina/lib/ldap/ModuleUtils.java +++ b/native/src/main/java/io/ballerina/lib/ldap/ModuleUtils.java @@ -24,7 +24,7 @@ import io.ballerina.runtime.api.values.BString; /** - * Utility functions of the Schema Registry module. + * Utility functions of the LDAP module. * * @since 0.1.0 */ @@ -37,7 +37,7 @@ public final class ModuleUtils { public static final String NATIVE_CLIENT = "client"; public static final String LDAP_RESPONSE = "LDAPResponse"; - public static final String RESULT_CODE = "resultCode"; + public static final String RESULT_STATUS = "resultStatus"; public static final String MATCHED_DN = "matchedDN"; public static final String OPERATION_TYPE = "operationType"; diff --git a/native/src/main/java/io/ballerina/lib/ldap/Utils.java b/native/src/main/java/io/ballerina/lib/ldap/Utils.java index 57970ab..8383447 100644 --- a/native/src/main/java/io/ballerina/lib/ldap/Utils.java +++ b/native/src/main/java/io/ballerina/lib/ldap/Utils.java @@ -24,6 +24,9 @@ import static io.ballerina.lib.ldap.ModuleUtils.getModule; import static io.ballerina.runtime.api.utils.StringUtils.fromString; +/** + * This class contains utility methods for the Ballerina LDAP module. + */ public final class Utils { private Utils() { From d40143c1474f08221a3c5eb54cfdf4f13fb5d63d Mon Sep 17 00:00:00 2001 From: Nuvindu Date: Mon, 3 Jun 2024 18:24:03 +0530 Subject: [PATCH 22/34] Add module specific error type --- ballerina/error.bal | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 ballerina/error.bal diff --git a/ballerina/error.bal b/ballerina/error.bal new file mode 100644 index 0000000..d672462 --- /dev/null +++ b/ballerina/error.bal @@ -0,0 +1,18 @@ +// Copyright (c) 2024 WSO2 LLC. (http://www.wso2.com). +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +# Represents any error related to Ballerina Avro module +public type Error distinct error; From 10bbae3c9061580754519682f46d39db0e43ad66 Mon Sep 17 00:00:00 2001 From: Nuvindu Date: Mon, 3 Jun 2024 18:34:19 +0530 Subject: [PATCH 23/34] Update the overview in docs --- README.md | 2 +- ballerina/Module.md | 6 +++--- ballerina/Package.md | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index afb885d..91c4f29 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ Ballerina LDAP Connector LDAP (Lightweight Directory Access Protocol) is an vendor-neutral software protocol for accessing and maintaining distributed directory information services. It allows users to locate organizations, individuals, and other resources such as files and devices in a network. LDAP is used in various applications for directory-based authentication and authorization. -The Ballerina LDAP module provides the capability to efficiently connect, authenticate, and interact with LDAP servers. It allows users to perform operations such as searching for entries, modifying entries in an LDAP directory, providing better support for directory-based operations. +The Ballerina LDAP module provides the capability to efficiently connect, authenticate, and interact with directory servers. It allows users to perform operations such as searching for entries, modifying entries in an LDAP directory, providing better support for directory-based operations. ### APIs associated with LDAP diff --git a/ballerina/Module.md b/ballerina/Module.md index 32829dd..19c81eb 100644 --- a/ballerina/Module.md +++ b/ballerina/Module.md @@ -2,7 +2,7 @@ LDAP (Lightweight Directory Access Protocol) is an vendor-neutral software protocol for accessing and maintaining distributed directory information services. It allows users to locate organizations, individuals, and other resources such as files and devices in a network. LDAP is used in various applications for directory-based authentication and authorization. -The Ballerina LDAP module provides the capability to efficiently connect, authenticate, and interact with LDAP servers. It allows users to perform operations such as searching for entries, modifying entries in an LDAP directory, providing better support for directory-based operations. +The Ballerina LDAP module provides the capability to efficiently connect, authenticate, and interact with directory servers. It allows users to perform operations such as searching for entries, modifying entries in an LDAP directory, providing better support for directory-based operations. ### APIs associated with LDAP @@ -17,7 +17,7 @@ Updates information of an entry. import ballerina/ldap; public function main() returns error? { - ldap:UserConfig user = { + anydata user = { sn: "User", givenName: "Updated User", displayName: "Updated User" @@ -34,6 +34,6 @@ Gets information of an entry import ballerina/ldap; public function main() returns error? { - ldap:UserConfig value = check ldapClient->getEntry(userDN); + anydata value = check ldapClient->getEntry(userDN); } ``` diff --git a/ballerina/Package.md b/ballerina/Package.md index 32829dd..ceb34c3 100644 --- a/ballerina/Package.md +++ b/ballerina/Package.md @@ -2,7 +2,7 @@ LDAP (Lightweight Directory Access Protocol) is an vendor-neutral software protocol for accessing and maintaining distributed directory information services. It allows users to locate organizations, individuals, and other resources such as files and devices in a network. LDAP is used in various applications for directory-based authentication and authorization. -The Ballerina LDAP module provides the capability to efficiently connect, authenticate, and interact with LDAP servers. It allows users to perform operations such as searching for entries, modifying entries in an LDAP directory, providing better support for directory-based operations. +The Ballerina LDAP module provides the capability to efficiently connect, authenticate, and interact with directory servers. It allows users to perform operations such as searching for entries, modifying entries in an LDAP directory, providing better support for directory-based operations. ### APIs associated with LDAP From 9773db2c06375f79092e796c148454d63fa0a527 Mon Sep 17 00:00:00 2001 From: Nuvindu Date: Mon, 3 Jun 2024 18:34:53 +0530 Subject: [PATCH 24/34] Add docs for public record types --- ballerina/client.bal | 4 ++-- ballerina/tests/test.bal | 11 ++++++----- ballerina/tests/types.bal | 39 +++++++++++++++++++++++++++++++++++++++ ballerina/types.bal | 38 ++++++++++---------------------------- 4 files changed, 57 insertions(+), 35 deletions(-) create mode 100644 ballerina/tests/types.bal diff --git a/ballerina/client.bal b/ballerina/client.bal index 31831e8..27b293e 100644 --- a/ballerina/client.bal +++ b/ballerina/client.bal @@ -37,7 +37,7 @@ public isolated client class Client { # + entry - The information to update # + return - `error` if the operation fails or `()` if successfully updated remote isolated function modify(string distinguishedName, record {|anydata...;|} entry) - returns LDAPResponse|error = @java:Method { + returns LDAPResponse|Error = @java:Method { 'class: "io.ballerina.lib.ldap.Ldap" } external; @@ -47,7 +47,7 @@ public isolated client class Client { # + targetType - Default parameter use to infer the user specified type # + return - An entry result with the given type or else an error remote isolated function getEntry(string distinguishedName, typedesc targetType = <>) - returns targetType|error = @java:Method { + returns targetType|Error = @java:Method { 'class: "io.ballerina.lib.ldap.Ldap" } external; } diff --git a/ballerina/tests/test.bal b/ballerina/tests/test.bal index 449e299..33a891a 100644 --- a/ballerina/tests/test.bal +++ b/ballerina/tests/test.bal @@ -29,15 +29,16 @@ Client ldapClient = check new ({ password: password }); + @test:Config {} public function testUpdateUser() returns error? { - UserConfig user = { - sn: "User", - givenName: "Updated User", - displayName: "Updated User" + record {} user = { + "sn": "User", + "givenName": "Updated User", + "displayName": "Updated User" }; LDAPResponse val = check ldapClient->modify(userDN, user); - test:assertEquals(val.resultCode, SUCCESS); + test:assertEquals(val.resultStatus, SUCCESS); } @test:Config { diff --git a/ballerina/tests/types.bal b/ballerina/tests/types.bal new file mode 100644 index 0000000..506035f --- /dev/null +++ b/ballerina/tests/types.bal @@ -0,0 +1,39 @@ +// Copyright (c) 2024, WSO2 LLC. (http://www.wso2.com) +// +// WSO2 LLC. licenses this file to you under the Apache License, +// Version 2.0 (the "License"); you may not use this file except +// in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +type UserConfig record { + string userPrincipalName?; + string givenName?; + string sn?; + string company?; + string co?; + string streetAddress?; + string mobile?; + string displayName?; + string middleName?; + string employeeId?; + string extensionAttribute11?; + string extensionAttribute10?; + string postalCode?; + string mail?; + string l?; + string telephoneNumber?; + string department?; + string st?; + string title?; + string distinguishedName?; + string manager?; +}; diff --git a/ballerina/types.bal b/ballerina/types.bal index 099fb8f..7aa7b06 100644 --- a/ballerina/types.bal +++ b/ballerina/types.bal @@ -27,37 +27,19 @@ public type ConnectionConfig record {| string password; |}; -public type UserConfig record { - string userPrincipalName?; - string givenName?; - string sn?; - string company?; - string co?; - string streetAddress?; - string mobile?; - string displayName?; - string middleName?; - string employeeId?; - string extensionAttribute11?; - string extensionAttribute10?; - string postalCode?; - string mail?; - string l?; - string telephoneNumber?; - string department?; - string st?; - string title?; - string distinguishedName?; - string manager?; -}; - -public type LDAPResponse record { +# LDAP response type. +# +# + matchedDN - The matched DN from the response +# + resultStatus - The operation status of the response +# + operationType - The protocol operation type +public type LDAPResponse record {| string matchedDN; - ResultCode resultCode; + Status resultStatus; string operationType; -}; +|}; -public enum ResultCode { +# Represents the status of the operation +public enum Status { SUCCESS, OPERATIONS_ERROR, PROTOCOL_ERROR, From 1a937b81267830ebec4712d5c0bf6c12528fe385 Mon Sep 17 00:00:00 2001 From: Nuvindu Date: Tue, 4 Jun 2024 09:13:12 +0530 Subject: [PATCH 25/34] Fix error handling in the APIs --- ballerina/client.bal | 6 +++--- ballerina/error.bal | 2 +- native/src/main/java/io/ballerina/lib/ldap/Ldap.java | 10 ++++++++-- .../main/java/io/ballerina/lib/ldap/ModuleUtils.java | 1 - native/src/main/java/io/ballerina/lib/ldap/Utils.java | 3 ++- 5 files changed, 14 insertions(+), 8 deletions(-) diff --git a/ballerina/client.bal b/ballerina/client.bal index 27b293e..46a4369 100644 --- a/ballerina/client.bal +++ b/ballerina/client.bal @@ -23,11 +23,11 @@ public isolated client class Client { # # + config - The configurations to be used when initializing the client # + return - An error if client initialization failed - public isolated function init(*ConnectionConfig config) returns error? { - self.initLdapConnection(config); + public isolated function init(*ConnectionConfig config) returns Error? { + check self.initLdapConnection(config); } - private isolated function initLdapConnection(ConnectionConfig config) = @java:Method { + private isolated function initLdapConnection(ConnectionConfig config) returns Error? = @java:Method { 'class: "io.ballerina.lib.ldap.Ldap" } external; diff --git a/ballerina/error.bal b/ballerina/error.bal index d672462..91e1337 100644 --- a/ballerina/error.bal +++ b/ballerina/error.bal @@ -14,5 +14,5 @@ // specific language governing permissions and limitations // under the License. -# Represents any error related to Ballerina Avro module +# Represents any error related to Ballerina LDAP module public type Error distinct error; diff --git a/native/src/main/java/io/ballerina/lib/ldap/Ldap.java b/native/src/main/java/io/ballerina/lib/ldap/Ldap.java index 8f3c515..de7d75a 100644 --- a/native/src/main/java/io/ballerina/lib/ldap/Ldap.java +++ b/native/src/main/java/io/ballerina/lib/ldap/Ldap.java @@ -29,6 +29,7 @@ import io.ballerina.runtime.api.creators.ValueCreator; import io.ballerina.runtime.api.utils.StringUtils; import io.ballerina.runtime.api.utils.ValueUtils; +import io.ballerina.runtime.api.values.BError; import io.ballerina.runtime.api.values.BMap; import io.ballerina.runtime.api.values.BObject; import io.ballerina.runtime.api.values.BString; @@ -41,6 +42,7 @@ import static io.ballerina.lib.ldap.ModuleUtils.MATCHED_DN; import static io.ballerina.lib.ldap.ModuleUtils.OPERATION_TYPE; import static io.ballerina.lib.ldap.ModuleUtils.RESULT_STATUS; +import static io.ballerina.lib.ldap.Utils.ENTRY_NOT_FOUND; /** * This class handles APIs of the LDAP client. @@ -49,7 +51,7 @@ public class Ldap { private Ldap() { } - public static void initLdapConnection(BObject ldapClient, BMap config) { + public static BError initLdapConnection(BObject ldapClient, BMap config) { String hostName = ((BString) config.get(ModuleUtils.HOST_NAME)).getValue(); int port = Math.toIntExact(config.getIntValue(ModuleUtils.PORT)); String domainName = ((BString) config.get(ModuleUtils.DOMAIN_NAME)).getValue(); @@ -58,8 +60,9 @@ public static void initLdapConnection(BObject ldapClient, BMap LDAPConnection ldapConnection = new LDAPConnection(hostName, port, domainName, password); ldapClient.addNativeData(ModuleUtils.NATIVE_CLIENT, ldapConnection); } catch (LDAPException e) { - throw new RuntimeException(e); + return Utils.createError(e.getMessage(), e); } + return null; } public static Object modify(BObject ldapClient, BString distinguishedName, BMap entry) { @@ -83,6 +86,9 @@ public static Object getEntry(BObject ldapClient, BString distinguishedName, BTy try { LDAPConnection ldapConnection = (LDAPConnection) ldapClient.getNativeData(ModuleUtils.NATIVE_CLIENT); SearchResultEntry userEntry = ldapConnection.getEntry(distinguishedName.getValue()); + if (userEntry == null) { + return Utils.createError(ENTRY_NOT_FOUND + distinguishedName, null); + } for (Attribute attribute: userEntry.getAttributes()) { entry.put(StringUtils.fromString(attribute.getName()), StringUtils.fromString(attribute.getValue())); } diff --git a/native/src/main/java/io/ballerina/lib/ldap/ModuleUtils.java b/native/src/main/java/io/ballerina/lib/ldap/ModuleUtils.java index 1855d14..3e0d23d 100644 --- a/native/src/main/java/io/ballerina/lib/ldap/ModuleUtils.java +++ b/native/src/main/java/io/ballerina/lib/ldap/ModuleUtils.java @@ -52,5 +52,4 @@ public static Module getModule() { public static void setModule(Environment env) { ldapModule = env.getCurrentModule(); } - } diff --git a/native/src/main/java/io/ballerina/lib/ldap/Utils.java b/native/src/main/java/io/ballerina/lib/ldap/Utils.java index 8383447..9ea7230 100644 --- a/native/src/main/java/io/ballerina/lib/ldap/Utils.java +++ b/native/src/main/java/io/ballerina/lib/ldap/Utils.java @@ -33,9 +33,10 @@ private Utils() { } public static final String ERROR_TYPE = "Error"; + public static final String ENTRY_NOT_FOUND = "LDAP entry is not found for DN: "; public static BError createError(String message, Throwable throwable) { - BError cause = ErrorCreator.createError(throwable); + BError cause = (throwable == null) ? null : ErrorCreator.createError(throwable); return ErrorCreator.createError(getModule(), ERROR_TYPE, fromString(message), cause, null); } } From 22a19af723190a2faeb9bcd4f72df31cda8fd6e3 Mon Sep 17 00:00:00 2001 From: Nuvindu Date: Tue, 4 Jun 2024 09:13:36 +0530 Subject: [PATCH 26/34] Add test cases to validate error handling --- ballerina/tests/test.bal | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/ballerina/tests/test.bal b/ballerina/tests/test.bal index 33a891a..da6cb3b 100644 --- a/ballerina/tests/test.bal +++ b/ballerina/tests/test.bal @@ -29,7 +29,6 @@ Client ldapClient = check new ({ password: password }); - @test:Config {} public function testUpdateUser() returns error? { record {} user = { @@ -48,3 +47,31 @@ public function testGetUser() returns error? { UserConfig value = check ldapClient->getEntry(userDN); test:assertEquals(value?.givenName, "Updated User"); } + +@test:Config {} +public function testInvalidClient() returns error? { + Client|Error ldapClient = new ({ + hostName: "111.111.11.111", + port: port, + domainName: domainName, + password: password + }); + test:assertTrue(ldapClient is Error); +} + +@test:Config {} +public function testInvalidDomainInClient() returns error? { + Client|Error ldapClient = new ({ + hostName: hostName, + port: port, + domainName: "invalid@ad.invalid", + password: password + }); + test:assertTrue(ldapClient is Error); +} + +@test:Config {} +public function testGetInvalidUser() returns error? { + UserConfig|Error value = ldapClient->getEntry("CN=Invalid User,OU=People,DC=ad,DC=windows"); + test:assertTrue(value is Error); +} From 57953d88b0bd27361366fb9fd044b99b2b23dbe4 Mon Sep 17 00:00:00 2001 From: Nuvindu Date: Tue, 4 Jun 2024 09:56:51 +0530 Subject: [PATCH 27/34] Ignore null values when updating entries --- native/src/main/java/io/ballerina/lib/ldap/Ldap.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/native/src/main/java/io/ballerina/lib/ldap/Ldap.java b/native/src/main/java/io/ballerina/lib/ldap/Ldap.java index de7d75a..81b9929 100644 --- a/native/src/main/java/io/ballerina/lib/ldap/Ldap.java +++ b/native/src/main/java/io/ballerina/lib/ldap/Ldap.java @@ -35,8 +35,8 @@ import io.ballerina.runtime.api.values.BString; import io.ballerina.runtime.api.values.BTypedesc; -import java.util.ArrayList; import java.util.List; +import java.util.stream.Collectors; import static io.ballerina.lib.ldap.ModuleUtils.LDAP_RESPONSE; import static io.ballerina.lib.ldap.ModuleUtils.MATCHED_DN; @@ -68,11 +68,11 @@ public static BError initLdapConnection(BObject ldapClient, BMap entry) { try { LDAPConnection ldapConnection = (LDAPConnection) ldapClient.getNativeData(ModuleUtils.NATIVE_CLIENT); - List modificationList = new ArrayList<>(); - for (BString key: entry.getKeys()) { - modificationList - .add(new Modification(ModificationType.REPLACE, key.getValue(), entry.get(key).getValue())); - } + List modificationList = entry.entrySet().stream() + .filter(e -> e.getValue() != null) + .map(e -> new Modification(ModificationType.REPLACE, + e.getKey().getValue(), e.getValue().getValue())) + .collect(Collectors.toList()); ModifyRequest modifyRequest = new ModifyRequest(distinguishedName.getValue(), modificationList); LDAPResult ldapResult = ldapConnection.modify(modifyRequest); return generateLdapResponse(ldapResult); From 3d03a358b7df53bfc0987353e393d49f972c57f9 Mon Sep 17 00:00:00 2001 From: Nuvindu Date: Tue, 4 Jun 2024 09:57:18 +0530 Subject: [PATCH 28/34] Add test cases to validate update API --- ballerina/tests/test.bal | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/ballerina/tests/test.bal b/ballerina/tests/test.bal index da6cb3b..1648076 100644 --- a/ballerina/tests/test.bal +++ b/ballerina/tests/test.bal @@ -75,3 +75,16 @@ public function testGetInvalidUser() returns error? { UserConfig|Error value = ldapClient->getEntry("CN=Invalid User,OU=People,DC=ad,DC=windows"); test:assertTrue(value is Error); } + +@test:Config { + groups: ["qq"] +} +public function testUpdateUserWithNullValues() returns error? { + string distinguishedName = "CN=John Doe,OU=People,DC=ad,DC=windows"; + record {} user = { + "employeeId":"30896","givenName":"John","sn":"Doe","company":"Grocery Co. USA","co":null,"streetAddress":null,"mobile":null,"displayName":"John Doe","middleName":null,"mail":null,"l":null,"telephoneNumber":null,"department":"Produce","st":null,"title":"Clerk" + }; + LDAPResponse val = check ldapClient->modify(distinguishedName, user); + test:assertEquals(val.resultStatus, SUCCESS); +} + From 78d62278b6c9537d28e14c128815284559629353 Mon Sep 17 00:00:00 2001 From: Nuvindu Date: Tue, 4 Jun 2024 12:13:21 +0530 Subject: [PATCH 29/34] Fix receiving attribute values from entries --- ballerina/tests/test.bal | 4 +--- native/src/main/java/io/ballerina/lib/ldap/Ldap.java | 9 ++++++++- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/ballerina/tests/test.bal b/ballerina/tests/test.bal index 1648076..358e7bb 100644 --- a/ballerina/tests/test.bal +++ b/ballerina/tests/test.bal @@ -76,9 +76,7 @@ public function testGetInvalidUser() returns error? { test:assertTrue(value is Error); } -@test:Config { - groups: ["qq"] -} +@test:Config {} public function testUpdateUserWithNullValues() returns error? { string distinguishedName = "CN=John Doe,OU=People,DC=ad,DC=windows"; record {} user = { diff --git a/native/src/main/java/io/ballerina/lib/ldap/Ldap.java b/native/src/main/java/io/ballerina/lib/ldap/Ldap.java index 81b9929..4c8e912 100644 --- a/native/src/main/java/io/ballerina/lib/ldap/Ldap.java +++ b/native/src/main/java/io/ballerina/lib/ldap/Ldap.java @@ -26,6 +26,7 @@ import com.unboundid.ldap.sdk.ModificationType; import com.unboundid.ldap.sdk.ModifyRequest; import com.unboundid.ldap.sdk.SearchResultEntry; +import com.unboundid.util.Base64; import io.ballerina.runtime.api.creators.ValueCreator; import io.ballerina.runtime.api.utils.StringUtils; import io.ballerina.runtime.api.utils.ValueUtils; @@ -90,7 +91,13 @@ public static Object getEntry(BObject ldapClient, BString distinguishedName, BTy return Utils.createError(ENTRY_NOT_FOUND + distinguishedName, null); } for (Attribute attribute: userEntry.getAttributes()) { - entry.put(StringUtils.fromString(attribute.getName()), StringUtils.fromString(attribute.getValue())); + if (attribute.needsBase64Encoding()) { + entry.put(StringUtils.fromString(attribute.getName()), + StringUtils.fromString(Base64.encode(attribute.getValueByteArray()))); + } else { + entry.put(StringUtils.fromString(attribute.getName()), + StringUtils.fromString(attribute.getValue())); + } } return ValueUtils.convert(entry, typeParam.getDescribingType()); } catch (LDAPException e) { From 1ea0d5282d1f3c6a2ccff7e8682d6bc8515debaa Mon Sep 17 00:00:00 2001 From: Nuvindu Date: Tue, 4 Jun 2024 19:09:53 +0530 Subject: [PATCH 30/34] Fix getting unreadable strings for objectGUID and objectSid --- .../main/java/io/ballerina/lib/ldap/Ldap.java | 101 ++++++++++++++++-- .../io/ballerina/lib/ldap/ModuleUtils.java | 3 + 2 files changed, 96 insertions(+), 8 deletions(-) diff --git a/native/src/main/java/io/ballerina/lib/ldap/Ldap.java b/native/src/main/java/io/ballerina/lib/ldap/Ldap.java index 4c8e912..051d1c9 100644 --- a/native/src/main/java/io/ballerina/lib/ldap/Ldap.java +++ b/native/src/main/java/io/ballerina/lib/ldap/Ldap.java @@ -41,6 +41,8 @@ import static io.ballerina.lib.ldap.ModuleUtils.LDAP_RESPONSE; import static io.ballerina.lib.ldap.ModuleUtils.MATCHED_DN; +import static io.ballerina.lib.ldap.ModuleUtils.OBJECT_GUID; +import static io.ballerina.lib.ldap.ModuleUtils.OBJECT_SID; import static io.ballerina.lib.ldap.ModuleUtils.OPERATION_TYPE; import static io.ballerina.lib.ldap.ModuleUtils.RESULT_STATUS; import static io.ballerina.lib.ldap.Utils.ENTRY_NOT_FOUND; @@ -90,14 +92,8 @@ public static Object getEntry(BObject ldapClient, BString distinguishedName, BTy if (userEntry == null) { return Utils.createError(ENTRY_NOT_FOUND + distinguishedName, null); } - for (Attribute attribute: userEntry.getAttributes()) { - if (attribute.needsBase64Encoding()) { - entry.put(StringUtils.fromString(attribute.getName()), - StringUtils.fromString(Base64.encode(attribute.getValueByteArray()))); - } else { - entry.put(StringUtils.fromString(attribute.getName()), - StringUtils.fromString(attribute.getValue())); - } + for (Attribute attribute : userEntry.getAttributes()) { + processAttribute(attribute, entry); } return ValueUtils.convert(entry, typeParam.getDescribingType()); } catch (LDAPException e) { @@ -105,6 +101,25 @@ public static Object getEntry(BObject ldapClient, BString distinguishedName, BTy } } + private static void processAttribute(Attribute attribute, BMap entry) { + BString attributeName = StringUtils.fromString(attribute.getName()); + if (attribute.needsBase64Encoding()) { + String readableString = encodeAttributeValue(attribute); + entry.put(attributeName, StringUtils.fromString(readableString)); + } else { + entry.put(attributeName, StringUtils.fromString(attribute.getValue())); + } + } + + private static String encodeAttributeValue(Attribute attribute) { + byte[] valueBytes = attribute.getValueByteArray(); + return switch (attribute.getName()) { + case OBJECT_GUID -> convertObjectGUIDToString(valueBytes); + case OBJECT_SID -> convertObjectSidToString(valueBytes); + default -> Base64.encode(valueBytes); + }; + } + private static BMap generateLdapResponse(LDAPResult ldapResult) { BMap response = ValueCreator.createRecordValue(ModuleUtils.getModule(), LDAP_RESPONSE); response.put(StringUtils.fromString(MATCHED_DN), StringUtils.fromString(ldapResult.getMatchedDN())); @@ -114,4 +129,74 @@ private static BMap generateLdapResponse(LDAPResult ldapResult) StringUtils.fromString(ldapResult.getOperationType().name())); return response; } + + public static String convertObjectSidToString(byte[] objectSid) { + int offset, size; + if (objectSid[0] != 1) { + throw new IllegalArgumentException("objectSid revision must be 1"); + } + StringBuilder stringSidBuilder = new StringBuilder("S-1-"); + int subAuthorityCount = objectSid[1] & 0xFF; + long identifierAuthority = 0; + offset = 2; + size = 6; + for (int i = 0; i < size; i++) { + identifierAuthority |= (long) (objectSid[offset + i] & 0xFF) << (8 * (size - 1 - i)); + } + if (identifierAuthority < Math.pow(2, 32)) { + stringSidBuilder.append(identifierAuthority); + } else { + stringSidBuilder.append("0x").append( + Long.toHexString(identifierAuthority).toUpperCase()); + } + offset = 8; + size = 4; + for (int i = 0; i < subAuthorityCount; i++, offset += size) { + long subAuthority = 0; + for (int j = 0; j < size; j++) { + subAuthority |= (long) (objectSid[offset + j] & 0xFF) << (8 * j); + } + stringSidBuilder.append("-").append(subAuthority); + } + + return stringSidBuilder.toString(); + } + + public static String convertObjectGUIDToString(byte[] objectGUID) { + StringBuilder displayStr = new StringBuilder(); + displayStr.append(prefixZeros((int) objectGUID[3] & 0xFF)); + displayStr.append(prefixZeros((int) objectGUID[2] & 0xFF)); + displayStr.append(prefixZeros((int) objectGUID[1] & 0xFF)); + displayStr.append(prefixZeros((int) objectGUID[0] & 0xFF)); + displayStr.append("-"); + displayStr.append(prefixZeros((int) objectGUID[5] & 0xFF)); + displayStr.append(prefixZeros((int) objectGUID[4] & 0xFF)); + displayStr.append("-"); + displayStr.append(prefixZeros((int) objectGUID[7] & 0xFF)); + displayStr.append(prefixZeros((int) objectGUID[6] & 0xFF)); + displayStr.append("-"); + displayStr.append(prefixZeros((int) objectGUID[8] & 0xFF)); + displayStr.append(prefixZeros((int) objectGUID[9] & 0xFF)); + displayStr.append("-"); + displayStr.append(prefixZeros((int) objectGUID[10] & 0xFF)); + displayStr.append(prefixZeros((int) objectGUID[11] & 0xFF)); + displayStr.append(prefixZeros((int) objectGUID[12] & 0xFF)); + displayStr.append(prefixZeros((int) objectGUID[13] & 0xFF)); + displayStr.append(prefixZeros((int) objectGUID[14] & 0xFF)); + displayStr.append(prefixZeros((int) objectGUID[15] & 0xFF)); + return displayStr.toString(); + } + + + private static String prefixZeros(int value) { + if (value <= 0xF) { + StringBuilder sb = new StringBuilder("0"); + sb.append(Integer.toHexString(value)); + + return sb.toString(); + + } else { + return Integer.toHexString(value); + } + } } diff --git a/native/src/main/java/io/ballerina/lib/ldap/ModuleUtils.java b/native/src/main/java/io/ballerina/lib/ldap/ModuleUtils.java index 3e0d23d..1c566fd 100644 --- a/native/src/main/java/io/ballerina/lib/ldap/ModuleUtils.java +++ b/native/src/main/java/io/ballerina/lib/ldap/ModuleUtils.java @@ -41,6 +41,9 @@ public final class ModuleUtils { public static final String MATCHED_DN = "matchedDN"; public static final String OPERATION_TYPE = "operationType"; + public static final String OBJECT_GUID = "objectGUID"; + public static final String OBJECT_SID = "objectSid"; + private ModuleUtils() {} private static Module ldapModule = null; From 47cc9cdf0e7aaeec22426f536b38bfe2c80a0d10 Mon Sep 17 00:00:00 2001 From: Nuvindu Date: Tue, 4 Jun 2024 19:10:18 +0530 Subject: [PATCH 31/34] Remove additional empty line --- ballerina/tests/test.bal | 1 - 1 file changed, 1 deletion(-) diff --git a/ballerina/tests/test.bal b/ballerina/tests/test.bal index 358e7bb..4d40e6e 100644 --- a/ballerina/tests/test.bal +++ b/ballerina/tests/test.bal @@ -85,4 +85,3 @@ public function testUpdateUserWithNullValues() returns error? { LDAPResponse val = check ldapClient->modify(distinguishedName, user); test:assertEquals(val.resultStatus, SUCCESS); } - From 709e0d3a92c797aeff9622be50819f0cea28a24d Mon Sep 17 00:00:00 2001 From: Nuvindu Date: Tue, 4 Jun 2024 19:50:53 +0530 Subject: [PATCH 32/34] Add error record type to describe errors --- ballerina/error.bal | 9 ++++++++ .../main/java/io/ballerina/lib/ldap/Ldap.java | 3 ++- .../java/io/ballerina/lib/ldap/Utils.java | 22 ++++++++++++++++++- 3 files changed, 32 insertions(+), 2 deletions(-) diff --git a/ballerina/error.bal b/ballerina/error.bal index 91e1337..1bc0c1c 100644 --- a/ballerina/error.bal +++ b/ballerina/error.bal @@ -16,3 +16,12 @@ # Represents any error related to Ballerina LDAP module public type Error distinct error; + +# The error details type for the Ballerina LDAP module. +# +# + resultStatus - The status of the error +# + message - The error message +public type ErrorDetails record {| + string resultStatus?; + string message?; +|}; diff --git a/native/src/main/java/io/ballerina/lib/ldap/Ldap.java b/native/src/main/java/io/ballerina/lib/ldap/Ldap.java index 051d1c9..b279d71 100644 --- a/native/src/main/java/io/ballerina/lib/ldap/Ldap.java +++ b/native/src/main/java/io/ballerina/lib/ldap/Ldap.java @@ -39,6 +39,7 @@ import java.util.List; import java.util.stream.Collectors; +import static com.unboundid.ldap.sdk.ResultCode.NO_SUCH_OBJECT; import static io.ballerina.lib.ldap.ModuleUtils.LDAP_RESPONSE; import static io.ballerina.lib.ldap.ModuleUtils.MATCHED_DN; import static io.ballerina.lib.ldap.ModuleUtils.OBJECT_GUID; @@ -90,7 +91,7 @@ public static Object getEntry(BObject ldapClient, BString distinguishedName, BTy LDAPConnection ldapConnection = (LDAPConnection) ldapClient.getNativeData(ModuleUtils.NATIVE_CLIENT); SearchResultEntry userEntry = ldapConnection.getEntry(distinguishedName.getValue()); if (userEntry == null) { - return Utils.createError(ENTRY_NOT_FOUND + distinguishedName, null); + return Utils.createError(ENTRY_NOT_FOUND + distinguishedName, new LDAPException(NO_SUCH_OBJECT)); } for (Attribute attribute : userEntry.getAttributes()) { processAttribute(attribute, entry); diff --git a/native/src/main/java/io/ballerina/lib/ldap/Utils.java b/native/src/main/java/io/ballerina/lib/ldap/Utils.java index 9ea7230..da72dea 100644 --- a/native/src/main/java/io/ballerina/lib/ldap/Utils.java +++ b/native/src/main/java/io/ballerina/lib/ldap/Utils.java @@ -18,8 +18,14 @@ package io.ballerina.lib.ldap; +import com.unboundid.ldap.sdk.LDAPException; import io.ballerina.runtime.api.creators.ErrorCreator; +import io.ballerina.runtime.api.creators.ValueCreator; import io.ballerina.runtime.api.values.BError; +import io.ballerina.runtime.api.values.BMap; +import io.ballerina.runtime.api.values.BString; + +import java.util.Map; import static io.ballerina.lib.ldap.ModuleUtils.getModule; import static io.ballerina.runtime.api.utils.StringUtils.fromString; @@ -33,10 +39,24 @@ private Utils() { } public static final String ERROR_TYPE = "Error"; + public static final String ERROR_DETAILS = "ErrorDetails"; + public static final String RESULT_STATUS = "resultStatus"; + public static final String ERROR_MESSAGE = "message"; public static final String ENTRY_NOT_FOUND = "LDAP entry is not found for DN: "; public static BError createError(String message, Throwable throwable) { BError cause = (throwable == null) ? null : ErrorCreator.createError(throwable); - return ErrorCreator.createError(getModule(), ERROR_TYPE, fromString(message), cause, null); + BMap errorDetails = getErrorDetails(throwable); + return ErrorCreator.createError(getModule(), ERROR_TYPE, fromString(message), cause, errorDetails); + } + + private static BMap getErrorDetails(Throwable throwable) { + if (throwable instanceof LDAPException) { + String resultStatus = ((LDAPException) throwable).getResultCode().getName(); + String message = ((LDAPException) throwable).getExceptionMessage(); + return ValueCreator.createRecordValue(getModule(), ERROR_DETAILS, + Map.of(RESULT_STATUS, resultStatus, ERROR_MESSAGE, message)); + } + return ValueCreator.createRecordValue(getModule(), ERROR_DETAILS); } } From a2cb17cf93f6841db67613994573c981807f3960 Mon Sep 17 00:00:00 2001 From: Nuvindu Date: Wed, 5 Jun 2024 03:52:24 +0530 Subject: [PATCH 33/34] Remove unnecessary external library versions --- ballerina/build.gradle | 2 +- gradle.properties | 8 +------- native/build.gradle | 2 +- 3 files changed, 3 insertions(+), 9 deletions(-) diff --git a/ballerina/build.gradle b/ballerina/build.gradle index 4a546b6..7415234 100644 --- a/ballerina/build.gradle +++ b/ballerina/build.gradle @@ -63,7 +63,7 @@ dependencies { jbalTools("org.ballerinalang:jballerina-tools:${ballerinaLangVersion}") { transitive = false } - externalJars(group: 'com.unboundid', name: 'unboundid-ldapsdk', version: '7.0.0') { + externalJars(group: 'com.unboundid', name: 'unboundid-ldapsdk', version: "${unboundIdLdapVersion}") { transitive = false } } diff --git a/gradle.properties b/gradle.properties index d13925c..ca0d88d 100644 --- a/gradle.properties +++ b/gradle.properties @@ -9,11 +9,5 @@ shadowJarPluginVersion=8.1.1 downloadPluginVersion=5.4.0 releasePluginVersion=2.8.0 ballerinaGradlePluginVersion=2.2.4 -jacocoVersion=0.8.10 -slf4jVersion=1.7.21 -jacksonVersion=2.17.0 -kafkaClientVersion=3.7.0 -kafkaSchemaRegistryVersion=5.3.2 -commonConfigVersion=5.3.0 -commonUtilsVersion=5.3.0 +unboundIdLdapVersion=7.0.0 diff --git a/native/build.gradle b/native/build.gradle index 565ca24..43416ce 100644 --- a/native/build.gradle +++ b/native/build.gradle @@ -35,7 +35,7 @@ dependencies { checkstyle "com.puppycrawl.tools:checkstyle:${checkstylePluginVersion}" implementation group: 'org.ballerinalang', name: 'ballerina-runtime', version: "${ballerinaLangVersion}" - implementation 'com.unboundid:unboundid-ldapsdk:7.0.0' + implementation group: 'com.unboundid', name: 'unboundid-ldapsdk', version: "${unboundIdLdapVersion}" } tasks.withType(JavaCompile) { From 4bb74a88d7734a50b193bbbe6e24d559d45280ef Mon Sep 17 00:00:00 2001 From: Nuvindu Date: Wed, 5 Jun 2024 03:55:33 +0530 Subject: [PATCH 34/34] Fix example code in docs --- README.md | 4 ++-- ballerina/Package.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 91c4f29..b3c4b89 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ Updates information of an entry. import ballerina/ldap; public function main() returns error? { - ldap:UserConfig user = { + anydata user = { sn: "User", givenName: "Updated User", displayName: "Updated User" @@ -35,6 +35,6 @@ Gets information of an entry import ballerina/ldap; public function main() returns error? { - ldap:UserConfig value = check ldapClient->getEntry(userDN); + anydata value = check ldapClient->getEntry(userDN); } ``` diff --git a/ballerina/Package.md b/ballerina/Package.md index ceb34c3..19c81eb 100644 --- a/ballerina/Package.md +++ b/ballerina/Package.md @@ -17,7 +17,7 @@ Updates information of an entry. import ballerina/ldap; public function main() returns error? { - ldap:UserConfig user = { + anydata user = { sn: "User", givenName: "Updated User", displayName: "Updated User" @@ -34,6 +34,6 @@ Gets information of an entry import ballerina/ldap; public function main() returns error? { - ldap:UserConfig value = check ldapClient->getEntry(userDN); + anydata value = check ldapClient->getEntry(userDN); } ```