Thursday, June 9, 2022

Cannot navigate to the symbol under the carret after updating VS 2022

After VS 2022 has been updated, suddenly some projects refuse to provide navigation between tokens, you can't just click on anything and "Go to Definition..."
There are couple of suggested workarounds, including restarting VS, deleting *.user files or going to Options/Debugging/Symbols and invoking Empty symbol cache
A working solution has been found here and it consists in updating the Microsoft.Net.Compilers package referenced in affected projects to a newer version.
This however, potentially causes another issue, this time on a build server, if you happen to rely on msbuild. This time, the MSB4184 is thrown
error MSB4184: The expression ""App_Start\RouteConfig.cs".GetPathsOfAllDirectoriesAbove()" 
cannot be evaluated. Method 'System.String.GetPathsOfAllDirectoriesAbove' not found
Turns out, this also has been observed. The proposed solution is to uninstall the Microsoft.Net.Compilers, this breaks token resolution is VS, though.
What seems to work for us is to have
  • Microsoft.Net.Compilers 2.10
  • Microsoft.CodeDom.Providers.DotNetCompilerPlatform 3.60

Thursday, May 5, 2022

Visual Studio 2022, Environment/Startup

What happens when you just open the Visual Studio? It displays a solution selector where you pick up one of latests solutions. This is the default behavior. This can be changed, if you go to Options and then go to Environment/Startup page, you can change this to just show an empty environment.

People got used to it, also, it's mentioned in the documentation

The only problem is that the Environment/Startup is not there in Visual Studio 2022.

Instead, it's been moved to Environment/General, unfortunately, it's at the very bottom of the page and can easily be missed because you need to actually scroll down the page to see it

Monday, April 25, 2022

Node.js + Express + TypeScript

The Node.js + Express + Javascript is a great platform and this short tutorial shows how to add TypeScript to the picture.

Start with installing TypeScript compiler globally

npm install typescript -g
so that whenever you invoke tsc from the command line, the compiler follows your tsconfig.json and does its job. Then add the tsconfig.json
{
    "compilerOptions": {
        "lib": [
            "es2021", "DOM"
        ],
        "module": "commonjs",
        "target": "es2021",
        "strict": true,
        "esModuleInterop": true,
        "sourceMap": true                
    }
}
Note that we specifically ask the module subsystem to translate TypeScript's export/imports into node.js commonjs modules. We also turn on the esModuleInterop flag for better node.js compatibility.

Now install some typings for node and express (other typings should possibly also be installed, depending on what packages you are going to use.

npm i --save-dev @types/node
npm i --save-dev @types/express

Create the app.ts file, which will be the starting point of the app and add .vscode/launch.json with

{
    "version": "0.2.0",
    "configurations": [
        {
            "type": "pwa-node",
            "request": "launch",
            "name": "Launch Program",
            "skipFiles": [
                "<node_internals>/**"
            ],
            "program": "${workspaceFolder}\\app.ts",
            "outFiles": [
                "${workspaceFolder}/**/*.js"
            ]
        }
    ]
}
Note how the entry point (program) is set up here. Because the Typescript compiler will generate source maps, you would be able to run&debug the Typescript code.

Writing Typescript is easy now. Create an example express app

import * as http from 'http';
import express from 'express';

var app = express();

app.get('/', (req, res) => {
    res.write('hello world');
    res.end();
});

var server = http.createServer(app)

server.listen(3000);

console.log( 'started' );
Note how express is imported and how a library module (http) is imported - in both cases, there's the full intellisense support in VS Code as well as the support from the Typescript compiler.

To compile the code just invoke

tsc
once. To run the compiler in the watch mode:
tsc --watch

To show how TS modules work, create a simple module, m.ts with

class Example {
    DoWork( s: string ) : string {
        return s + ' from Example';
    }
}

export default Example;
and import it in the app.ts
import * as http from 'http';
import express from 'express';
import Example from './m';

var app = express();

app.get('/', (req, res) => {

    let e = new Example();

    res.write(e.DoWork('hello world'));
    res.end();
});

var server = http.createServer(app)

server.listen(3000);

console.log( 'started' );
Note how m.ts is compiled to m.js and also note how m.js is imported in the app.js (compiled from app.ts).

Wednesday, April 13, 2022

Simplest SAML1.1 (WS-Federation) Federated Authentication in .NET 6

Years ago I've blogged on how to do the WS-Fed using code only in .NET. The approach had multiple advantages over the static WSFederationAuthenticationModule (the WSFam) configured in web.config. The most important feature here is that you have the most control over what happens: how the redirect to the Identity Provider is created and how the SAML token response is consumed.
This is extremely useful in complex scenarios, e.g. in multitenant apps where tenants are configured individually or multiple identity providers are allowed or even when the federated authentication is optional at all.
.NET 6 (.NET Core) has its own way of handling WS-Federation, namely, it registers its own middleware (AddWsFederation) that can be controlled with few configuration options. However, I still feel I miss some features, like triggering the flow conditionally in a multitenant app. What I need is not yet another middleware but rather, a slightly more low-level approach following the basic principle: not use module/middleware but rather have a custom code in the logon controller/action:
if ( !IsWsFedResponse() )
{
   RedirectToIdP();
}
else
{
  var token = GetTokenFromResponse();
  var principal = ValidateToken();
  
  AuthenticateUsingPrincipal(principal);
}
Please refer to the blog entry I've linked at the top to see the approach presented there follows this.
Can we have a similar flow in .NET Core? Ignore the middleware but rather have a total control over the WS-Fed?
The answer is: sure. Since the docs are sparse and there are not-that-much examples, a decompiler is handy to just see how things are done internally so we don't rewrite anything that's already there.
So, just setup your cookie authentication and point your unauthenticated users to /Account/Logon. And then:
public class AccountController : Controller
{
	private KeyValuePair Convert(KeyValuePair pair)
	{
		return new KeyValuePair(pair.Key, pair.Value);
	}

	public async Task Logon()
	{
		WsFederationMessage wsFederationMessage = null;

		if (HttpMethods.IsPost(base.Request.Method))
		{
			var parameters = (await this.Request.ReadFormAsync()).Select(Convert);
			wsFederationMessage = new WsFederationMessage(parameters);
		}

		if (wsFederationMessage == null || !wsFederationMessage.IsSignInMessage)
		{
			var signInMessage           = new WsFederationMessage();
            
			signInMessage.IssuerAddress = "https://issuer.address/tenant/fs/ls";
			signInMessage.Wtrealm       = "https://realm.address/tenant/account/logon";

			var redirectUri = signInMessage.CreateSignInUrl();

			return Redirect(redirectUri);
		}
		else
		{
			string token = wsFederationMessage.GetToken();

			SecurityToken validatedToken;

			var tokenHandler = 
				new SamlSecurityTokenHandler()
				{
					MaximumTokenSizeInBytes = Int32.MaxValue                        
				};
			var tokenValidationParameters = new TokenValidationParameters()
			{
				AudienceValidator = (audiences, token, parameters) =>
				{
					return true;
				},
				IssuerValidator = (issuer, token, parameters) =>
				{
					return issuer;
				},
				IssuerSigningKeyValidator = (key, token, parameters) =>
				{
					if ( key is X509SecurityKey )
					{
						X509SecurityKey x509Key = (X509SecurityKey)key;

						// validate cert thumb
						// return x509Key.Certificate.Thumbprint == "alamakota";
						return true;
					}
					else
					{
						return false;
					}
				},   
				IssuerSigningKeyResolver = (token, securityToken, kid, parameters) =>
				{
					var samlToken   = (SamlSecurityToken)securityToken;
					var signature   = samlToken.Assertion.Signature;
                    
					// rewrite this to handle edge cases!
					var certificate = signature.KeyInfo.X509Data.FirstOrDefault().Certificates.FirstOrDefault();

					var x509Certificate2 = new X509Certificate2(System.Convert.FromBase64String(certificate));

					return new List()
					{
						new X509SecurityKey(x509Certificate2)
					};
				}
			};

			// if this succeeds - we have the principal
			var validatedPrincipal = tokenHandler.ValidateToken(token, tokenValidationParameters, out validatedToken);

			// we strip all claims except the user name to have a complete control over how the cookie is issued
			List claims = new List
				{
					new Claim(ClaimTypes.Name, validatedPrincipal.Identity.Name)
				};

			// create identity
			var identity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
			ClaimsPrincipal principal = new ClaimsPrincipal(identity);

			await this.HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, principal);

			return Redirect("/");
		}
	}
}
Note how the IssuerSigningKeyValidator delegate replaces the IssuerNameRegistry from the .NET Framework example (linked blog entry). Also note, that other configuration options of the TokenValidationParameters can be changed freely here on a per-request basis.

Friday, March 25, 2022

Guess6 published on GitHub

Decided to polish the code a little bit and published it on GitHub. Such small yet complete apps are a great sandboxes to test/show various programming techniques. Expect more features soon then (a redux store is the next step).

Tuesday, February 22, 2022

Guess6 in React - source

The game itself is published here. Here's the full source code. It's surprisingly consise, feel free to study it on your own.
// index.tsx
import React from 'react';
import ReactDOM from 'react-dom';

import App from '@/app';

/**
 * Entry point
 */
class Program {
    
    Main() {

        var app = (
                <App />
        );

        ReactDOM.render(app, document.getElementById('guess6'));
    }
}

new Program().Main();

// App.tsx
import React, { useEffect, useState } from 'react';
import Dictionary from './dictionary';
import Keyboard from './keyboard';
import WordMatch from './wordMatch';

const App = () => {

  const EXPECTEDLENGTH = 6;

  const [words, setWords] = useState<Array<string>>([]);
  const [secretWord, setSecretWord] = useState<string>('');

  function getRandomWord(Dictionary: string[]): string {
    const randomIndex = Math.floor(Math.random() * (Dictionary.length));
    return Dictionary[randomIndex].toUpperCase();
  }

  function restartGame() {
      setWords([]);
      setSecretWord(getRandomWord(Dictionary));
  }
  
  function onWordTyped( newWord: string ) {
    setWords( words => words.concat([newWord]) );   
  }

  function giveUp() {
    if ( secretWord.length == EXPECTEDLENGTH ) {
      setWords( words => words.concat( secretWord ) );
    }
  }

  useEffect( () => {
    restartGame();
  }, []);

  return <>  
    <div>
      <button className='flatButton' onClick={() => restartGame()}>NEW GAME</button>
      <button className='flatButton' onClick={() => giveUp()}>GIVE UP</button>
    </div>
    <h1>Enter {EXPECTEDLENGTH}-letter word</h1>
    <Keyboard dictionary={Dictionary} expectedLength={EXPECTEDLENGTH} onWordTyped={onWordTyped} />
    {words.map( (word, index) => <ordMatch candidate={word} secret={secretWord} key={index} />)}
  </>
};

export default App;

// Keyboard.tsx
import React, { KeyboardEvent, useEffect, useState } from 'react';

const Keyboard = ({dictionary, expectedLength, onWordTyped} : 
    {dictionary: string[] | undefined, expectedLength: number, onWordTyped: (word: string) => void}) => {

    const [message, setMessage] = useState<string>('');
    const [word, setWord]       = useState<string>('');

    const LETTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    const QWERTY  = "QWERTYUIOP";
    const ASDF    = "ASDFGHJKL";
    const ZXCV    = "ZXCVBNM";

    function appendLetter(letter: string) {
        if ( word.length < expectedLength ) {
            setWord( w => w + letter );
            setMessage('');
        }
    }

    function clearWord() {
        setWord('');
        setMessage('');
    }

    function tryAcceptWord() {
        if ( word.length != expectedLength ) {
            setMessage(`Expected ${expectedLength} characters, got ${word.length} so far`);
            return;
        } 
        
        if ( dictionary !== undefined && dictionary.map( w => w.toUpperCase() ).indexOf( word ) < 0 ) {
            setMessage(`Word ${word} not in dictionary`);
            return;
        }
      
        onWordTyped(word);
        setWord('');
    }

    return <div>
        <div>
            <input className='keyboardInput' value={word} readOnly />
        </div>
        <div className='firstRow'>
            {QWERTY.split('').map( (letter) => <button className='letterButton flatButton'  
              onClick={() => appendLetter(letter)} key={letter}>{letter}</button> )}
        </div>
        <div className='secondRow'>
            {ASDF.split('').map( (letter) => <button className='letterButton flatButton'  
              onClick={() => appendLetter(letter)} key={letter}>{letter}</button> )}
            <button className='flatButton' onClick={() => clearWord()}>DEL</button>
        </div>
        <div className='thirdRow'>
            {ZXCV.split('').map( (letter) => <button className='letterButton flatButton'  
              onClick={() => appendLetter(letter)} key={letter}>{letter}</button> )}
            <button className='flatButton' onClick={() => tryAcceptWord()}>ENTER</button>
        </div>
        <div>{message}</div>
    </div>;
}

export default Keyboard;

// WordMatch.tsx
import React from 'react';

const WordMatch = ({candidate, secret} : {candidate: string, secret: string}) => {

    if ( candidate.length != secret.length ) {
        throw new Error('candidate and secret word must have same length');
    }

    type  letterState = 'USED' | undefined;
    const letterStates: Array<letterState> = Array<letterState>(candidate.length);

    function getLetterClass( index: number ) : string {        
        // match
        if ( secret[index] == candidate[index] ) {
            letterStates[index] = 'USED';
            return 'letter letterMatch';
        }
        // possible
        for ( let i=0; i<secret.length; i++ ) {
            if ( secret[i] == candidate[index] &&
                 letterStates[i] == undefined 
                ) {
                letterStates[i] = 'USED';
                return 'letter letterPossible';
            }
        }
        // none
        return 'letter letterWrong';
    }

    return <div>
        {candidate.split('').map( (letter, index) => 
            <span className={getLetterClass(index)} key={index}>{letter}</span>
        )}
    </div>;
}

export default WordMatch;

// Dictionary.ts
// https://eslforums.com/6-letter-words/

const Dictionary: Array<string> = [
"abacus",
// the rest of the dictionary here
"zoning"
];

export default Dictionary;

Guess6 in React